Exemple #1
0
class BaseIPSimulation2D(BaseDCSimulation2D):

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

    _data_type = properties.StringChoice(
        "IP data type",
        default="volt",
        choices=["volt", "apparent_chargeability"],
    )

    data_type = deprecate_property(
        _data_type,
        "data_type",
        new_name="receiver.data_type",
        removal_version="0.17.0",
        future_warn=True,
    )

    fieldsPair = Fields2D
    _Jmatrix = None
    _f = None  # the DC fields
    _sign = 1.0
    _pred = None
    _scale = None
    gtgdiag = None

    def fields(self, m):
        if self.verbose:
            print(">> Compute DC fields")
        if self._f is None:
            # re-uses the DC simulation's fields method
            self._f = super().fields(None)

        if self._scale is None:
            scale = Data(self.survey, np.full(self.survey.nD, self._sign))
            f = self.fields_to_space(self._f)
            # loop through receievers to check if they need to set the _dc_voltage
            for src in self.survey.source_list:
                for rx in src.receiver_list:
                    if (rx.data_type == "apparent_chargeability"
                            or self._data_type == "apparent_chargeability"):
                        scale[src,
                              rx] = self._sign / rx.eval(src, self.mesh, f)
            self._scale = scale.dobs

        self._pred = self.forward(m, f=self._f)

        return self._f

    def dpred(self, m=None, f=None):
        """
        Predicted data.

        .. math::

            d_\\text{pred} = Pf(m)

        """
        # return self.Jvec(m, m, f=f)
        if f is None:
            f = self.fields(m)

        return self._pred

    def getJtJdiag(self, m, W=None):
        if self.gtgdiag is None:
            J = self.getJ(m)
            if W is None:
                W = self._scale**2
            else:
                W = (self._scale * W.diagonal())**2

            self.gtgdiag = np.einsum("i,ij,ij->j", W, J, J)

        return self.gtgdiag

    def Jvec(self, m, v, f=None):
        return self._scale * super().Jvec(m, v, f)

    def forward(self, m, f=None):
        return self.Jvec(m, m, f=f)

    def Jtvec(self, m, v, f=None):
        return super().Jtvec(m, v * self._scale, f)

    @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 = 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 * (sdiag(u) * v)
            else:
                return sdiag(u) * (self.MeSigmaDerivMat * v)
        else:
            dsigma_dlogsigma = 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 = sdiag(rho) * self.etaDeriv
            self._MccRhoiDerivMat = sdiag(vol * (-1.0 / 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 * (sdiag(u) * v)
            else:
                return sdiag(u) * (self.MccRhoiDerivMat * v)
        else:
            vol = self.mesh.vol
            rho = self.rho
            drho_dlogrho = sdiag(rho) * self.etaDeriv
            if adjoint:
                return drho_dlogrho.T * (sdiag(u * vol * (-1.0 / rho**2)) * v)
            else:
                return sdiag(u * vol * (-1.0 / 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 = sdiag(sigma) * self.etaDeriv
            self._MnSigmaDerivMat = self.mesh.aveN2CC.T * 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 * (sdiag(u) * v)
            else:
                return sdiag(u) * (self.MnSigmaDerivMat * v)
        else:
            sigma = self.sigma
            vol = self.mesh.vol
            dsigma_dlogsigma = 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))
Exemple #2
0
class BaseRx(SimPEG.Survey.BaseTimeRx):
    locs = None
    rxType = None

    data_type = properties.StringChoice(
        "Type of DC-IP survey",
        required=True,
        default="volt",
        choices=["volt", "apparent_resistivity", "apparent_chargeability"])

    data_type = 'volt'

    knownRxTypes = {
        'phi': ['phi', None],
        'ex': ['e', 'x'],
        'ey': ['e', 'y'],
        'ez': ['e', 'z'],
        'jx': ['j', 'x'],
        'jy': ['j', 'y'],
        'jz': ['j', 'z'],
    }

    def __init__(self, locs, times, rxType, **kwargs):
        SimPEG.Survey.BaseTimeRx.__init__(self, locs, times, rxType, **kwargs)

    @property
    def dc_voltage(self):
        return self._dc_voltage

    @property
    def projField(self):
        """Field Type projection (e.g. e b ...)"""
        return self.knownRxTypes[self.rxType][0]

    def projGLoc(self, f):
        """Grid Location projection (e.g. Ex Fy ...)"""
        comp = self.knownRxTypes[self.rxType][1]
        if comp is not None:
            return f._GLoc(self.rxType) + comp
        return f._GLoc(self.rxType)

    def getTimeP(self, timesall):
        """
            Returns the time projection matrix.

            .. note::

                This is not stored in memory, but is created on demand.
        """
        time_inds = np.in1d(timesall, self.times)
        return time_inds

    def eval(self, src, mesh, f):
        P = self.getP(mesh, self.projGLoc(f))
        return P * f[src, self.projField]

    def evalDeriv(self, src, mesh, f, v, adjoint=False):
        P = self.getP(mesh, self.projGLoc(f))
        if not adjoint:
            return P * v
        elif adjoint:
            return P.T * v
Exemple #3
0
class BaseRx(survey.BaseRx):
    """
    Frequency domain receiver base class

    :param numpy.ndarray locations: receiver locations (ie. :code:`np.r_[x,y,z]`)
    :param string orientation: receiver orientation 'x', 'y' or 'z'
    :param string component: real or imaginary component 'real' or 'imag'
    """

    orientation = properties.StringChoice(
        "orientation of the receiver. Must currently be 'x', 'y', 'z'",
        ["x", "y", "z"])

    component = properties.StringChoice(
        "component of the field (real or imag)",
        {
            "real": ["re", "in-phase", "in phase"],
            "imag": ["imaginary", "im", "out-of-phase", "out of phase"],
        },
    )

    projComp = deprecate_property(
        orientation,
        "projComp",
        new_name="orientation",
        removal_version="0.16.0",
        future_warn=True,
    )

    def __init__(self, locations, orientation=None, component=None, **kwargs):
        proj = kwargs.pop("projComp", None)
        if proj is not None:
            self.projComp = proj
        else:
            self.orientation = orientation

        self.component = component

        super(BaseRx, self).__init__(locations, **kwargs)

    def projGLoc(self, f):
        """Grid Location projection (e.g. Ex Fy ...)"""
        return f._GLoc(self.projField) + self.orientation

    def eval(self, src, mesh, f):
        """
        Project fields to receivers to get data.

        :param SimPEG.electromagnetics.frequency_domain.sources.BaseFDEMSrc src: FDEM source
        :param discretize.base.BaseMesh mesh: mesh used
        :param Fields f: fields object
        :rtype: numpy.ndarray
        :return: fields projected to recievers
        """

        P = self.getP(mesh, self.projGLoc(f))
        f_part_complex = f[src, self.projField]
        f_part = getattr(f_part_complex,
                         self.component)  # real or imag component

        return P * f_part

    def evalDeriv(self, src, mesh, f, du_dm_v=None, v=None, adjoint=False):
        """
        Derivative of projected fields with respect to the inversion model times a vector.

        :param SimPEG.electromagnetics.frequency_domain.sources.BaseFDEMSrc src: FDEM source
        :param discretize.base.BaseMesh mesh: mesh used
        :param Fields f: fields object
        :param numpy.ndarray v: vector to multiply
        :rtype: numpy.ndarray
        :return: fields projected to recievers
        """

        df_dmFun = getattr(f, "_{0}Deriv".format(self.projField), None)

        assert v is not None, "v must be provided to compute the deriv or adjoint"

        P = self.getP(mesh, self.projGLoc(f))

        if not adjoint:
            assert (
                du_dm_v is not None
            ), "du_dm_v must be provided to evaluate the receiver deriv"
            df_dm_v = df_dmFun(src, du_dm_v, v, adjoint=False)
            Pv_complex = P * df_dm_v
            Pv = getattr(Pv_complex, self.component)

            return Pv

        elif adjoint:
            PTv_real = P.T * v

            if self.component == "imag":
                PTv = 1j * PTv_real
            elif self.component == "real":
                PTv = PTv_real.astype(complex)
            else:
                raise NotImplementedError("must be real or imag")

            df_duT, df_dmT = df_dmFun(src, None, PTv, adjoint=True)
            if self.component == "imag":  # conjugate
                df_duT *= -1
                df_dmT *= -1

            return df_duT, df_dmT
Exemple #4
0
class Simulation3DIntegral(BasePFSimulation):
    """
    magnetic simulation in integral form.

    """

    chi, chiMap, chiDeriv = props.Invertible(
        "Magnetic Susceptibility (SI)", default=1.0
    )

    modelType = properties.StringChoice(
        "Type of magnetization model",
        choices=["susceptibility", "vector"],
        default="susceptibility",
    )

    is_amplitude_data = properties.Boolean(
        "Whether the supplied data is amplitude data", default=False
    )

    def __init__(self, mesh, **kwargs):
        super().__init__(mesh, **kwargs)
        self._G = None
        self._M = None
        self._gtg_diagonal = None
        self.modelMap = self.chiMap
        setKwargs(self, **kwargs)

    @property
    def M(self):
        """
        M: ndarray
            Magnetization matrix
        """
        if getattr(self, "_M", None) is None:

            if self.modelType == "vector":
                self._M = sp.identity(self.nC) * self.survey.source_field.parameters[0]

            else:
                mag = mat_utils.dip_azimuth2cartesian(
                    np.ones(self.nC) * self.survey.source_field.parameters[1],
                    np.ones(self.nC) * self.survey.source_field.parameters[2],
                )

                self._M = sp.vstack(
                    (
                        sdiag(mag[:, 0] * self.survey.source_field.parameters[0]),
                        sdiag(mag[:, 1] * self.survey.source_field.parameters[0]),
                        sdiag(mag[:, 2] * self.survey.source_field.parameters[0]),
                    )
                )

        return self._M

    @M.setter
    def M(self, M):
        """
        Create magnetization matrix from unit vector orientation
        :parameter
        M: array (3*nC,) or (nC, 3)
        """
        if self.modelType == "vector":
            self._M = sdiag(mkvc(M) * self.survey.source_field.parameters[0])
        else:
            M = M.reshape((-1, 3))
            self._M = sp.vstack(
                (
                    sdiag(M[:, 0] * self.survey.source_field.parameters[0]),
                    sdiag(M[:, 1] * self.survey.source_field.parameters[0]),
                    sdiag(M[:, 2] * self.survey.source_field.parameters[0]),
                )
            )

    def fields(self, model):

        model = self.chiMap * model

        if self.store_sensitivities == "forward_only":
            self.model = model
            fields = mkvc(self.linear_operator())
        else:
            fields = np.asarray(self.G @ model.astype(np.float32))

        if self.is_amplitude_data:
            fields = self.compute_amplitude(fields)

        return fields

    @property
    def G(self):

        if getattr(self, "_G", None) is None:

            self._G = self.linear_operator()

        return self._G

    @property
    def nD(self):
        """
            Number of data
        """
        self._nD = self.survey.receiver_locations.shape[0]

        return self._nD

    @property
    def tmi_projection(self):

        if getattr(self, "_tmi_projection", None) is None:

            # Convert from north to cartesian
            self._tmi_projection = mat_utils.dip_azimuth2cartesian(
                self.survey.source_field.parameters[1],
                self.survey.source_field.parameters[2],
            )

        return self._tmi_projection

    def getJtJdiag(self, m, W=None):
        """
            Return the diagonal of JtJ
        """
        self.model = m

        if W is None:
            W = np.ones(self.nD)
        else:
            W = W.diagonal() ** 2
        if getattr(self, "_gtg_diagonal", None) is None:
            diag = np.zeros(self.G.shape[1])
            if not self.is_amplitude_data:
                for i in range(len(W)):
                    diag += W[i] * (self.G[i] * self.G[i])
            else:
                fieldDeriv = self.fieldDeriv
                Gx = self.G[::3]
                Gy = self.G[1::3]
                Gz = self.G[2::3]
                for i in range(len(W)):
                    row = (
                        fieldDeriv[0, i] * Gx[i]
                        + fieldDeriv[1, i] * Gy[i]
                        + fieldDeriv[2, i] * Gz[i]
                    )
                    diag += W[i] * (row * row)
            self._gtg_diagonal = diag
        else:
            diag = self._gtg_diagonal
        return mkvc((sdiag(np.sqrt(diag)) @ self.chiDeriv).power(2).sum(axis=0))

    def Jvec(self, m, v, f=None):
        if self.chi is None:
            self.model = np.zeros(self.G.shape[1])
        dmu_dm_v = self.chiDeriv @ v

        Jvec = self.G @ dmu_dm_v.astype(np.float32)

        if self.is_amplitude_data:
            Jvec = Jvec.reshape((-1, 3)).T
            fieldDeriv_Jvec = self.fieldDeriv * Jvec
            return fieldDeriv_Jvec[0] + fieldDeriv_Jvec[1] + fieldDeriv_Jvec[2]
        else:
            return Jvec

    def Jtvec(self, m, v, f=None):
        if self.chi is None:
            self.model = np.zeros(self.G.shape[1])

        if self.is_amplitude_data:
            v = (self.fieldDeriv * v).T.reshape(-1)
        Jtvec = self.G.T @ v.astype(np.float32)
        return np.asarray(self.chiDeriv.T @ Jtvec)

    @property
    def fieldDeriv(self):

        if self.chi is None:
            self.model = np.zeros(self.G.shape[1])

        if getattr(self, "_fieldDeriv", None) is None:
            fields = np.asarray(self.G.dot((self.chiMap @ self.chi).astype(np.float32)))
            b_xyz = self.normalized_fields(fields)

            self._fieldDeriv = b_xyz

        return self._fieldDeriv

    @classmethod
    def normalized_fields(cls, fields):
        """
            Return the normalized B fields
        """

        # Get field amplitude
        amp = cls.compute_amplitude(fields.astype(np.float64))

        return fields.reshape((3, -1), order="F") / amp[None, :]

    @classmethod
    def compute_amplitude(cls, b_xyz):
        """
            Compute amplitude of the magnetic field
        """

        amplitude = np.linalg.norm(b_xyz.reshape((3, -1), order="F"), axis=0)

        return amplitude

    def evaluate_integral(self, receiver_location, components):
        """
            Load in the active nodes of a tensor mesh and computes the magnetic
            forward relation between a cuboid and a given observation
            location outside the Earth [obsx, obsy, obsz]

            INPUT:
            receiver_location:  [obsx, obsy, obsz] nC x 3 Array

            components: list[str]
                List of magnetic components chosen from:
                'bx', 'by', 'bz', 'bxx', 'bxy', 'bxz', 'byy', 'byz', 'bzz'

            OUTPUT:
            Tx = [Txx Txy Txz]
            Ty = [Tyx Tyy Tyz]
            Tz = [Tzx Tzy Tzz]
        """
        # TODO: This should probably be converted to C
        eps = 1e-8  # add a small value to the locations to avoid /0

        rows = {component: np.zeros(3 * self.Xn.shape[0]) for component in components}

        # number of cells in mesh
        nC = self.Xn.shape[0]

        # comp. pos. differences for tne, bsw nodes
        dz2 = self.Zn[:, 1] - receiver_location[2] + eps
        dz1 = self.Zn[:, 0] - receiver_location[2] + eps

        dy2 = self.Yn[:, 1] - receiver_location[1] + eps
        dy1 = self.Yn[:, 0] - receiver_location[1] + eps

        dx2 = self.Xn[:, 1] - receiver_location[0] + eps
        dx1 = self.Xn[:, 0] - receiver_location[0] + eps

        # comp. squared diff
        dx2dx2 = dx2 ** 2.0
        dx1dx1 = dx1 ** 2.0

        dy2dy2 = dy2 ** 2.0
        dy1dy1 = dy1 ** 2.0

        dz2dz2 = dz2 ** 2.0
        dz1dz1 = dz1 ** 2.0

        # 2D radius component squared of corner nodes
        R1 = dy2dy2 + dx2dx2
        R2 = dy2dy2 + dx1dx1
        R3 = dy1dy1 + dx2dx2
        R4 = dy1dy1 + dx1dx1

        # radius to each cell node
        r1 = np.sqrt(dz2dz2 + R2) + eps
        r2 = np.sqrt(dz2dz2 + R1) + eps
        r3 = np.sqrt(dz1dz1 + R1) + eps
        r4 = np.sqrt(dz1dz1 + R2) + eps
        r5 = np.sqrt(dz2dz2 + R3) + eps
        r6 = np.sqrt(dz2dz2 + R4) + eps
        r7 = np.sqrt(dz1dz1 + R4) + eps
        r8 = np.sqrt(dz1dz1 + R3) + eps

        # compactify argument calculations
        arg1_ = dx1 + dy2 + r1
        arg1 = dy2 + dz2 + r1
        arg2 = dx1 + dz2 + r1
        arg3 = dx1 + r1
        arg4 = dy2 + r1
        arg5 = dz2 + r1

        arg6_ = dx2 + dy2 + r2
        arg6 = dy2 + dz2 + r2
        arg7 = dx2 + dz2 + r2
        arg8 = dx2 + r2
        arg9 = dy2 + r2
        arg10 = dz2 + r2

        arg11_ = dx2 + dy2 + r3
        arg11 = dy2 + dz1 + r3
        arg12 = dx2 + dz1 + r3
        arg13 = dx2 + r3
        arg14 = dy2 + r3
        arg15 = dz1 + r3

        arg16_ = dx1 + dy2 + r4
        arg16 = dy2 + dz1 + r4
        arg17 = dx1 + dz1 + r4
        arg18 = dx1 + r4
        arg19 = dy2 + r4
        arg20 = dz1 + r4

        arg21_ = dx2 + dy1 + r5
        arg21 = dy1 + dz2 + r5
        arg22 = dx2 + dz2 + r5
        arg23 = dx2 + r5
        arg24 = dy1 + r5
        arg25 = dz2 + r5

        arg26_ = dx1 + dy1 + r6
        arg26 = dy1 + dz2 + r6
        arg27 = dx1 + dz2 + r6
        arg28 = dx1 + r6
        arg29 = dy1 + r6
        arg30 = dz2 + r6

        arg31_ = dx1 + dy1 + r7
        arg31 = dy1 + dz1 + r7
        arg32 = dx1 + dz1 + r7
        arg33 = dx1 + r7
        arg34 = dy1 + r7
        arg35 = dz1 + r7

        arg36_ = dx2 + dy1 + r8
        arg36 = dy1 + dz1 + r8
        arg37 = dx2 + dz1 + r8
        arg38 = dx2 + r8
        arg39 = dy1 + r8
        arg40 = dz1 + r8

        if ("bxx" in components) or ("bzz" in components):
            rows["bxx"] = np.zeros((1, 3 * nC))

            rows["bxx"][0, 0:nC] = 2 * (
                ((dx1 ** 2 - r1 * arg1) / (r1 * arg1 ** 2 + dx1 ** 2 * r1 + eps))
                - ((dx2 ** 2 - r2 * arg6) / (r2 * arg6 ** 2 + dx2 ** 2 * r2 + eps))
                + ((dx2 ** 2 - r3 * arg11) / (r3 * arg11 ** 2 + dx2 ** 2 * r3 + eps))
                - ((dx1 ** 2 - r4 * arg16) / (r4 * arg16 ** 2 + dx1 ** 2 * r4 + eps))
                + ((dx2 ** 2 - r5 * arg21) / (r5 * arg21 ** 2 + dx2 ** 2 * r5 + eps))
                - ((dx1 ** 2 - r6 * arg26) / (r6 * arg26 ** 2 + dx1 ** 2 * r6 + eps))
                + ((dx1 ** 2 - r7 * arg31) / (r7 * arg31 ** 2 + dx1 ** 2 * r7 + eps))
                - ((dx2 ** 2 - r8 * arg36) / (r8 * arg36 ** 2 + dx2 ** 2 * r8 + eps))
            )

            rows["bxx"][0, nC : 2 * nC] = (
                dx2 / (r5 * arg25 + eps)
                - dx2 / (r2 * arg10 + eps)
                + dx2 / (r3 * arg15 + eps)
                - dx2 / (r8 * arg40 + eps)
                + dx1 / (r1 * arg5 + eps)
                - dx1 / (r6 * arg30 + eps)
                + dx1 / (r7 * arg35 + eps)
                - dx1 / (r4 * arg20 + eps)
            )

            rows["bxx"][0, 2 * nC :] = (
                dx1 / (r1 * arg4 + eps)
                - dx2 / (r2 * arg9 + eps)
                + dx2 / (r3 * arg14 + eps)
                - dx1 / (r4 * arg19 + eps)
                + dx2 / (r5 * arg24 + eps)
                - dx1 / (r6 * arg29 + eps)
                + dx1 / (r7 * arg34 + eps)
                - dx2 / (r8 * arg39 + eps)
            )

            rows["bxx"] /= 4 * np.pi
            rows["bxx"] *= self.M

        if ("byy" in components) or ("bzz" in components):

            rows["byy"] = np.zeros((1, 3 * nC))

            rows["byy"][0, 0:nC] = (
                dy2 / (r3 * arg15 + eps)
                - dy2 / (r2 * arg10 + eps)
                + dy1 / (r5 * arg25 + eps)
                - dy1 / (r8 * arg40 + eps)
                + dy2 / (r1 * arg5 + eps)
                - dy2 / (r4 * arg20 + eps)
                + dy1 / (r7 * arg35 + eps)
                - dy1 / (r6 * arg30 + eps)
            )
            rows["byy"][0, nC : 2 * nC] = 2 * (
                ((dy2 ** 2 - r1 * arg2) / (r1 * arg2 ** 2 + dy2 ** 2 * r1 + eps))
                - ((dy2 ** 2 - r2 * arg7) / (r2 * arg7 ** 2 + dy2 ** 2 * r2 + eps))
                + ((dy2 ** 2 - r3 * arg12) / (r3 * arg12 ** 2 + dy2 ** 2 * r3 + eps))
                - ((dy2 ** 2 - r4 * arg17) / (r4 * arg17 ** 2 + dy2 ** 2 * r4 + eps))
                + ((dy1 ** 2 - r5 * arg22) / (r5 * arg22 ** 2 + dy1 ** 2 * r5 + eps))
                - ((dy1 ** 2 - r6 * arg27) / (r6 * arg27 ** 2 + dy1 ** 2 * r6 + eps))
                + ((dy1 ** 2 - r7 * arg32) / (r7 * arg32 ** 2 + dy1 ** 2 * r7 + eps))
                - ((dy1 ** 2 - r8 * arg37) / (r8 * arg37 ** 2 + dy1 ** 2 * r8 + eps))
            )
            rows["byy"][0, 2 * nC :] = (
                dy2 / (r1 * arg3 + eps)
                - dy2 / (r2 * arg8 + eps)
                + dy2 / (r3 * arg13 + eps)
                - dy2 / (r4 * arg18 + eps)
                + dy1 / (r5 * arg23 + eps)
                - dy1 / (r6 * arg28 + eps)
                + dy1 / (r7 * arg33 + eps)
                - dy1 / (r8 * arg38 + eps)
            )

            rows["byy"] /= 4 * np.pi
            rows["byy"] *= self.M

        if "bzz" in components:

            rows["bzz"] = -rows["bxx"] - rows["byy"]

        if "bxy" in components:
            rows["bxy"] = np.zeros((1, 3 * nC))

            rows["bxy"][0, 0:nC] = 2 * (
                ((dx1 * arg4) / (r1 * arg1 ** 2 + (dx1 ** 2) * r1 + eps))
                - ((dx2 * arg9) / (r2 * arg6 ** 2 + (dx2 ** 2) * r2 + eps))
                + ((dx2 * arg14) / (r3 * arg11 ** 2 + (dx2 ** 2) * r3 + eps))
                - ((dx1 * arg19) / (r4 * arg16 ** 2 + (dx1 ** 2) * r4 + eps))
                + ((dx2 * arg24) / (r5 * arg21 ** 2 + (dx2 ** 2) * r5 + eps))
                - ((dx1 * arg29) / (r6 * arg26 ** 2 + (dx1 ** 2) * r6 + eps))
                + ((dx1 * arg34) / (r7 * arg31 ** 2 + (dx1 ** 2) * r7 + eps))
                - ((dx2 * arg39) / (r8 * arg36 ** 2 + (dx2 ** 2) * r8 + eps))
            )
            rows["bxy"][0, nC : 2 * nC] = (
                dy2 / (r1 * arg5 + eps)
                - dy2 / (r2 * arg10 + eps)
                + dy2 / (r3 * arg15 + eps)
                - dy2 / (r4 * arg20 + eps)
                + dy1 / (r5 * arg25 + eps)
                - dy1 / (r6 * arg30 + eps)
                + dy1 / (r7 * arg35 + eps)
                - dy1 / (r8 * arg40 + eps)
            )
            rows["bxy"][0, 2 * nC :] = (
                1 / r1 - 1 / r2 + 1 / r3 - 1 / r4 + 1 / r5 - 1 / r6 + 1 / r7 - 1 / r8
            )

            rows["bxy"] /= 4 * np.pi

            rows["bxy"] *= self.M

        if "bxz" in components:
            rows["bxz"] = np.zeros((1, 3 * nC))

            rows["bxz"][0, 0:nC] = 2 * (
                ((dx1 * arg5) / (r1 * (arg1 ** 2) + (dx1 ** 2) * r1 + eps))
                - ((dx2 * arg10) / (r2 * (arg6 ** 2) + (dx2 ** 2) * r2 + eps))
                + ((dx2 * arg15) / (r3 * (arg11 ** 2) + (dx2 ** 2) * r3 + eps))
                - ((dx1 * arg20) / (r4 * (arg16 ** 2) + (dx1 ** 2) * r4 + eps))
                + ((dx2 * arg25) / (r5 * (arg21 ** 2) + (dx2 ** 2) * r5 + eps))
                - ((dx1 * arg30) / (r6 * (arg26 ** 2) + (dx1 ** 2) * r6 + eps))
                + ((dx1 * arg35) / (r7 * (arg31 ** 2) + (dx1 ** 2) * r7 + eps))
                - ((dx2 * arg40) / (r8 * (arg36 ** 2) + (dx2 ** 2) * r8 + eps))
            )
            rows["bxz"][0, nC : 2 * nC] = (
                1 / r1 - 1 / r2 + 1 / r3 - 1 / r4 + 1 / r5 - 1 / r6 + 1 / r7 - 1 / r8
            )
            rows["bxz"][0, 2 * nC :] = (
                dz2 / (r1 * arg4 + eps)
                - dz2 / (r2 * arg9 + eps)
                + dz1 / (r3 * arg14 + eps)
                - dz1 / (r4 * arg19 + eps)
                + dz2 / (r5 * arg24 + eps)
                - dz2 / (r6 * arg29 + eps)
                + dz1 / (r7 * arg34 + eps)
                - dz1 / (r8 * arg39 + eps)
            )

            rows["bxz"] /= 4 * np.pi

            rows["bxz"] *= self.M

        if "byz" in components:
            rows["byz"] = np.zeros((1, 3 * nC))

            rows["byz"][0, 0:nC] = (
                1 / r3 - 1 / r2 + 1 / r5 - 1 / r8 + 1 / r1 - 1 / r4 + 1 / r7 - 1 / r6
            )
            rows["byz"][0, nC : 2 * nC] = 2 * (
                (((dy2 * arg5) / (r1 * (arg2 ** 2) + (dy2 ** 2) * r1 + eps)))
                - (((dy2 * arg10) / (r2 * (arg7 ** 2) + (dy2 ** 2) * r2 + eps)))
                + (((dy2 * arg15) / (r3 * (arg12 ** 2) + (dy2 ** 2) * r3 + eps)))
                - (((dy2 * arg20) / (r4 * (arg17 ** 2) + (dy2 ** 2) * r4 + eps)))
                + (((dy1 * arg25) / (r5 * (arg22 ** 2) + (dy1 ** 2) * r5 + eps)))
                - (((dy1 * arg30) / (r6 * (arg27 ** 2) + (dy1 ** 2) * r6 + eps)))
                + (((dy1 * arg35) / (r7 * (arg32 ** 2) + (dy1 ** 2) * r7 + eps)))
                - (((dy1 * arg40) / (r8 * (arg37 ** 2) + (dy1 ** 2) * r8 + eps)))
            )
            rows["byz"][0, 2 * nC :] = (
                dz2 / (r1 * arg3 + eps)
                - dz2 / (r2 * arg8 + eps)
                + dz1 / (r3 * arg13 + eps)
                - dz1 / (r4 * arg18 + eps)
                + dz2 / (r5 * arg23 + eps)
                - dz2 / (r6 * arg28 + eps)
                + dz1 / (r7 * arg33 + eps)
                - dz1 / (r8 * arg38 + eps)
            )

            rows["byz"] /= 4 * np.pi

            rows["byz"] *= self.M

        if ("bx" in components) or ("tmi" in components):
            rows["bx"] = np.zeros((1, 3 * nC))

            rows["bx"][0, 0:nC] = (
                (-2 * np.arctan2(dx1, arg1 + eps))
                - (-2 * np.arctan2(dx2, arg6 + eps))
                + (-2 * np.arctan2(dx2, arg11 + eps))
                - (-2 * np.arctan2(dx1, arg16 + eps))
                + (-2 * np.arctan2(dx2, arg21 + eps))
                - (-2 * np.arctan2(dx1, arg26 + eps))
                + (-2 * np.arctan2(dx1, arg31 + eps))
                - (-2 * np.arctan2(dx2, arg36 + eps))
            )
            rows["bx"][0, nC : 2 * nC] = (
                np.log(arg5)
                - np.log(arg10)
                + np.log(arg15)
                - np.log(arg20)
                + np.log(arg25)
                - np.log(arg30)
                + np.log(arg35)
                - np.log(arg40)
            )
            rows["bx"][0, 2 * nC :] = (
                (np.log(arg4) - np.log(arg9))
                + (np.log(arg14) - np.log(arg19))
                + (np.log(arg24) - np.log(arg29))
                + (np.log(arg34) - np.log(arg39))
            )
            rows["bx"] /= -4 * np.pi

            rows["bx"] *= self.M

        if ("by" in components) or ("tmi" in components):
            rows["by"] = np.zeros((1, 3 * nC))

            rows["by"][0, 0:nC] = (
                np.log(arg5)
                - np.log(arg10)
                + np.log(arg15)
                - np.log(arg20)
                + np.log(arg25)
                - np.log(arg30)
                + np.log(arg35)
                - np.log(arg40)
            )
            rows["by"][0, nC : 2 * nC] = (
                (-2 * np.arctan2(dy2, arg2 + eps))
                - (-2 * np.arctan2(dy2, arg7 + eps))
                + (-2 * np.arctan2(dy2, arg12 + eps))
                - (-2 * np.arctan2(dy2, arg17 + eps))
                + (-2 * np.arctan2(dy1, arg22 + eps))
                - (-2 * np.arctan2(dy1, arg27 + eps))
                + (-2 * np.arctan2(dy1, arg32 + eps))
                - (-2 * np.arctan2(dy1, arg37 + eps))
            )
            rows["by"][0, 2 * nC :] = (
                (np.log(arg3) - np.log(arg8))
                + (np.log(arg13) - np.log(arg18))
                + (np.log(arg23) - np.log(arg28))
                + (np.log(arg33) - np.log(arg38))
            )

            rows["by"] /= -4 * np.pi

            rows["by"] *= self.M

        if ("bz" in components) or ("tmi" in components):
            rows["bz"] = np.zeros((1, 3 * nC))

            rows["bz"][0, 0:nC] = (
                np.log(arg4)
                - np.log(arg9)
                + np.log(arg14)
                - np.log(arg19)
                + np.log(arg24)
                - np.log(arg29)
                + np.log(arg34)
                - np.log(arg39)
            )
            rows["bz"][0, nC : 2 * nC] = (
                (np.log(arg3) - np.log(arg8))
                + (np.log(arg13) - np.log(arg18))
                + (np.log(arg23) - np.log(arg28))
                + (np.log(arg33) - np.log(arg38))
            )
            rows["bz"][0, 2 * nC :] = (
                (-2 * np.arctan2(dz2, arg1_ + eps))
                - (-2 * np.arctan2(dz2, arg6_ + eps))
                + (-2 * np.arctan2(dz1, arg11_ + eps))
                - (-2 * np.arctan2(dz1, arg16_ + eps))
                + (-2 * np.arctan2(dz2, arg21_ + eps))
                - (-2 * np.arctan2(dz2, arg26_ + eps))
                + (-2 * np.arctan2(dz1, arg31_ + eps))
                - (-2 * np.arctan2(dz1, arg36_ + eps))
            )
            rows["bz"] /= -4 * np.pi

            rows["bz"] *= self.M

        if "tmi" in components:

            rows["tmi"] = np.dot(
                self.tmi_projection, np.r_[rows["bx"], rows["by"], rows["bz"]]
            )

        return np.vstack([rows[component] for component in components])

    @property
    def deleteTheseOnModelUpdate(self):
        deletes = super().deleteTheseOnModelUpdate
        if self.is_amplitude_data:
            deletes += ["_gtg_diagonal"]
        return deletes

    @property
    def coordinate_system(self):
        raise AttributeError(
            "The coordinate_system property has been removed. "
            "Instead make use of `SimPEG.maps.SphericalSystem`."
        )
Exemple #5
0
class IO(properties.HasProperties):
    """

    """

    # Survey
    survey_layout = properties.StringChoice(
        "Survey geometry of DC surveys",
        default="SURFACE",
        choices=["SURFACE", "BOREHOLE", "GENERAL"])

    survey_type = properties.StringChoice(
        "DC-IP Survey type",
        default="dipole-dipole",
        choices=["dipole-dipole", "pole-dipole", "dipole-pole", "pole-pole"])

    dimension = properties.Integer("Dimension of electrode locations",
                                   default=2,
                                   required=True)

    a_locations = properties.Array(
        "locations of the positive (+) current electrodes",
        required=True,
        shape=('*', '*'),  # ('*', 3) for 3D or ('*', 2) for 2D
        dtype=float  # data are floats
    )

    b_locations = properties.Array(
        "locations of the negative (-) current electrodes",
        required=True,
        shape=('*', '*'),  # ('*', 3) for 3D or ('*', 2) for 2D
        dtype=float  # data are floats
    )

    m_locations = properties.Array(
        "locations of the positive (+) potential electrodes",
        required=True,
        shape=('*', '*'),  # ('*', 3) for 3D or ('*', 2) for 2D
        dtype=float  # data are floats
    )

    n_locations = properties.Array(
        "locations of the negative (-) potential electrodes",
        required=True,
        shape=('*', '*'),  # ('*', 3) for 3D or ('*', 2) for 2D
        dtype=float  # data are floats
    )

    electrode_locations = properties.Array(
        "unique locations of a, b, m, n electrodes",
        required=True,
        shape=('*', '*'),  # ('*', 3) for 3D or ('*', 2) for 2D
        dtype=float  # data are floats
    )

    # Data
    data_dc_type = properties.StringChoice("Type of DC-IP survey",
                                           required=True,
                                           default="volt",
                                           choices=[
                                               "volt",
                                               "apparent_resistivity",
                                               "apparent_conductivity",
                                           ])

    data_dc = properties.Array(
        "Measured DC data",
        shape=('*', ),
        dtype=float  # data are floats
    )

    data_ip_type = properties.StringChoice("Type of DC-IP survey",
                                           required=True,
                                           default="volt",
                                           choices=[
                                               "volt",
                                               "apparent_chargeability",
                                           ])

    data_ip = properties.Array(
        "Measured IP data",
        shape=('*', ),
        dtype=float  # data are floats
    )

    data_sip_type = properties.StringChoice("Type of DC-IP survey",
                                            required=True,
                                            default="volt",
                                            choices=[
                                                "volt",
                                                "apparent_chargeability",
                                            ])

    data_sip = properties.Array(
        "Measured Spectral IP data",
        shape=('*', '*'),
        dtype=float  # data are floats
    )

    times_ip = properties.Array(
        "Time channels of measured Spectral IP voltages (s)",
        required=True,
        shape=('*', ),
        dtype=float  # data are floats
    )

    G = properties.Array(
        "Geometric factor of DC-IP survey",
        shape=('*', ),
        dtype=float  # data are floats
    )

    grids = properties.Array(
        "Spatial grids for plotting pseudo-section",
        shape=('*', '*'),
        dtype=float  # data are floats
    )

    space_type = properties.StringChoice(
        "Assumption to compute apparent resistivity",
        default="half-space",
        choices=["half-space", "whole-space"])

    line_inds = properties.Array(
        "Line indices",
        required=True,
        shape=('*', ),
        dtype=int  # data are floats
    )
    sort_inds = properties.Array(
        "Sorting indices from ABMN",
        required=True,
        shape=('*', ),
        dtype=int  # data are floats
    )

    # Related to Physics and Discretization
    mesh = properties.Instance("Mesh for discretization",
                               BaseMesh,
                               required=True)

    dx = properties.Float(
        "Length of corecell in x-direction",
        required=True,
    )
    dy = properties.Float("Length of corecell in y-direction", required=True)
    dy = properties.Float("Length of corecell in z-direction", required=True)

    npad_x = properties.Integer("The number of padding cells x-direction",
                                required=True,
                                default=5)

    npad_y = properties.Integer("The number of padding cells y-direction",
                                required=True,
                                default=5)

    npad_z = properties.Integer("The number of padding cells z-direction",
                                required=True,
                                default=5)

    pad_rate_x = properties.Float(
        "Expansion rate of padding cells in  x-direction",
        required=True,
        default=1.3)

    pad_rate_y = properties.Float(
        "Expansion rate of padding cells in  y-direction",
        required=True,
        default=1.3)

    pad_rate_z = properties.Float(
        "Expansion rate of padding cells in  z-direction",
        required=True,
        default=1.3)

    ncell_per_dipole = properties.Integer(
        "The number of cells between dipole electrodes",
        required=True,
        default=4)

    # For synthetic surveys
    x0 = None
    lineLength = None
    a = None
    n_spacing = None
    n_data = None

    def __init__(self, **kwargs):
        super(IO, self).__init__(**kwargs)
        warnings.warn(
            "code under construction - API might change in the future")

    # Properties
    @property
    def voltages(self):
        """
        Votages (V)
        """
        if self.data_dc_type.lower() == "volt":
            return self.data_dc
        elif self.data_dc_type.lower() == "apparent_resistivity":
            return self.data_dc * self.G
        elif self.data_dc_type.lower() == "apparent_conductivity":
            return self.apparent_conductivity / (self.data_dc * self.G)
        else:
            raise NotImplementedError()

    @property
    def apparent_resistivity(self):
        """
        Apparent Resistivity (Ohm-m)
        """
        if self.data_dc_type.lower() == "apparent_resistivity":
            return self.data_dc
        elif self.data_dc_type.lower() == "volt":
            return self.data_dc / self.G
        elif self.data_dc_type.lower() == "apparent_conductivity":
            return 1. / self.data_dc
        else:
            print(self.data_dc_type.lower())
            raise NotImplementedError()

    @property
    def apparent_conductivity(self):
        """
        Apparent Conductivity (S/m)
        """
        if self.data_dc_type.lower() == "apparent_conductivity":
            return self.data_dc
        elif self.data_dc_type.lower() == "apparent_resistivity":
            return 1. / self.data_dc
        elif self.data_dc_type.lower() == "volt":
            return 1. / self.data_dc * self.G

    # For IP
    @property
    def voltages_ip(self):
        """
        IP votages (V)
        """
        if self.data_ip_type.lower() == "volt":
            return self.data_ip
        elif self.data_ip_type.lower() == "apparent_chargeability":
            if self.voltages is None:
                raise Exception(
                    "DC voltages must be set to compute IP voltages")
            return self.data_ip * self.voltages
        else:
            raise NotImplementedError()

    # For SIP
    @property
    def voltages_sip(self):
        """
        IP votages (V)
        """
        if self.data_sip_type.lower() == "volt":
            return self.data_sip
        elif self.data_sip_type.lower() == "apparent_chargeability":
            if self.voltages is None:
                raise Exception(
                    "DC voltages must be set to compute IP voltages")
            return Utils.sdiag(self.voltages) * self.data_sip
        else:
            raise NotImplementedError()

    @property
    def apparent_chargeability(self):
        """
        Apparent Conductivity (S/m)
        """
        if self.data_ip_type.lower() == "apparent_chargeability":
            return self.data_ip
        elif self.data_ip_type.lower() == "volt":
            if self.voltages is None:
                raise Exception(
                    "DC voltages must be set to compute Apparent Chargeability"
                )
            return self.data_ip / self.voltages
        else:
            raise NotImplementedError()

    # For SIP
    @property
    def apparent_chargeability_sip(self):
        """
        Apparent Conductivity (S/m)
        """
        if self.data_sip_type.lower() == "apparent_chargeability":
            return self.data_sip
        elif self.data_sip_type.lower() == "volt":
            if self.voltages is None:
                raise Exception(
                    "DC voltages must be set to compute Apparent Chargeability"
                )
            return Utils.sdiag(1. / self.voltages) * self.data_sip
        else:
            raise NotImplementedError()

    def geometric_factor(self, survey):
        """
        Compute geometric factor, G, using locational informaition
        in survey object
        """
        geometric_factor = SimPEG.EM.Static.Utils.StaticUtils.geometric_factor
        G = geometric_factor(survey,
                             survey_type=self.survey_type,
                             space_type=self.space_type)
        return G

    def from_ambn_locations_to_survey(self,
                                      a_locations,
                                      b_locations,
                                      m_locations,
                                      n_locations,
                                      survey_type=None,
                                      data_dc=None,
                                      data_ip=None,
                                      data_sip=None,
                                      data_dc_type="volt",
                                      data_ip_type="volt",
                                      data_sip_type="volt",
                                      fname=None,
                                      dimension=2,
                                      line_inds=None,
                                      times_ip=None):
        """
        read A, B, M, N electrode location and data (V or apparent_resistivity)
        """
        self.a_locations = a_locations.copy()
        self.b_locations = b_locations.copy()
        self.m_locations = m_locations.copy()
        self.n_locations = n_locations.copy()
        self.survey_type = survey_type
        self.dimension = dimension
        self.data_dc_type = data_dc_type
        self.data_ip_type = data_ip_type
        self.data_sip_type = data_sip_type
        if times_ip is not None:
            self.times_ip = times_ip

        uniqSrc = Utils.uniqueRows(np.c_[self.a_locations, self.b_locations])
        uniqElec = SimPEG.Utils.uniqueRows(
            np.vstack((self.a_locations, self.b_locations, self.m_locations,
                       self.n_locations)))
        self.electrode_locations = uniqElec[0]
        nSrc = uniqSrc[0].shape[0]
        ndata = self.a_locations.shape[0]

        if self.survey_layout == "SURFACE":
            # 2D locations
            srcLists = []
            sort_inds = []
            for iSrc in range(nSrc):
                inds = uniqSrc[2] == iSrc
                sort_inds.append(np.arange(ndata)[inds])

                locsM = self.m_locations[inds, :]
                locsN = self.n_locations[inds, :]
                if dimension == 2:
                    if survey_type in ['dipole-dipole', 'pole-dipole']:
                        rx = Rx.Dipole_ky(locsM, locsN)
                    elif survey_type in ['dipole-pole', 'pole-pole']:
                        rx = Rx.Pole_ky(locsM)
                elif dimension == 3:
                    if survey_type in ['dipole-dipole', 'pole-dipole']:
                        rx = Rx.Dipole(locsM, locsN)
                    elif survey_type in ['dipole-pole', 'pole-pole']:
                        rx = Rx.Pole(locsM)
                else:
                    raise NotImplementedError()

                if dimension == 2:
                    locA = uniqSrc[0][iSrc, :2]
                    locB = uniqSrc[0][iSrc, 2:]
                elif dimension == 3:
                    locA = uniqSrc[0][iSrc, :3]
                    locB = uniqSrc[0][iSrc, 3:]

                if survey_type in ['dipole-dipole', 'dipole-pole']:
                    src = Src.Dipole([rx], locA, locB)
                elif survey_type in ['pole-dipole', 'pole-pole']:
                    src = Src.Pole([rx], locA)

                srcLists.append(src)

            self.sort_inds = np.hstack(sort_inds)

            if dimension == 2:
                survey = Survey_ky(srcLists)
            elif dimension == 3:
                survey = Survey(srcLists)
            else:
                raise NotImplementedError()

            self.a_locations = self.a_locations[self.sort_inds, :]
            self.b_locations = self.b_locations[self.sort_inds, :]
            self.m_locations = self.m_locations[self.sort_inds, :]
            self.n_locations = self.n_locations[self.sort_inds, :]
            self.G = self.geometric_factor(survey)

            if data_dc is not None:
                self.data_dc = data_dc[self.sort_inds]
            if data_ip is not None:
                self.data_ip = data_ip[self.sort_inds]
            if data_sip is not None:
                self.data_sip = data_sip[self.sort_inds, :]
            if line_inds is not None:
                self.line_inds = line_inds[self.sort_inds]
            # Here we ignore ... z-locations
            self.n_data = survey.nD

            midABx = (self.a_locations[:, 0] + self.b_locations[:, 0]) * 0.5
            midMNx = (self.m_locations[:, 0] + self.n_locations[:, 0]) * 0.5

            if dimension == 2:
                z = abs(midABx - midMNx) * 1. / 3.
                x = (midABx + midMNx) * 0.5
                self.grids = np.c_[x, z]

            elif dimension == 3:
                midABy = (self.a_locations[:, 1] +
                          self.b_locations[:, 1]) * 0.5
                midMNy = (self.m_locations[:, 1] +
                          self.n_locations[:, 1]) * 0.5
                z = np.sqrt((midABx - midMNx)**2 +
                            (midABy - midMNy)**2) * 1. / 3.
                x = (midABx + midMNx) * 0.5
                y = (midABy + midMNy) * 0.5
                self.grids = np.c_[x, y, z]
            else:
                raise Exception()
        else:
            raise NotImplementedError()
        return survey

    def set_mesh(self,
                 topo=None,
                 dx=None,
                 dy=None,
                 dz=None,
                 n_spacing=None,
                 corezlength=None,
                 npad_x=7,
                 npad_y=7,
                 npad_z=7,
                 pad_rate_x=1.3,
                 pad_rate_y=1.3,
                 pad_rate_z=1.3,
                 ncell_per_dipole=4,
                 mesh_type='TensorMesh',
                 dimension=2,
                 method='nearest'):
        """
        Set up a mesh for a given DC survey
        """
        if mesh_type == 'TreeMesh':
            raise NotImplementedError()

        # 2D or 3D mesh
        if dimension in [2, 3]:
            if dimension == 2:
                z_ind = 1
            else:
                z_ind = 2
            a = abs(np.diff(np.sort(self.electrode_locations[:, 0]))).min()
            lineLength = abs(self.electrode_locations[:, 0].max() -
                             self.electrode_locations[:, 0].min())
            dx_ideal = a / ncell_per_dipole
            if dx is None:
                dx = dx_ideal
                warnings.warn(
                    "dx is set to {} m (samllest electrode spacing ({}) / {})".
                    format(dx, a, ncell_per_dipole))
            if dz is None:
                dz = dx * 0.5
                warnings.warn("dz ({} m) is set to dx ({} m) / {}".format(
                    dz, dx, 2))
            x0 = self.electrode_locations[:, 0].min()
            if topo is None:
                locs = np.sort(self.electrode_locations, axis=0)
            else:
                locs = np.vstack((topo, self.electrode_locations))

            if dx > dx_ideal:
                # warnings.warn(
                #     "Input dx ({}) is greater than expected \n We recommend using {:0.1e} m cells, that is, {} cells per {0.1e} m dipole length".format(dx, dx_ideal, ncell_per_dipole, a)
                # )
                pass
            self.dx = dx
            self.dz = dz
            self.npad_x = npad_x
            self.npad_z = npad_z
            self.pad_rate_x = pad_rate_x
            self.pad_rate_z = pad_rate_z
            self.ncell_per_dipole = ncell_per_dipole
            zmax = locs[:, z_ind].max()
            zmin = locs[:, z_ind].min()

            # 3 cells each for buffer
            corexlength = lineLength + dx * 6
            if corezlength is None:
                corezlength = self.grids[:, z_ind].max()

            ncx = np.round(corexlength / dx)
            ncz = np.round(corezlength / dz)
            hx = [(dx, npad_x, -pad_rate_x), (dx, ncx),
                  (dx, npad_x, pad_rate_x)]
            hz = [(dz, npad_z, -pad_rate_z), (dz, ncz)]
            x0_mesh = -(
                (dx * pad_rate_x**(np.arange(npad_x) + 1)).sum() + dx * 3 - x0)
            z0_mesh = -((dz * pad_rate_z**
                         (np.arange(npad_z) + 1)).sum() + dz * ncz) + zmax

            # For 2D mesh
            if dimension == 2:
                h = [hx, hz]
                x0_for_mesh = [x0_mesh, z0_mesh]
                self.xyzlim = np.vstack(
                    (np.r_[x0, x0 + lineLength], np.r_[zmax - corezlength,
                                                       zmax]))
                fill_value = "extrapolate"

            # For 3D mesh
            else:
                if dy is None:
                    raise Exception("You must input dy (m)")

                self.dy = dy
                self.npad_y = npad_y
                self.pad_rate_y = pad_rate_y

                ylocs = np.unique(self.electrode_locations[:, 1])
                ymin, ymax = ylocs.min(), ylocs.max()
                # 3 cells each for buffer in y-direction
                coreylength = ymax - ymin + dy * 6
                ncy = np.round(coreylength / dy)
                hy = [(dy, npad_y, -pad_rate_y), (dy, ncy),
                      (dy, npad_y, pad_rate_y)]
                y0 = ylocs.min() - dy / 2.
                y0_mesh = -((dy * pad_rate_y**(np.arange(npad_y) + 1)).sum() +
                            dy * 3 - y0)

                h = [hx, hy, hz]
                x0_for_mesh = [x0_mesh, y0_mesh, z0_mesh]
                self.xyzlim = np.vstack(
                    (np.r_[x0, x0 + lineLength], np.r_[ymin - dy * 3,
                                                       ymax + dy * 3],
                     np.r_[zmax - corezlength, zmax]))
                fill_value = np.nan
            mesh = Mesh.TensorMesh(h, x0=x0_for_mesh)
            actind = Utils.surface2ind_topo(mesh,
                                            locs,
                                            method=method,
                                            fill_value=fill_value)
        else:
            raise NotImplementedError()

        return mesh, actind

    def plotPseudoSection(
        self,
        data_type="apparent_resistivity",
        data=None,
        dataloc=True,
        aspect_ratio=2,
        scale="log",
        cmap="viridis",
        ncontour=10,
        ax=None,
        figname=None,
        clim=None,
        label=None,
        iline=0,
    ):
        """
            Plot 2D pseudo-section for DC-IP data
        """
        matplotlib.rcParams['font.size'] = 12

        if ax is None:
            fig = plt.figure(figsize=(10, 5))
            ax = plt.subplot(111)

        if self.dimension == 2:
            inds = np.ones(self.n_data, dtype=bool)
            grids = self.grids.copy()
        elif self.dimension == 3:
            inds = self.line_inds == iline
            grids = self.grids[inds, :][:, [0, 2]]
        else:
            raise NotImplementedError()

        if data_type == "apparent_resistivity":
            if data is None:
                val = self.apparent_resistivity[inds]
            else:
                val = data.copy()[inds]
            label = "Apparent Res. ($\Omega$m)"
        elif data_type == "volt":
            if data is None:
                val = self.voltages[inds]
            else:
                val = data.copy()[inds]
            label = "Voltage (V)"
        elif data_type == "apparent_conductivity":
            if data is None:
                val = self.apparent_conductivity[inds]
            else:
                val = data.copy()[inds]
            label = "Apparent Cond. (S/m)"
        elif data_type == "apparent_chargeability":
            if data is not None:
                val = data.copy()[inds]
            else:
                val = self.apparent_chargeability.copy()[inds] * 1e3
            label = "Apparent Charg. (mV/V)"
        elif data_type == "volt_ip":
            if data is not None:
                val = data.copy()[inds]
            else:
                val = self.voltages_ip.copy()[inds] * 1e3
            label = "Secondary voltage. (mV)"
        else:
            print(data_type)
            raise NotImplementedError()
        if scale == "log":
            fmt = "10$^{%.1f}$"
        elif scale == "linear":
            fmt = "%.1e"
        else:
            raise NotImplementedError()

        out = Utils.plot2Ddata(grids,
                               val,
                               contourOpts={'cmap': cmap},
                               ax=ax,
                               dataloc=dataloc,
                               scale=scale,
                               ncontour=ncontour,
                               clim=clim)
        ax.invert_yaxis()
        ax.set_xlabel("x (m)")
        ax.set_yticklabels([])
        ax.set_ylabel("n-spacing")
        cb = plt.colorbar(out[0], fraction=0.01, format=fmt, ax=ax)
        cb.set_label(label)
        cb.set_ticks(out[0].levels)
        ax.set_aspect(aspect_ratio)
        plt.tight_layout()
        if figname is not None:
            fig.savefig(figname, dpi=200)
Exemple #6
0
class Simulation2DNodal(BaseDCSimulation2D):
    """
    2.5D nodal DC problem
    """

    _solutionType = "phiSolution"
    _formulation = "EB"  # CC potentials means J is on faces
    fieldsPair = Fields2DNodal
    fieldsPair_fwd = Fields3DNodal
    _gradT = None

    bc_type = properties.StringChoice(
        "Type of boundary condition to use for simulation. Note that Robin and Mixed "
        "are equivalent.",
        choices=["Neumann", "Robin", "Mixed"],
        default="Robin",
    )

    def __init__(self, mesh, **kwargs):
        BaseDCSimulation2D.__init__(self, mesh, **kwargs)
        self.solver_opts["is_symmetric"] = True
        self.solver_opts["is_positive_definite"] = True

    def getA(self, ky):
        """
        Make the A matrix for the cell centered DC resistivity problem
        A = D MfRhoI G
        """
        # To handle Mixed boundary condition
        self.setBC(ky=ky)

        MeSigma = self.MeSigma
        MnSigma = self.MnSigma
        Grad = self.mesh.nodalGrad
        if self._gradT is None:
            self._gradT = Grad.T.tocsr()  # cache the .tocsr()
        GradT = self._gradT
        A = GradT * MeSigma * Grad + ky**2 * MnSigma

        if self.bc_type != "Neumann":
            try:
                A = A + sdiag(self._AvgBC[ky] @ self.sigma)
            except ValueError as err:
                if len(self.sigma) != len(self.mesh):
                    raise NotImplementedError(
                        "Anisotropic conductivity is not supported for Robin boundary "
                        "conditions, please use 'Neumann'.")
                else:
                    raise err
        return A

    def getADeriv(self, ky, u, v, adjoint=False):

        Grad = self.mesh.nodalGrad

        if adjoint:
            out = self.MeSigmaDeriv(
                Grad * u.flatten(), Grad * v, adjoint=adjoint
            ) + ky**2 * self.MnSigmaDeriv(u.flatten(), v, adjoint=adjoint)
        else:
            out = Grad.T * self.MeSigmaDeriv(
                Grad * u.flatten(), v, adjoint=adjoint
            ) + ky**2 * self.MnSigmaDeriv(u.flatten(), v, adjoint=adjoint)
        if self.bc_type != "Neumann" and self.sigmaMap is not None:
            if getattr(self, "_MBC_sigma", None) is None:
                self._MBC_sigma = {}
            if ky not in self._MBC_sigma:
                self._MBC_sigma[ky] = self._AvgBC[ky] @ self.sigmaDeriv
            if not isinstance(u, Zero):
                u = u.flatten()
                if v.ndim > 1:
                    u = u[:, None]
                if not adjoint:
                    out += u * (self._MBC_sigma[ky] @ v)
                else:
                    out += self._MBC_sigma[ky].T @ (u * v)
        return out

    def getRHS(self, ky):
        """
        RHS for the DC problem
        q
        """

        RHS = self.getSourceTerm(ky)
        return RHS

    def getRHSDeriv(self, ky, src, v, adjoint=False):
        """
        Derivative of the right hand side with respect to the model
        """
        # TODO: add qDeriv for RHS depending on m
        # qDeriv = src.evalDeriv(self, ky, adjoint=adjoint)
        # return qDeriv
        return Zero()

    def setBC(self, ky=None):
        if self.bc_type == "Dirichlet":
            # do nothing
            raise ValueError(
                "Dirichlet conditions are not supported in the Nodal formulation"
            )
        elif self.bc_type == "Neumann":
            if self.verbose:
                print(
                    "Homogeneous Neumann is the natural BC for this Nodal discretization."
                )
            return
        else:
            if getattr(self, "_AvgBC", None) is None:
                self._AvgBC = {}
            if ky in self._AvgBC:
                return
            mesh = self.mesh
            # calculate alpha, beta, gamma at the boundary faces
            boundary_faces = mesh.boundary_faces
            boundary_normals = mesh.boundary_face_outward_normals
            n_bf = len(boundary_faces)

            alpha = np.zeros(n_bf)

            # assume a source point at the middle of the top of the mesh
            middle = np.median(mesh.nodes, axis=0)
            top_v = np.max(mesh.nodes[:, -1])
            source_point = np.r_[middle[:-1], top_v]

            r_vec = boundary_faces - source_point
            r = np.linalg.norm(r_vec, axis=-1)
            r_hat = r_vec / r[:, None]
            r_dot_n = np.einsum("ij,ij->i", r_hat, boundary_normals)

            # determine faces that are on the sides and bottom of the mesh...
            if mesh._meshType.lower() == "tree":
                not_top = boundary_faces[:, -1] != top_v
            else:
                # mesh faces are ordered, faces_x, faces_y, faces_z so...
                is_b = make_boundary_bool(mesh.shape_faces_y)
                is_t = np.zeros(mesh.shape_faces_y, dtype=bool, order="F")
                is_t[:, -1] = True
                is_t = is_t.reshape(-1, order="F")[is_b]
                not_top = np.zeros(boundary_faces.shape[0], dtype=bool)
                not_top[-len(is_t):] = ~is_t

            # use the exponentiall scaled modified bessel function of second kind,
            # (the division will cancel out the scaling)
            # This is more stable for large values of ky * r
            # actual ratio is k1/k0...
            alpha[not_top] = (ky * k1e(ky * r) / k0e(ky * r) *
                              r_dot_n)[not_top]

            P_bf = self.mesh.project_face_to_boundary_face

            AvgN2Fb = P_bf @ self.mesh.average_node_to_face
            AvgCC2Fb = P_bf @ self.mesh.average_cell_to_face

            AvgCC2Fb = sdiag(alpha * (P_bf @ self.mesh.face_areas)) @ AvgCC2Fb
            self._AvgBC[ky] = AvgN2Fb.T @ AvgCC2Fb
Exemple #7
0
class Point1DImpedance(BaseRx):
    """
    Natural source 1D impedance receiver class

    :param string component: real or imaginary component 'real' or 'imag'
    """

    component = properties.StringChoice(
        "component of the field (real or imag)",
        {
            "real": ["re", "in-phase", "in phase"],
            "imag": ["imaginary", "im", "out-of-phase", "out of phase"],
        },
    )

    orientation = "yx"

    def __init__(self, locs, component="real"):
        self.component = component
        BaseRx.__init__(self, locs)

    @property
    def mesh(self):
        return self._mesh

    @mesh.setter
    def mesh(self, value):
        if value is getattr(self, "_mesh", None):
            pass
        else:
            self._mesh = value

    # Utility for convienece
    def _sDiag(self, t):
        return sdiag(mkvc(t, 2))

    @property
    def src(self):
        return self._src

    @src.setter
    def src(self, value):
        self._src = value

    @property
    def f(self):
        return self._f

    @f.setter
    def f(self, value):
        self._f = value

    @property
    def Pex(self):
        if getattr(self, "_Pex", None) is None:
            self._Pex = self._mesh.getInterpolationMat(self.locations[:, -1],
                                                       "Fx")
        return self._Pex

    @property
    def Pbx(self):
        if getattr(self, "_Pbx", None) is None:
            self._Pbx = self._mesh.getInterpolationMat(self.locations[:, -1],
                                                       "Ex")
        return self._Pbx

    @property
    def _ex(self):
        return self.Pex * mkvc(self.f[self.src, "e_1d"], 2)

    @property
    def _hx(self):
        return self.Pbx * mkvc(self.f[self.src, "b_1d"], 2) / mu_0

    def _ex_u(self, v):
        return self.Pex * self.f._eDeriv_u(self.src, v)

    def _hx_u(self, v):
        return self.Pbx * self.f._bDeriv_u(self.src, v) / mu_0

    def _aex_u(self, v):
        return self.f._eDeriv_u(self.src, self.Pex.T * v, adjoint=True)

    def _ahx_u(self, v):
        return self.f._bDeriv_u(self.src, self.Pbx.T * v, adjoint=True) / mu_0

    @property
    def _Hd(self):
        return self._sDiag(1.0 / self._hx)

    def eval(self, src, mesh, f, return_complex=False):
        """
        Project the fields to natural source data.

        :param SimPEG.electromagnetics.frequency_domain.sources.BaseFDEMSrc src: NSEM source
        :param discretize.TensorMesh mesh: Mesh defining the topology of the problem
        :param SimPEG.electromagnetics.frequency_domain.fields.FieldsFDEM f: NSEM fields object of the source
        :param bool (optional) return_complex: Flag for return the complex evaluation
        :rtype: numpy.ndarray
        :return: Evaluated data for the receiver
        """
        # NOTE: Maybe set this as a property
        self.src = src
        self.mesh = mesh
        self.f = f

        rx_eval_complex = -self._Hd * self._ex
        # Return the full impedance
        if return_complex:
            return rx_eval_complex
        return getattr(rx_eval_complex, self.component)

    def evalDeriv(self, src, mesh, f, v, adjoint=False):
        """method evalDeriv

        The derivative of the projection wrt u

        :param SimPEG.electromagnetics.frequency_domain.sources.BaseFDEMSrc src: NSEM source
        :param discretize.TensorMesh mesh: Mesh defining the topology of the problem
        :param SimPEG.electromagnetics.frequency_domain.fields.FieldsFDEM f: NSEM fields object of the source
        :param numpy.ndarray v: vector of size (nU,) (adjoint=False) and size (nD,) (adjoint=True)
        :rtype: numpy.ndarray
        :return: Calculated derivative (nD,) (adjoint=False) and (nP,2) (adjoint=True) for both polarizations
        """
        self.src = src
        self.mesh = mesh
        self.f = f

        if adjoint:
            Z1d = self.eval(src, mesh, f, True)

            def aZ_N_uV(x):
                return -self._aex_u(x)

            def aZ_D_uV(x):
                return self._ahx_u(x)

            rx_deriv = aZ_N_uV(self._Hd.T * v) - aZ_D_uV(
                self._sDiag(Z1d).T * self._Hd.T * v)
            if self.component == "imag":
                rx_deriv_component = 1j * rx_deriv
            elif self.component == "real":
                rx_deriv_component = rx_deriv.astype(complex)
        else:
            Z1d = self.eval(src, mesh, f, True)
            Z_N_uV = -self._ex_u(v)
            Z_D_uV = self._hx_u(v)
            # Evaluate
            rx_deriv = self._Hd * (Z_N_uV - self._sDiag(Z1d) * Z_D_uV)
            rx_deriv_component = np.array(getattr(rx_deriv, self.component))
        return rx_deriv_component
Exemple #8
0
class BaseEM1DSurvey(Survey.BaseSurvey, properties.HasProperties):
    """
        Base EM1D Survey

    """

    frequency = properties.Array("Frequency (Hz)", dtype=float)

    rx_location = properties.Array("Receiver location (x, y, z)", dtype=float)
    src_location = properties.Array("Source location (x, y, z)", dtype=float)

    src_path = properties.Array(
        "Source path (xi, yi, zi), i=0,...N",
        dtype=float
    )

    src_type = properties.StringChoice(
        "Source type",
        default="VMD",
        choices=[
            "VMD", "CircularLoop", "piecewise_segment"
        ]
    )
    offset = properties.Array("Src-Rx offsets", dtype=float)
    rx_type = properties.StringChoice(
        "Source location",
        default="Hz",
        choices=["Hz", "ppm", "Bz", "dBzdt"]
    )
    field_type = properties.StringChoice(
        "Field type",
        default="secondary",
        choices=["total", "secondary"]
    )
    depth = properties.Array("Depth of the layers", dtype=float)
    topo = properties.Array("Topography (x, y, z)", dtype=float)
    I = properties.Float("Src loop current", default=1.)
    a = properties.Float("Src loop radius", default=1.)
    half_switch = properties.Bool("Switch for half-space", default=False)

    def __init__(self, **kwargs):
        Survey.BaseSurvey.__init__(self, **kwargs)

    @property
    def h(self):
        """
            Source height
        """
        return self.src_location[2]-self.topo[2]

    @property
    def z(self):
        """
            Receiver height
        """
        return self.rx_location[2]-self.topo[2]

    @property
    def dz(self):
        """
            Source height - Rx height
        """
        return self.z - self.h

    @property
    def n_layer(self):
        """
            Srource height
        """
        if self.half_switch is False:
            return self.depth.size
        elif self.half_switch is True:
            return int(1)

    @property
    def n_frequency(self):
        """
            # of frequency
        """

        return int(self.frequency.size)

    @property
    def src_paths_on_x(self):
        """
            # of frequency
        """
        if getattr(self, '_src_paths_on_x', None) is None:
            offset = np.unique(self.offset)
            if offset.size != 1:
                raise Exception(
                    "For the sourth paths, only single offset works!"
                )
            xy_rot, xy_obs_rot, angle = rotate_to_x_axis(
                np.flipud(xy), np.r_[offset, 0.]
            )

        return self._src_paths

    @Utils.requires('prob')
    def dpred(self, m, f=None):
        """
            Computes predicted data.
            Here we do not store predicted data
            because projection (`d = P(f)`) is cheap.
        """

        if f is None:
            f = self.prob.fields(m)
        return Utils.mkvc(self.projectFields(f))
Exemple #9
0
class EM1DSurveyTD(BaseEM1DSurvey):
    """docstring for EM1DSurveyTD"""

    time = properties.Array(
        "Time channels (s) at current off-time", dtype=float
    )

    wave_type = properties.StringChoice(
        "Source location",
        default="stepoff",
        choices=["stepoff", "general"]
    )

    moment_type = properties.StringChoice(
        "Source moment type",
        default="single",
        choices=["single", "dual"]
    )

    n_pulse = properties.Integer(
        "The number of pulses",
    )

    base_frequency = properties.Float(
        "Base frequency (Hz)"
    )

    time_input_currents = properties.Array(
        "Time for input currents", dtype=float
    )

    input_currents = properties.Array(
        "Input currents", dtype=float
    )

    use_lowpass_filter = properties.Bool(
        "Switch for low pass filter", default=False
    )

    high_cut_frequency = properties.Float(
        "High cut frequency for low pass filter (Hz)",
        default=210*1e3
    )

    # Predicted data
    _pred = None

    # ------------- For dual moment ------------- #

    time_dual_moment = properties.Array(
        "Off-time channels (s) for the dual moment", dtype=float
    )

    time_input_currents_dual_moment = properties.Array(
        "Time for input currents (dual moment)", dtype=float
    )

    input_currents_dual_moment = properties.Array(
        "Input currents (dual moment)", dtype=float
    )

    base_frequency_dual_moment = properties.Float(
        "Base frequency for the dual moment (Hz)"
    )

    def __init__(self, **kwargs):
        BaseEM1DSurvey.__init__(self, **kwargs)
        if self.time is None:
            raise Exception("time is required!")

        # Use Sin filter for frequency to time transform
        self.fftfilt = filters.key_81_CosSin_2009()
        self.set_frequency()

        if self.src_type == "VMD":
            if self.offset is None:
                raise Exception("offset is required!")

            if self.offset.size == 1:
                self.offset = self.offset * np.ones(self.n_frequency)

    @property
    def time_int(self):
        """
        Time channels (s) for interpolation"
        """
        if getattr(self, '_time_int', None) is None:

            if self.moment_type == "single":
                time = self.time
                pulse_period = self.pulse_period
                period = self.period
            # Dual moment
            else:
                time = np.unique(np.r_[self.time, self.time_dual_moment])
                pulse_period = np.maximum(
                    self.pulse_period, self.pulse_period_dual_moment
                )
                period = np.maximum(self.period, self.period_dual_moment)
            tmin = time[time>0.].min()
            if self.n_pulse == 1:
                tmax = time.max() + pulse_period
            elif self.n_pulse == 2:
                tmax = time.max() + pulse_period + period/2.
            else:
                raise NotImplementedError("n_pulse must be either 1 or 2")
            n_time = int((np.log10(tmax)-np.log10(tmin))*10+1)
            self._time_int = np.logspace(
                np.log10(tmin), np.log10(tmax), n_time
            )
            # print (tmin, tmax)

        return self._time_int

    @property
    def n_time(self):
        return int(self.time.size)

    @property
    def period(self):
        return 1./self.base_frequency

    @property
    def pulse_period(self):
        Tp = (
            self.time_input_currents.max() -
            self.time_input_currents.min()
        )
        return Tp

    # ------------- For dual moment ------------- #
    @property
    def n_time_dual_moment(self):
        return int(self.time_dual_moment.size)

    @property
    def period_dual_moment(self):
        return 1./self.base_frequency_dual_moment

    @property
    def pulse_period_dual_moment(self):
        Tp = (
            self.time_input_currents_dual_moment.max() -
            self.time_input_currents_dual_moment.min()
        )
        return Tp

    @property
    def nD(self):
        """
            # of data
        """
        if self.moment_type == "single":
            return self.n_time
        else:
            return self.n_time + self.n_time_dual_moment

    @property
    def lowpass_filter(self):
        """
            Low pass filter values
        """
        if getattr(self, '_lowpass_filter', None) is None:
            # self._lowpass_filter = butterworth_type_filter(
            #     self.frequency, self.high_cut_frequency
            # )

            self._lowpass_filter = (1+1j*(self.frequency/self.high_cut_frequency))**-1
            self._lowpass_filter *= (1+1j*(self.frequency/3e5))**-0.99
            # For actual butterworth filter

            # filter_frequency, values = butter_lowpass_filter(
            #     self.high_cut_frequency
            # )
            # lowpass_func = interp1d(
            #     filter_frequency, values, fill_value='extrapolate'
            # )
            # self._lowpass_filter = lowpass_func(self.frequency)

        return self._lowpass_filter

    def set_frequency(self, pts_per_dec=-1):
        """
        Compute Frequency reqired for frequency to time transform
        """
        if self.wave_type == "general":
            _, frequency, ft, ftarg = check_time(
                self.time_int, -1, 'dlf',
                {'pts_per_dec': pts_per_dec, 'dlf': self.fftfilt}, 0
            )
        elif self.wave_type == "stepoff":
            _, frequency, ft, ftarg = check_time(
                self.time, -1, 'dlf',
                {'pts_per_dec': pts_per_dec, 'dlf': self.fftfilt}, 0,
            )
        else:
            raise Exception("wave_type must be either general or stepoff")

        self.frequency = frequency
        self.ftarg = ftarg

    def projectFields(self, u):
        """
            Transform frequency domain responses to time domain responses
        """
        # Compute frequency domain reponses right at filter coefficient values
        # Src waveform: Step-off

        if self.use_lowpass_filter:
            factor = self.lowpass_filter.copy()
        else:
            factor = np.ones_like(self.frequency, dtype=complex)

        if self.rx_type == 'Bz':
            factor *= 1./(2j*np.pi*self.frequency)

        if self.wave_type == 'stepoff':
            # Compute EM responses
            if u.size == self.n_frequency:
                resp, _ = fourier_dlf(
                    u.flatten()*factor, self.time,
                    self.frequency, self.ftarg
                )
            # Compute EM sensitivities
            else:
                resp = np.zeros(
                    (self.n_time, self.n_layer), dtype=np.float64, order='F')
                # )
                # TODO: remove for loop
                for i in range(self.n_layer):
                    resp_i, _ = fourier_dlf(
                        u[:, i]*factor, self.time,
                        self.frequency, self.ftarg
                    )
                    resp[:, i] = resp_i

        # Evaluate piecewise linear input current waveforms
        # Using Fittermann's approach (19XX) with Gaussian Quadrature
        elif self.wave_type == 'general':
            # Compute EM responses
            if u.size == self.n_frequency:
                resp_int, _ = fourier_dlf(
                    u.flatten()*factor, self.time_int,
                    self.frequency, self.ftarg
                )
                # step_func = interp1d(
                #     self.time_int, resp_int
                # )
                step_func = iuSpline(
                    np.log10(self.time_int), resp_int
                )

                resp = piecewise_pulse_fast(
                    step_func, self.time,
                    self.time_input_currents, self.input_currents,
                    self.period, n_pulse=self.n_pulse
                )

                # Compute response for the dual moment
                if self.moment_type == "dual":
                    resp_dual_moment = piecewise_pulse_fast(
                        step_func, self.time_dual_moment,
                        self.time_input_currents_dual_moment,
                        self.input_currents_dual_moment,
                        self.period_dual_moment,
                        n_pulse=self.n_pulse
                    )
                    # concatenate dual moment response
                    # so, ordering is the first moment data
                    # then the second moment data.
                    resp = np.r_[resp, resp_dual_moment]

            # Compute EM sensitivities
            else:
                if self.moment_type == "single":
                    resp = np.zeros(
                        (self.n_time, self.n_layer),
                        dtype=np.float64, order='F'
                    )
                else:
                    # For dual moment
                    resp = np.zeros(
                        (self.n_time+self.n_time_dual_moment, self.n_layer),
                        dtype=np.float64, order='F')

                # TODO: remove for loop (?)
                for i in range(self.n_layer):
                    resp_int_i, _ = fourier_dlf(
                        u[:, i]*factor, self.time_int,
                        self.frequency, self.ftarg
                    )
                    # step_func = interp1d(
                    #     self.time_int, resp_int_i
                    # )

                    step_func = iuSpline(
                        np.log10(self.time_int), resp_int_i
                    )

                    resp_i = piecewise_pulse_fast(
                        step_func, self.time,
                        self.time_input_currents, self.input_currents,
                        self.period, n_pulse=self.n_pulse
                    )

                    if self.moment_type == "single":
                        resp[:, i] = resp_i
                    else:
                        resp_dual_moment_i = piecewise_pulse_fast(
                            step_func,
                            self.time_dual_moment,
                            self.time_input_currents_dual_moment,
                            self.input_currents_dual_moment,
                            self.period_dual_moment,
                            n_pulse=self.n_pulse
                        )
                        resp[:, i] = np.r_[resp_i, resp_dual_moment_i]
        return resp * (-2.0/np.pi) * mu_0

    @Utils.requires('prob')
    def dpred(self, m, f=None):
        """
            Computes predicted data.
            Predicted data (`_pred`) are computed and stored
            when self.prob.fields(m) is called.
        """
        if f is None:
            f = self.prob.fields(m)

        return self._pred
Exemple #10
0
class BaseRx(BaseSimPEGRx):
    """
    Base DC receiver
    """

    orientation = properties.StringChoice(
        "orientation of the receiver. Must currently be 'x', 'y', 'z'", ["x", "y", "z"]
    )

    projField = properties.StringChoice(
        "field to be projected in the calculation of the data",
        choices=["phi", "e", "j"],
        default="phi",
    )

    _geometric_factor = {}

    def __init__(self, locations=None, **kwargs):
        super(BaseRx, self).__init__(**kwargs)
        if locations is not None:
            self.locations = locations

    # @property
    # def projField(self):
    #     """Field Type projection (e.g. e b ...)"""
    #     return self.knownRxTypes[self.rxType][0]

    data_type = properties.StringChoice(
        "Type of DC-IP survey",
        required=True,
        default="volt",
        choices=["volt", "apparent_resistivity", "apparent_chargeability"],
    )

    # data_type = 'volt'

    # knownRxTypes = {
    #     'phi': ['phi', None],
    #     'ex': ['e', 'x'],
    #     'ey': ['e', 'y'],
    #     'ez': ['e', 'z'],
    #     'jx': ['j', 'x'],
    #     'jy': ['j', 'y'],
    #     'jz': ['j', 'z'],
    # }

    @property
    def geometric_factor(self):
        return self._geometric_factor

    def projGLoc(self, f):
        """Grid Location projection (e.g. Ex Fy ...)"""
        # field = self.knownRxTypes[self.rxType][0]
        # orientation = self.knownRxTypes[self.rxType][1]
        if self.orientation is not None:
            return f._GLoc(self.projField) + self.orientation
        return f._GLoc(self.projField)

    def eval(self, src, mesh, f):
        P = self.getP(mesh, self.projGLoc(f))
        proj_f = self.projField
        if proj_f == "phi":
            proj_f = "phiSolution"
        v = P * f[src, proj_f]

        if self.data_type == "apparent_resistivity":
            try:
                if mesh.dim == 2:
                    return v / self.geometric_factor[src][:, None]
                return v / self.geometric_factor[src]
            except KeyError:
                raise KeyError(
                    "Receiver geometric factor has not been set, please execute "
                    "survey.set_geometric_factor()"
                )
        return v

    def evalDeriv(self, src, mesh, f, v=None, adjoint=False):
        P = self.getP(mesh, self.projGLoc(f))

        factor = None
        if self.data_type == "apparent_resistivity":
            factor = 1.0 / self.geometric_factor[src]

        if v is None:
            if factor is not None:
                P = sdiag(factor) @ P
            if adjoint:
                return P.T
            return P

        if not adjoint:
            v = P @ v
            if factor is not None:
                v = factor * v
            return v
        elif adjoint:
            if factor is not None:
                v = factor * v
            return P.T @ v
Exemple #11
0
class EM1DSurveyFD(BaseEM1DSurvey):
    """
        Freqency-domain EM1D survey
    """
    # Nfreq = None
    switch_real_imag = properties.StringChoice(
        "Switch for real and imaginary part of the data",
        default="all",
        choices=["all", "real", "imag"]
    )

    def __init__(self, **kwargs):
        BaseEM1DSurvey.__init__(self, **kwargs)

        if self.src_type == "VMD":
            if self.offset is None:
                raise Exception("offset is required!")

            if self.offset.size == 1:
                self.offset = self.offset * np.ones(self.n_frequency)

    @property
    def nD(self):
        """
            # of data
        """

        if self.switch_real_imag == "all":
            return int(self.frequency.size * 2)
        elif (
            self.switch_real_imag == "imag" or self.switch_real_imag == "real"
        ):
            return int(self.n_frequency)

    @property
    def hz_primary(self):
        # Assumes HCP only at the moment
        if self.src_type == 'VMD':
            return -1./(4*np.pi*self.offset**3)
        elif self.src_type == 'CircularLoop':
            return self.I/(2*self.a) * np.ones_like(self.frequency)
        else:
            raise NotImplementedError()

    def projectFields(self, u):
        """
            Decompose frequency domain EM responses as real and imaginary
            components
        """

        ureal = (u.real).copy()
        uimag = (u.imag).copy()

        if self.rx_type == 'Hz':
            factor = 1.
        elif self.rx_type == 'ppm':
            factor = 1./self.hz_primary * 1e6

        if self.switch_real_imag == 'all':
            ureal = (u.real).copy()
            uimag = (u.imag).copy()
            if ureal.ndim == 1 or 0:
                resp = np.r_[ureal*factor, uimag*factor]
            elif ureal.ndim == 2:
                if np.isscalar(factor):
                    resp = np.vstack(
                            (factor*ureal, factor*uimag)
                    )
                else:
                    resp = np.vstack(
                        (Utils.sdiag(factor)*ureal, Utils.sdiag(factor)*uimag)
                    )
            else:
                raise NotImplementedError()
        elif self.switch_real_imag == 'real':
            resp = (u.real).copy()
        elif self.switch_real_imag == 'imag':
            resp = (u.imag).copy()
        else:
            raise NotImplementedError()

        return resp
class Conversation(BaseConversation):
    """This represents a full conversation result."""

    type = properties.StringChoice(
        'The type of conversation.',
        choices=['email', 'chat', 'phone', 'spam'],
        required=True,
    )
    folder_id = properties.Integer(
        'ID of the Mailbox Folder to which this conversation resides.',
        required=True,
    )
    is_draft = properties.Bool(
        'Is this a draft conversation? This property duplicates ``draft``, '
        'but both are received in API responses at the same time so neither '
        'can be considered "deprecated".', )
    draft = properties.Bool(
        'Is this a draft conversation? This property duplicates '
        '``is_draft``, but both are received in API responses at the same '
        'time so neither can be considered "deprecated".', )
    owner = properties.Instance(
        'The Help Scout user who is currently assigned to this conversation.',
        instance_class=Person,
        required=True,
    )
    mailbox = properties.Instance(
        'The mailbox to which this conversation belongs.',
        instance_class=MailboxRef,
        required=True,
    )
    customer = properties.Instance(
        'The customer who this conversation is associated with.',
        instance_class=Person,
        required=True,
    )
    created_by = properties.Instance(
        'The ``Person`` who created this conversation. The ``type`` property '
        'will specify whether it was created by a ``user`` or a ``customer``.',
        instance_class=Person,
    )
    created_by_type = properties.String(
        'The type of user that created this conversation.', )
    created_at = properties.DateTime(
        'UTC time when this conversation was created.', )
    closed_at = properties.DateTime(
        'UTC time when this conversation was closed. Null if not closed.', )
    closed_by = properties.Instance(
        'The Help Scout user who closed this conversation.',
        instance_class=Person,
    )
    source = properties.Instance(
        'Specifies the method in which this conversation was created.',
        instance_class=Source,
    )
    threads = properties.List(
        'Threads associated with the conversation.',
        prop=Thread,
    )
    cc = properties.List(
        'Emails that are CCd.',
        prop=properties.String('Email Address', ),
    )
    bcc = properties.List(
        'Emails that are BCCd.',
        prop=properties.String('Email Address', ),
    )
    tags = properties.List(
        'Tags for the conversation',
        prop=properties.String('Tag Name'),
    )
    spam = properties.Bool('If this conversation is marked as SPAM.', )
    locked = properties.Bool('If this conversation is locked from editing.')
    user_modified_at = properties.DateTime(
        'Last time that this conversation was edited by a user.', )
Exemple #13
0
class Survey(BaseEMSurvey, properties.HasProperties):
    """
    Base DC survey
    """
    rxPair = Rx.BaseRx
    srcPair = Src.BaseSrc

    # Survey
    survey_geometry = properties.StringChoice(
        "Survey geometry of DC surveys",
        default="surface",
        choices=["surface", "borehole", "general"])

    survey_type = properties.StringChoice(
        "DC-IP Survey type",
        default="dipole-dipole",
        choices=["dipole-dipole", "pole-dipole", "dipole-pole", "pole-pole"])

    a_locations = properties.Array(
        "locations of the positive (+) current electrodes",
        shape=('*', '*'),  # ('*', 3) for 3D or ('*', 2) for 2D
        dtype=float  # data are floats
    )

    b_locations = properties.Array(
        "locations of the negative (-) current electrodes",
        shape=('*', '*'),  # ('*', 3) for 3D or ('*', 2) for 2D
        dtype=float  # data are floats
    )

    m_locations = properties.Array(
        "locations of the positive (+) potential electrodes",
        shape=('*', '*'),  # ('*', 3) for 3D or ('*', 2) for 2D
        dtype=float  # data are floats
    )

    n_locations = properties.Array(
        "locations of the negative (-) potential electrodes",
        shape=('*', '*'),  # ('*', 3) for 3D or ('*', 2) for 2D
        dtype=float  # data are floats
    )

    electrode_locations = properties.Array(
        "unique locations of a, b, m, n electrodes",
        shape=('*', '*'),  # ('*', 3) for 3D or ('*', 2) for 2D
        dtype=float  # data are floats
    )

    electrodes_info = None
    topo_function = None

    def __init__(self, srcList, **kwargs):
        BaseEMSurvey.__init__(self, srcList, **kwargs)

    def set_geometric_factor(self,
                             data_type="volt",
                             survey_type='dipole-dipole',
                             space_type='half-space'):

        geometric_factor = SimPEG.EM.Static.Utils.geometric_factor(
            self, survey_type=survey_type, space_type=space_type)

        geometric_factor = SimPEG.Survey.Data(self, geometric_factor)
        for src in self.srcList:
            for rx in src.rxList:
                rx._geometric_factor = geometric_factor[src, rx]
                rx.data_type = data_type
        return geometric_factor

    def getABMN_locations(self):
        a_locations = []
        b_locations = []
        m_locations = []
        n_locations = []
        for src in self.srcList:
            for rx in src.rxList:
                nRx = rx.nD
                # Pole Source
                if isinstance(src, Src.Pole):
                    a_locations.append(
                        src.loc.reshape([1, -1]).repeat(nRx, axis=0))
                    b_locations.append(
                        src.loc.reshape([1, -1]).repeat(nRx, axis=0))
                # Dipole Source
                elif isinstance(src, Src.Dipole):
                    a_locations.append(src.loc[0].reshape([1,
                                                           -1]).repeat(nRx,
                                                                       axis=0))
                    b_locations.append(src.loc[1].reshape([1,
                                                           -1]).repeat(nRx,
                                                                       axis=0))

                # Pole RX
                if isinstance(rx, Rx.Pole) or isinstance(rx, Rx.Pole_ky):
                    m_locations.append(rx.locs)
                    n_locations.append(rx.locs)

                # Dipole RX
                elif isinstance(rx, Rx.Dipole) or isinstance(rx, Rx.Dipole_ky):
                    m_locations.append(rx.locs[0])
                    n_locations.append(rx.locs[1])

        self.a_locations = np.vstack(a_locations)
        self.b_locations = np.vstack(b_locations)
        self.m_locations = np.vstack(m_locations)
        self.n_locations = np.vstack(n_locations)

    def drapeTopo(self,
                  mesh,
                  actind,
                  option='top',
                  topography=None,
                  force=False):
        if self.a_locations is None:
            self.getABMN_locations()

        # 2D
        if mesh.dim == 2:
            if self.survey_geometry == "surface":
                if self.electrodes_info is None or force:
                    self.electrodes_info = SimPEG.Utils.uniqueRows(
                        np.hstack((
                            self.a_locations[:, 0],
                            self.b_locations[:, 0],
                            self.m_locations[:, 0],
                            self.n_locations[:, 0],
                        )).reshape([-1, 1]))
                    self.electrode_locations = SimPEG.EM.Static.Utils.drapeTopotoLoc(
                        mesh,
                        self.electrodes_info[0].flatten(),
                        actind=actind,
                        option=option)
                temp = (
                    self.electrode_locations[self.electrodes_info[2],
                                             1]).reshape(
                                                 (self.a_locations.shape[0],
                                                  4),
                                                 order="F")
                self.a_locations = np.c_[self.a_locations[:, 0], temp[:, 0]]
                self.b_locations = np.c_[self.b_locations[:, 0], temp[:, 1]]
                self.m_locations = np.c_[self.m_locations[:, 0], temp[:, 2]]
                self.n_locations = np.c_[self.n_locations[:, 0], temp[:, 3]]

                # Make interpolation function
                self.topo_function = interp1d(self.electrode_locations[:, 0],
                                              self.electrode_locations[:, 1])

                # Loop over all Src and Rx locs and Drape topo
                for src in self.srcList:
                    # Pole Src
                    if isinstance(src, Src.Pole):
                        locA = src.loc.flatten()
                        z_SrcA = self.topo_function(locA[0])
                        src.loc = np.array([locA[0], z_SrcA])
                        for rx in src.rxList:
                            # Pole Rx
                            if isinstance(rx, Rx.Pole) or isinstance(
                                    rx, Rx.Pole_ky):
                                locM = rx.locs.copy()
                                z_RxM = self.topo_function(locM[:, 0])
                                rx.locs = np.c_[locM[:, 0], z_RxM]
                            # Dipole Rx
                            elif isinstance(rx, Rx.Dipole) or isinstance(
                                    rx, Rx.Dipole_ky):
                                locM = rx.locs[0].copy()
                                locN = rx.locs[1].copy()
                                z_RxM = self.topo_function(locM[:, 0])
                                z_RxN = self.topo_function(locN[:, 0])
                                rx.locs[0] = np.c_[locM[:, 0], z_RxM]
                                rx.locs[1] = np.c_[locN[:, 0], z_RxN]
                            else:
                                raise Exception()

                    # Dipole Src
                    elif isinstance(src, Src.Dipole):
                        locA = src.loc[0].flatten()
                        locB = src.loc[1].flatten()
                        z_SrcA = self.topo_function(locA[0])
                        z_SrcB = self.topo_function(locB[0])

                        src.loc[0] = np.array([locA[0], z_SrcA])
                        src.loc[1] = np.array([locB[0], z_SrcB])

                        for rx in src.rxList:
                            # Pole Rx
                            if isinstance(rx, Rx.Pole) or isinstance(
                                    rx, Rx.Pole_ky):
                                locM = rx.locs.copy()
                                z_RxM = self.topo_function(locM[:, 0])
                                rx.locs = np.c_[locM[:, 0], z_RxM]
                            # Dipole Rx
                            elif isinstance(rx, Rx.Dipole) or isinstance(
                                    rx, Rx.Dipole_ky):
                                locM = rx.locs[0].copy()
                                locN = rx.locs[1].copy()
                                z_RxM = self.topo_function(locM[:, 0])
                                z_RxN = self.topo_function(locN[:, 0])
                                rx.locs[0] = np.c_[locM[:, 0], z_RxM]
                                rx.locs[1] = np.c_[locN[:, 0], z_RxN]
                            else:
                                raise Exception()

            elif self.survey_geometry == "borehole":
                raise Exception(
                    "Not implemented yet for borehole survey_geometry")
            else:
                raise Exception(
                    "Input valid survey survey_geometry: surface or borehole")

        if mesh.dim == 3:
            if self.survey_geometry == "surface":
                if self.electrodes_info is None or force:
                    self.electrodes_info = SimPEG.Utils.uniqueRows(
                        np.vstack((
                            self.a_locations[:, :2],
                            self.b_locations[:, :2],
                            self.m_locations[:, :2],
                            self.n_locations[:, :2],
                        )))
                self.electrode_locations = SimPEG.EM.Static.Utils.drapeTopotoLoc(
                    mesh,
                    self.electrodes_info[0],
                    actind=actind,
                    topo=topography)
                temp = (
                    self.electrode_locations[self.electrodes_info[2],
                                             1]).reshape(
                                                 (self.a_locations.shape[0],
                                                  4),
                                                 order="F")

                self.a_locations = np.c_[self.a_locations[:, :2], temp[:, 0]]
                self.b_locations = np.c_[self.b_locations[:, :2], temp[:, 1]]
                self.m_locations = np.c_[self.m_locations[:, :2], temp[:, 2]]
                self.n_locations = np.c_[self.n_locations[:, :2], temp[:, 3]]

                # Make interpolation function
                self.topo_function = NearestNDInterpolator(
                    self.electrode_locations[:, :2],
                    self.electrode_locations[:, 2])
                # Loop over all Src and Rx locs and Drape topo
                for src in self.srcList:
                    # Pole Src
                    if isinstance(src, Src.Pole):
                        locA = src.loc.reshape([1, -1])
                        z_SrcA = self.topo_function(locA[0, :2])
                        src.loc = np.r_[locA[0, :2].flatten(), z_SrcA]

                        for rx in src.rxList:
                            # Pole Rx
                            if isinstance(rx, Rx.Pole):
                                locM = rx.locs.copy()
                                z_RxM = self.topo_function(locM[:, :2])
                                rx.locs = np.c_[locM[:, 0], z_RxM]
                            # Dipole Rx
                            elif isinstance(rx, Rx.Dipole):
                                locM = rx.locs[0].copy()
                                locN = rx.locs[1].copy()
                                z_RxM = self.topo_function(locM[:, :2])
                                z_RxN = self.topo_function(locN[:, :2])
                                rx.locs[0] = np.c_[locM[:, :2], z_RxM]
                                rx.locs[1] = np.c_[locN[:, :2], z_RxN]
                            else:
                                raise Exception()

                    # Dipole Src
                    elif isinstance(src, Src.Dipole):
                        locA = src.loc[0].reshape([1, -1])
                        locB = src.loc[1].reshape([1, -1])
                        z_SrcA = self.topo_function(locA[0, :2])
                        z_SrcB = self.topo_function(locB[0, :2])
                        src.loc[0] = np.r_[locA[0, :2].flatten(), z_SrcA]
                        src.loc[1] = np.r_[locB[0, :2].flatten(), z_SrcB]

                        for rx in src.rxList:
                            # Pole Rx
                            if isinstance(rx, Rx.Pole):
                                locM = rx.locs.copy()
                                z_RxM = self.topo_function(locM[:, :2])
                                rx.locs = np.c_[locM[:, :2], z_RxM]
                            # Dipole Rx
                            elif isinstance(rx, Rx.Dipole):
                                locM = rx.locs[0].copy()
                                locN = rx.locs[1].copy()
                                z_RxM = self.topo_function(locM[:, :2])
                                z_RxN = self.topo_function(locN[:, :2])
                                rx.locs[0] = np.c_[locM[:, :2], z_RxM]
                                rx.locs[1] = np.c_[locN[:, :2], z_RxN]
                            else:
                                raise Exception()

            elif self.survey_geometry == "borehole":
                raise Exception(
                    "Not implemented yet for borehole survey_geometry")
            else:
                raise Exception(
                    "Input valid survey survey_geometry: surface or borehole")
Exemple #14
0
class BasePFSimulation(LinearSimulation):
    actInd = properties.Array("Array of active cells (ground)",
                              dtype=(bool, int),
                              default=None)

    n_cpu = properties.Integer(
        "Number of processors used for the forward simulation",
        default=int(multiprocessing.cpu_count()),
    )

    store_sensitivities = properties.StringChoice(
        "Compute and store G",
        choices=["disk", "ram", "forward_only"],
        default="ram")

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

        LinearSimulation.__init__(self, mesh, **kwargs)

        # Find non-zero cells
        if getattr(self, "actInd", None) is not None:
            if self.actInd.dtype == "bool":
                indices = np.where(self.actInd)[0]
            else:
                indices = self.actInd

        else:

            indices = np.asarray(range(self.mesh.nC))

        self.nC = len(indices)

        # Create active cell projector
        projection = csr((np.ones(self.nC), (indices, range(self.nC))),
                         shape=(self.mesh.nC, self.nC))

        # Create vectors of nodal location for the lower and upper corners
        bsw = self.mesh.gridCC - self.mesh.h_gridded / 2.0
        tne = self.mesh.gridCC + self.mesh.h_gridded / 2.0

        xn1, xn2 = bsw[:, 0], tne[:, 0]
        yn1, yn2 = bsw[:, 1], tne[:, 1]

        self.Yn = projection.T * np.c_[mkvc(yn1), mkvc(yn2)]
        self.Xn = projection.T * np.c_[mkvc(xn1), mkvc(xn2)]

        # Allows for 2D mesh where Zn is defined by user
        if self.mesh.dim > 2:
            zn1, zn2 = bsw[:, 2], tne[:, 2]
            self.Zn = projection.T * np.c_[mkvc(zn1), mkvc(zn2)]

    def linear_operator(self):

        self.nC = self.modelMap.shape[0]

        components = np.array(list(self.survey.components.keys()))
        active_components = np.hstack([
            np.c_[values] for values in self.survey.components.values()
        ]).tolist()
        nD = self.survey.nD

        if self.store_sensitivities == "disk":
            sens_name = self.sensitivity_path + "sensitivity.npy"
            if os.path.exists(sens_name):
                # do not pull array completely into ram, just need to check the size
                kernel = np.load(sens_name, mmap_mode="r")
                if kernel.shape == (nD, self.nC):
                    print(
                        f"Found sensitivity file at {sens_name} with expected shape"
                    )
                    kernel = np.asarray(kernel)
                    return kernel
        # Single threaded
        if self.store_sensitivities != "forward_only":
            kernel = np.vstack([
                self.evaluate_integral(receiver, components[component])
                for receiver, component in zip(
                    self.survey.receiver_locations.tolist(), active_components)
            ])
        else:
            kernel = np.hstack([
                self.evaluate_integral(receiver,
                                       components[component]).dot(self.model)
                for receiver, component in zip(
                    self.survey.receiver_locations.tolist(), active_components)
            ])
        if self.store_sensitivities == "disk":
            print(f"writing sensitivity to {sens_name}")
            os.makedirs(self.sensitivity_path, exist_ok=True)
            np.save(sens_name, kernel)
        return kernel

    def evaluate_integral(self):
        """
        evaluate_integral

        Compute the forward linear relationship between the model and the physics at a point.
        :param self:
        :return:
        """

        raise RuntimeError(
            f"Integral calculations must implemented by the subclass {self}.")

    @property
    def forwardOnly(self):
        """The forwardOnly property has been deprecated. Please set the store_sensitivites
        property instead. This will be removed in version 0.15.0 of SimPEG
        """
        warnings.warn(
            "The forwardOnly property has been deprecated. Please set the store_sensitivites "
            "property instead. This will be removed in version 0.15.0 of SimPEG",
            DeprecationWarning,
        )
        return self.store_sensitivities == "forward_only"

    @forwardOnly.setter
    def forwardOnly(self, other):
        warnings.warn(
            "Do not set parallelized. If interested, try out "
            "loading dask for parallelism by doing ``import SimPEG.dask``. This will "
            "be removed in version 0.15.0 of SimPEG",
            DeprecationWarning,
        )
        if self.other:
            self.store_sensitivities = "forward_only"

    @property
    def parallelized(self):
        """The parallelized property has been removed. If interested, try out
        loading dask for parallelism by doing ``import SimPEG.dask``. This will
        be removed in version 0.15.0 of SimPEG
        """
        warnings.warn(
            "parallelized has been deprecated. If interested, try out "
            "loading dask for parallelism by doing ``import SimPEG.dask``. "
            "This will be removed in version 0.15.0 of SimPEG",
            DeprecationWarning,
        )
        return False

    @parallelized.setter
    def parallelized(self, other):
        warnings.warn(
            "Do not set parallelized. If interested, try out "
            "loading dask for parallelism by doing ``import SimPEG.dask``. This will"
            "be removed in version 0.15.0 of SimPEG",
            DeprecationWarning,
        )

    @property
    def n_cpu(self):
        """The parallelized property has been removed. If interested, try out
        loading dask for parallelism by doing ``import SimPEG.dask``. This will
        be removed in version 0.15.0 of SimPEG
        """
        warnings.warn(
            "n_cpu has been deprecated. If interested, try out "
            "loading dask for parallelism by doing ``import SimPEG.dask``. "
            "This will be removed in version 0.15.0 of SimPEG",
            DeprecationWarning,
        )
        return 1

    @parallelized.setter
    def n_cpu(self, other):
        warnings.warn(
            "Do not set n_cpu. If interested, try out "
            "loading dask for parallelism by doing ``import SimPEG.dask``. This will"
            "be removed in version 0.15.0 of SimPEG",
            DeprecationWarning,
        )
Exemple #15
0
class MagneticIntegral(Problem.LinearProblem):

    chi, chiMap, chiDeriv = Props.Invertible(
        "Magnetic Susceptibility (SI)",
        default=1.
    )

    forwardOnly = False  # If false, matrix is store to memory (watch your RAM)
    actInd = None  #: Active cell indices provided
    M = None  #: Magnetization matrix provided, otherwise all induced
    rx_type = 'tmi'  #: Receiver type either "tmi" | "xyz"
    magType = 'H0'
    equiSourceLayer = False
    silent = False  # Don't display progress on screen
    W = None
    gtgdiag = None
    memory_saving_mode = False
    n_cpu = None
    parallelized = False
    coordinate_system = properties.StringChoice(
        "Type of coordinate system we are regularizing in",
        choices=['cartesian', 'spherical'],
        default='cartesian'
    )

    modelType = properties.StringChoice(
        "Type of magnetization model",
        choices=['susceptibility', 'vector', 'amplitude'],
        default='susceptibility'
    )

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

        assert mesh.dim == 3, 'Integral formulation only available for 3D mesh'
        Problem.BaseProblem.__init__(self, mesh, **kwargs)

    def fields(self, m):

        if self.coordinate_system == 'cartesian':
            m = self.chiMap*(m)
        else:
            m = self.chiMap*(matutils.spherical2cartesian(m.reshape((int(len(m)/3), 3), order='F')))

        if self.forwardOnly:
            # Compute the linear operation without forming the full dense F
            fields = self.Intrgl_Fwr_Op(m=m)

        else:

            if getattr(self, '_Mxyz', None) is not None:

                fields = np.dot(self.G, (self.Mxyz*m).astype(np.float32))

            else:
                fields = np.dot(self.G, m.astype(np.float32))

            if self.modelType == 'amplitude':

                fields = self.calcAmpData(fields.astype(np.float64))

        return fields.astype(np.float64)

    def calcAmpData(self, Bxyz):
        """
            Compute amplitude of the field
        """

        amplitude = np.sum(
            Bxyz.reshape((3, self.nD), order='F')**2., axis=0
        )**0.5

        return amplitude

    @property
    def G(self):
        if not self.ispaired:
            raise Exception('Need to pair!')

        if getattr(self, '_G', None) is None:

            if self.modelType == 'vector':
                self.magType = 'full'

            self._G = self.Intrgl_Fwr_Op(magType=self.magType,
                                         rx_type=self.rx_type)

        return self._G

    @property
    def nD(self):
        """
            Number of data
        """
        self._nD = self.survey.srcField.rxList[0].locs.shape[0]

        return self._nD

    @property
    def ProjTMI(self):
        if not self.ispaired:
            raise Exception('Need to pair!')

        if getattr(self, '_ProjTMI', None) is None:

            # Convert Bdecination from north to cartesian
            self._ProjTMI = Utils.matutils.dip_azimuth2cartesian(
                self.survey.srcField.param[1],
                self.survey.srcField.param[2]
            )

        return self._ProjTMI

    def getJtJdiag(self, m, W=None):
        """
            Return the diagonal of JtJ
        """
        dmudm = self.chiMap.deriv(m)
        self._dSdm = None
        self._dfdm = None
        self.model = m
        if (self.gtgdiag is None) and (self.modelType != 'amplitude'):

            if W is None:
                w = np.ones(self.G.shape[1])
            else:
                w = W.diagonal()

            self.gtgdiag = np.zeros(dmudm.shape[1])

            for ii in range(self.G.shape[0]):

                self.gtgdiag += (w[ii]*self.G[ii, :]*dmudm)**2.

        if self.coordinate_system == 'cartesian':
            if self.modelType == 'amplitude':
                return np.sum((W * self.dfdm * self.G * dmudm)**2., axis=0)
            else:
                return self.gtgdiag

        else:  # spherical
            if self.modelType == 'amplitude':
                return np.sum(((W * self.dfdm) * self.G * (self.dSdm * dmudm))**2., axis=0)
            else:
                Japprox = sdiag(mkvc(self.gtgdiag)**0.5*dmudm.T) * (self.dSdm * dmudm)
                return mkvc(np.sum(Japprox.power(2), axis=0))

    def getJ(self, m, f=None):
        """
            Sensitivity matrix
        """
        if self.coordinate_system == 'cartesian':
            dmudm = self.chiMap.deriv(m)
        else:  # spherical
            dmudm = self.dSdm * self.chiMap.deriv(m)

        if self.modelType == 'amplitude':
            return self.dfdm * (self.G * dmudm)
        else:
            return self.G * dmudm

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

        if self.coordinate_system == 'cartesian':
            dmudm = self.chiMap.deriv(m)
        else:
            dmudm = self.dSdm * self.chiMap.deriv(m)

        if getattr(self, '_Mxyz', None) is not None:

            vec = np.dot(self.G, (self.Mxyz*(dmudm*v)).astype(np.float32))

        else:
            vec = np.dot(self.G, (dmudm*v).astype(np.float32))

        if self.modelType == 'amplitude':
            return self.dfdm*vec.astype(np.float64)
        else:
            return vec.astype(np.float64)

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

        if self.coordinate_system == 'spherical':
            dmudm = self.dSdm * self.chiMap.deriv(m)
        else:
            dmudm = self.chiMap.deriv(m)

        if self.modelType == 'amplitude':
            if getattr(self, '_Mxyz', None) is not None:

                vec = self.Mxyz.T*np.dot(self.G.T, (self.dfdm.T*v).astype(np.float32)).astype(np.float64)

            else:
                vec = np.dot(self.G.T, (self.dfdm.T*v).astype(np.float32))

        else:

            vec = np.dot(self.G.T, v.astype(np.float32))

        return dmudm.T * vec.astype(np.float64)

    @property
    def dSdm(self):

        if getattr(self, '_dSdm', None) is None:

            if self.model is None:
                raise Exception('Requires a chi')

            nC = int(len(self.model)/3)

            m_xyz = self.chiMap * matutils.spherical2cartesian(self.model.reshape((nC, 3), order='F'))

            nC = int(m_xyz.shape[0]/3.)
            m_atp = matutils.cartesian2spherical(m_xyz.reshape((nC, 3), order='F'))

            a = m_atp[:nC]
            t = m_atp[nC:2*nC]
            p = m_atp[2*nC:]

            Sx = sp.hstack([sp.diags(np.cos(t)*np.cos(p), 0),
                            sp.diags(-a*np.sin(t)*np.cos(p), 0),
                            sp.diags(-a*np.cos(t)*np.sin(p), 0)])

            Sy = sp.hstack([sp.diags(np.cos(t)*np.sin(p), 0),
                            sp.diags(-a*np.sin(t)*np.sin(p), 0),
                            sp.diags(a*np.cos(t)*np.cos(p), 0)])

            Sz = sp.hstack([sp.diags(np.sin(t), 0),
                            sp.diags(a*np.cos(t), 0),
                            sp.csr_matrix((nC, nC))])

            self._dSdm = sp.vstack([Sx, Sy, Sz])

        return self._dSdm

    @property
    def modelMap(self):
        """
            Call for general mapping of the problem
        """
        return self.chiMap

    @property
    def dfdm(self):

        if self.model is None:
            self.model = np.zeros(self.G.shape[1])

        if getattr(self, '_dfdm', None) is None:

            Bxyz = self.Bxyz_a(self.chiMap * self.model)

            # Bx = sp.spdiags(Bxyz[:, 0], 0, self.nD, self.nD)
            # By = sp.spdiags(Bxyz[:, 1], 0, self.nD, self.nD)
            # Bz = sp.spdiags(Bxyz[:, 2], 0, self.nD, self.nD)
            ii = np.kron(np.asarray(range(self.survey.nD), dtype='int'), np.ones(3))
            jj = np.asarray(range(3*self.survey.nD), dtype='int')
            # (data, (row, col)), shape=(3, 3))
            # P = s
            self._dfdm = sp.csr_matrix(( mkvc(Bxyz), (ii,jj)), shape=(self.survey.nD, 3*self.survey.nD))

        return self._dfdm

    def Bxyz_a(self, m):
        """
            Return the normalized B fields
        """

        # Get field data
        if self.coordinate_system == 'spherical':
            m = matutils.atp2xyz(m)

        if getattr(self, '_Mxyz', None) is not None:
            Bxyz = np.dot(self.G, (self.Mxyz*m).astype(np.float32))
        else:
            Bxyz = np.dot(self.G, m.astype(np.float32))

        amp = self.calcAmpData(Bxyz.astype(np.float64))
        Bamp = sp.spdiags(1./amp, 0, self.nD, self.nD)

        return (Bxyz.reshape((3, self.nD), order='F')*Bamp)

    def Intrgl_Fwr_Op(self, m=None, magType='H0', rx_type='tmi'):
        """

        Magnetic forward operator in integral form

        magType  = 'H0' | 'x' | 'y' | 'z'
        rx_type  = 'tmi' | 'x' | 'y' | 'z'

        Return
        _G = Linear forward operator | (forwardOnly)=data

         """
        if m is not None:
            self.model = self.chiMap*m

        # Find non-zero cells
        if getattr(self, 'actInd', None) is not None:
            if self.actInd.dtype == 'bool':
                inds = np.where(self.actInd)[0]
            else:
                inds = self.actInd

        else:

            inds = np.asarray(range(self.mesh.nC))

        nC = len(inds)

        # Create active cell projector
        P = sp.csr_matrix((np.ones(nC), (inds, range(nC))),
                          shape=(self.mesh.nC, nC))

        # Create vectors of nodal location
        # (lower and upper coners for each cell)
        if isinstance(self.mesh, Mesh.TreeMesh):
            # Get upper and lower corners of each cell
            bsw = (self.mesh.gridCC - self.mesh.h_gridded/2.)
            tne = (self.mesh.gridCC + self.mesh.h_gridded/2.)

            xn1, xn2 = bsw[:, 0], tne[:, 0]
            yn1, yn2 = bsw[:, 1], tne[:, 1]
            zn1, zn2 = bsw[:, 2], tne[:, 2]

        else:

            xn = self.mesh.vectorNx
            yn = self.mesh.vectorNy
            zn = self.mesh.vectorNz

            yn2, xn2, zn2 = np.meshgrid(yn[1:], xn[1:], zn[1:])
            yn1, xn1, zn1 = np.meshgrid(yn[:-1], xn[:-1], zn[:-1])

        # If equivalent source, use semi-infite prism
        if self.equiSourceLayer:
            zn1 -= 1000.

        self.Yn = P.T*np.c_[mkvc(yn1), mkvc(yn2)]
        self.Xn = P.T*np.c_[mkvc(xn1), mkvc(xn2)]
        self.Zn = P.T*np.c_[mkvc(zn1), mkvc(zn2)]

        # survey = self.survey
        self.rxLoc = self.survey.srcField.rxList[0].locs

        if magType == 'H0':
            if getattr(self, 'M', None) is None:
                self.M = matutils.dip_azimuth2cartesian(np.ones(nC) * self.survey.srcField.param[1],
                                      np.ones(nC) * self.survey.srcField.param[2])

            Mx = sdiag(self.M[:, 0] * self.survey.srcField.param[0])
            My = sdiag(self.M[:, 1] * self.survey.srcField.param[0])
            Mz = sdiag(self.M[:, 2] * self.survey.srcField.param[0])

            self.Mxyz = sp.vstack((Mx, My, Mz))

        elif magType == 'full':

            self.Mxyz = sp.identity(3*nC) * self.survey.srcField.param[0]

        else:
            raise Exception('magType must be: "H0" or "full"')

                # Loop through all observations and create forward operator (nD-by-nC)
        print("Begin forward: M=" + magType + ", Rx type= " + self.rx_type)

        # Switch to determine if the process has to be run in parallel
        job = Forward(
                rxLoc=self.rxLoc, Xn=self.Xn, Yn=self.Yn, Zn=self.Zn,
                n_cpu=self.n_cpu, forwardOnly=self.forwardOnly,
                model=self.model, rx_type=self.rx_type, Mxyz=self.Mxyz,
                P=self.ProjTMI, parallelized=self.parallelized
                )

        G = job.calculate()

        return G
Exemple #16
0
class DataArray(BaseData):
    """Data array with unique values at every point in the mesh

    .. note:

        DataArray custom colormap is currently unsupported on
        steno3d.com
    """
    _resource_class = 'array'
    array = properties.Array(
        doc='Data, unique values at every point in the mesh',
        shape=('*', ),
        dtype=(float, int),
        serializer=array_serializer,
        deserializer=array_download(('*', ), (float, int)),
    )

    order = properties.StringChoice(
        doc='Data array order, for data on grid meshes',
        choices={
            'c': ('C-STYLE', 'NUMPY', 'ROW-MAJOR', 'ROW'),
            'f': ('FORTRAN', 'MATLAB', 'COLUMN-MAJOR', 'COLUMN', 'COL')
        },
        default='c',
    )
    colormap = properties.List(
        doc='Colormap applied to data range or categories',
        prop=properties.Color(''),
        min_length=1,
        max_length=256,
        required=False,
    )

    def __init__(self, array=None, **kwargs):
        super(DataArray, self).__init__(**kwargs)
        if array is not None:
            self.array = array

    def _nbytes(self, arr=None):
        if arr is None or (isinstance(arr, string_types) and arr == 'array'):
            arr = self.array
        if isinstance(arr, np.ndarray):
            return arr.astype('f4').nbytes
        raise ValueError('DataArray cannot calculate the number of '
                         'bytes of {}'.format(arr))

    @properties.observer('array')
    def _reject_large_files(self, change):
        self._validate_file_size(change['name'], change['value'])

    @properties.validator
    def _validate_array(self):
        self._validate_file_size('array', self.array)
        return True

    def _get_dirty_data(self, force=False):
        datadict = super(DataArray, self)._get_dirty_data(force)
        dirty = self._dirty_props
        if 'order' in dirty or force:
            datadict['order'] = self.order
        if self.colormap and ('colormap' in dirty or force):
            datadict['colormap'] = dumps(self.colormap)
        return datadict

    def _get_dirty_files(self, force=False):
        files = super(DataArray, self)._get_dirty_files(force)
        dirty = self._dirty_props
        if 'array' in dirty or force:
            files['array'] = self._props['array'].serialize(self.array)
        return files

    @classmethod
    def _build_from_json(cls, json, **kwargs):
        data = DataArray(title=kwargs['title'],
                         description=kwargs['description'],
                         order=json['order'],
                         array=cls._props['array'].deserialize(
                             json['array'],
                             input_dtype=json.get('arrayType', None),
                         ))
        if json.get('colormap'):
            data.colormap = json['colormap']
        return data

    @classmethod
    def _build_from_omf(cls, omf_data):
        data = dict(location='N' if omf_data.location == 'vertices' else 'CC',
                    data=DataArray(title=omf_data.name,
                                   description=omf_data.description,
                                   array=omf_data.array.array))
        if omf_data.colormap:
            dlims = np.linspace(np.nanmin(omf_data.array.array),
                                np.nanmax(omf_data.array.array), 257)
            dlims = (dlims[:-1] + dlims[1:]) / 2
            omf_dlims = np.linspace(
                omf_data.colormap.limits[0],
                omf_data.colormap.limits[1],
                len(omf_data.colormap.gradient.array) + 1,
            )
            omf_dlims = (omf_dlims[:-1] + omf_dlims[1:]) / 2
            colormap = []
            for dval in dlims:
                colormap += [
                    omf_data.colormap.gradient.array[np.argmin(
                        np.abs(omf_dlims - dval))]
                ]
            data['data'].colormap = colormap
        return data

    def _to_omf(self):
        if self.order != 'c':
            raise ValueError('OMF data must be "c" order')
        import omf
        data = omf.ScalarData(
            name=self.title or '',
            description=self.description or '',
            array=omf.ScalarArray(self.array),
        )
        if self.colormap:
            data.colormap = omf.ScalarColormap(
                gradient=omf.ColorArray(self.colormap, ),
                limits=[np.min(self.array),
                        np.max(self.array)],
            )
        return data
Exemple #17
0
class Simulation2DCellCentered(BaseDCSimulation2D):
    """
    2.5D cell centered DC problem
    """

    _solutionType = "phiSolution"
    _formulation = "HJ"  # CC potentials means J is on faces
    fieldsPair = Fields2DCellCentered
    fieldsPair_fwd = Fields3DCellCentered

    bc_type = properties.StringChoice(
        "Type of boundary condition to use for simulation. Note that Robin and Mixed "
        "are equivalent.",
        choices=["Dirichlet", "Neumann", "Robin", "Mixed"],
        default="Robin",
    )

    def __init__(self, mesh, **kwargs):
        BaseDCSimulation2D.__init__(self, mesh, **kwargs)
        V = sdiag(self.mesh.cell_volumes)
        self.Div = V @ self.mesh.face_divergence
        self.Grad = self.Div.T

    def getA(self, ky):
        """
        Make the A matrix for the cell centered DC resistivity problem
        A = D MfRhoI G
        """
        # To handle Mixed boundary condition
        self.setBC(ky=ky)
        D = self.Div
        G = self.Grad
        if self.bc_type != "Dirichlet":
            G = G - self._MBC[ky]
        MfRhoI = self.MfRhoI
        # Get resistivity rho
        A = D * MfRhoI * G + ky**2 * self.MccRhoi
        if self.bc_type == "Neumann":
            A[0, 0] = A[0, 0] + 1.0
        return A

    def getADeriv(self, ky, u, v, adjoint=False):
        D = self.Div
        G = self.Grad
        if self.bc_type != "Dirichlet":
            G = G - self._MBC[ky]
        if adjoint:
            return self.MfRhoIDeriv(
                G * u.flatten(), D.T * v, adjoint=adjoint
            ) + ky**2 * self.MccRhoiDeriv(u.flatten(), v, adjoint=adjoint)
        else:
            return D * self.MfRhoIDeriv(G * u.flatten(), v, adjoint=adjoint
                                        ) + ky**2 * self.MccRhoiDeriv(
                                            u.flatten(), v, adjoint=adjoint)

    def getRHS(self, ky):
        """
        RHS for the DC problem
        q
        """

        RHS = self.getSourceTerm(ky)
        return RHS

    def getRHSDeriv(self, ky, src, v, adjoint=False):
        """
        Derivative of the right hand side with respect to the model
        """
        # TODO: add qDeriv for RHS depending on m
        # qDeriv = src.evalDeriv(self, ky, adjoint=adjoint)
        # return qDeriv
        return Zero()

    def setBC(self, ky=None):
        if self.bc_type == "Dirichlet":
            return
        if getattr(self, "_MBC", None) is None:
            self._MBC = {}
        if ky in self._MBC:
            # I have already created the BC matrix for this wavenumber
            return
        if self.bc_type == "Neumann":
            alpha, beta, gamma = 0, 1, 0
        else:
            mesh = self.mesh
            boundary_faces = mesh.boundary_faces
            boundary_normals = mesh.boundary_face_outward_normals
            n_bf = len(boundary_faces)

            # Top gets 0 Neumann
            alpha = np.zeros(n_bf)
            beta = np.ones(n_bf)
            gamma = 0

            # assume a source point at the middle of the top of the mesh
            middle = np.median(mesh.nodes, axis=0)
            top_v = np.max(mesh.nodes[:, -1])
            source_point = np.r_[middle[:-1], top_v]

            r_vec = boundary_faces - source_point
            r = np.linalg.norm(r_vec, axis=-1)
            r_hat = r_vec / r[:, None]
            r_dot_n = np.einsum("ij,ij->i", r_hat, boundary_normals)

            # determine faces that are on the sides and bottom of the mesh...
            if mesh._meshType.lower() == "tree":
                not_top = boundary_faces[:, -1] != top_v
            else:
                # mesh faces are ordered, faces_x, faces_y, faces_z so...
                is_b = make_boundary_bool(mesh.shape_faces_y)
                is_t = np.zeros(mesh.shape_faces_y, dtype=bool, order="F")
                is_t[:, -1] = True
                is_t = is_t.reshape(-1, order="F")[is_b]
                not_top = np.zeros(boundary_faces.shape[0], dtype=bool)
                not_top[-len(is_t):] = ~is_t

            # use the exponentialy scaled modified bessel function of second kind,
            # (the division will cancel out the scaling)
            # This is more stable for large values of ky * r
            # actual ratio is k1/k0...
            alpha[not_top] = (ky * k1e(ky * r) / k0e(ky * r) *
                              r_dot_n)[not_top]

        B, bc = self.mesh.cell_gradient_weak_form_robin(alpha, beta, gamma)
        # bc should always be 0 because gamma was always 0 above
        self._MBC[ky] = B
Exemple #18
0
class BaseTimeRx(BaseRx):
    """SimPEG Receiver Object for time-domain simulations"""

    times = properties.Array(
        "times where the recievers measure data", shape=("*",), required=True
    )

    projTLoc = properties.StringChoice(
        "location on the time mesh where the data are projected from",
        choices=["N", "CC"],
        default="N",
    )

    def __init__(self, locations=None, times=None, **kwargs):
        super(BaseTimeRx, self).__init__(locations=locations, **kwargs)
        if times is not None:
            self.times = times

    @property
    def nD(self):
        """Number of data in the receiver."""
        return self.locations.shape[0] * len(self.times)

    def getSpatialP(self, mesh):
        """
        Returns the spatial projection matrix.

        .. note::

            This is not stored in memory, but is created on demand.
        """
        return mesh.getInterpolationMat(self.locations, self.projGLoc)

    def getTimeP(self, timeMesh):
        """
        Returns the time projection matrix.

        .. note::

            This is not stored in memory, but is created on demand.
        """
        return timeMesh.getInterpolationMat(self.times, self.projTLoc)

    def getP(self, mesh, timeMesh):
        """
        Returns the projection matrices as a
        list for all components collected by
        the receivers.

        .. note::

            Projection matrices are stored as a dictionary (mesh, timeMesh)
            if storeProjections is True
        """
        if (mesh, timeMesh) in self._Ps:
            return self._Ps[(mesh, timeMesh)]

        Ps = self.getSpatialP(mesh)
        Pt = self.getTimeP(timeMesh)
        P = sp.kron(Pt, Ps)

        if self.storeProjections:
            self._Ps[(mesh, timeMesh)] = P

        return P
Exemple #19
0
class BaseRxNSEM_Point(BaseRx):
    """
    Natural source receiver base class.

    Assumes that the data locations are xyz coordinates.

    :param numpy.ndarray locs: receiver locations (ie. :code:`np.r_[x,y,z]`)
    :param string orientation: receiver orientation 'x', 'y' or 'z'
    :param string component: real or imaginary component 'real' or 'imag'
    """

    component = properties.StringChoice(
        "component of the field (real or imag)",
        {
            "real": ["re", "in-phase", "in phase"],
            "imag": ["imaginary", "im", "out-of-phase", "out of phase"],
        },
    )

    def __init__(self, locs, orientation=None, component=None):
        self.orientation = orientation
        self.component = component

        BaseRx.__init__(self, locs)

    # Set a mesh property - TODO: remove the following properties
    @property
    def mesh(self):
        return self._mesh

    @mesh.setter
    def mesh(self, value):
        if value is getattr(self, "_mesh", None):
            pass
        else:
            self._mesh = value

    @property
    def src(self):
        return self._src

    @src.setter
    def src(self, value):
        self._src = value

    @property
    def f(self):
        return self._f

    @f.setter
    def f(self, value):
        self._f = value

    def _locs_e(self):
        if self.locations.ndim == 3:
            loc = self.locations[:, :, 0]
        else:
            loc = self.locations
        return loc

    def _locs_b(self):
        if self.locations.ndim == 3:
            loc = self.locations[:, :, 1]
        else:
            loc = self.locations
        return loc

    # Location projection
    @property
    def Pex(self):
        if getattr(self, "_Pex", None) is None:
            self._Pex = self._mesh.getInterpolationMat(self._locs_e(), "Ex")
        return self._Pex

    @property
    def Pey(self):
        if getattr(self, "_Pey", None) is None:
            self._Pey = self._mesh.getInterpolationMat(self._locs_e(), "Ey")
        return self._Pey

    @property
    def Pbx(self):
        if getattr(self, "_Pbx", None) is None:
            self._Pbx = self._mesh.getInterpolationMat(self._locs_b(), "Fx")
        return self._Pbx

    @property
    def Pby(self):
        if getattr(self, "_Pby", None) is None:
            self._Pby = self._mesh.getInterpolationMat(self._locs_b(), "Fy")
        return self._Pby

    @property
    def Pbz(self):
        if getattr(self, "_Pbz", None) is None:
            self._Pbz = self._mesh.getInterpolationMat(self._locs_e(), "Fz")
        return self._Pbz

    # Utility for convienece
    def _sDiag(self, t):
        return sdiag(mkvc(t, 2))

    # Get the components of the fields
    # px: x-polaration and py: y-polaration.
    @property
    def _ex_px(self):
        return self.Pex * self.f[self.src, "e_px"]

    @property
    def _ey_px(self):
        return self.Pey * self.f[self.src, "e_px"]

    @property
    def _ex_py(self):
        return self.Pex * self.f[self.src, "e_py"]

    @property
    def _ey_py(self):
        return self.Pey * self.f[self.src, "e_py"]

    @property
    def _hx_px(self):
        return self.Pbx * self.f[self.src, "b_px"] / mu_0

    @property
    def _hy_px(self):
        return self.Pby * self.f[self.src, "b_px"] / mu_0

    @property
    def _hz_px(self):
        return self.Pbz * self.f[self.src, "b_px"] / mu_0

    @property
    def _hx_py(self):
        return self.Pbx * self.f[self.src, "b_py"] / mu_0

    @property
    def _hy_py(self):
        return self.Pby * self.f[self.src, "b_py"] / mu_0

    @property
    def _hz_py(self):
        return self.Pbz * self.f[self.src, "b_py"] / mu_0

    # Get the derivatives

    def _ex_px_u(self, vec):
        return self.Pex * self.f._e_pxDeriv_u(self.src, vec)

    def _ey_px_u(self, vec):
        return self.Pey * self.f._e_pxDeriv_u(self.src, vec)

    def _ex_py_u(self, vec):
        return self.Pex * self.f._e_pyDeriv_u(self.src, vec)

    def _ey_py_u(self, vec):
        return self.Pey * self.f._e_pyDeriv_u(self.src, vec)

    def _hx_px_u(self, vec):
        return self.Pbx * self.f._b_pxDeriv_u(self.src, vec) / mu_0

    def _hy_px_u(self, vec):
        return self.Pby * self.f._b_pxDeriv_u(self.src, vec) / mu_0

    def _hz_px_u(self, vec):
        return self.Pbz * self.f._b_pxDeriv_u(self.src, vec) / mu_0

    def _hx_py_u(self, vec):
        return self.Pbx * self.f._b_pyDeriv_u(self.src, vec) / mu_0

    def _hy_py_u(self, vec):
        return self.Pby * self.f._b_pyDeriv_u(self.src, vec) / mu_0

    def _hz_py_u(self, vec):
        return self.Pbz * self.f._b_pyDeriv_u(self.src, vec) / mu_0

    # Define the components of the derivative

    @property
    def _Hd(self):
        return self._sDiag(1.0 / (self._sDiag(self._hx_px) * self._hy_py -
                                  self._sDiag(self._hx_py) * self._hy_px))

    def _Hd_uV(self, v):
        return (self._sDiag(self._hy_py) * self._hx_px_u(v) +
                self._sDiag(self._hx_px) * self._hy_py_u(v) -
                self._sDiag(self._hx_py) * self._hy_px_u(v) -
                self._sDiag(self._hy_px) * self._hx_py_u(v))

    # Adjoint
    @property
    def _aex_px(self):
        return mkvc(mkvc(self.f[self.src, "e_px"], 2).T * self.Pex.T)

    @property
    def _aey_px(self):
        return mkvc(mkvc(self.f[self.src, "e_px"], 2).T * self.Pey.T)

    @property
    def _aex_py(self):
        return mkvc(mkvc(self.f[self.src, "e_py"], 2).T * self.Pex.T)

    @property
    def _aey_py(self):
        return mkvc(mkvc(self.f[self.src, "e_py"], 2).T * self.Pey.T)

    @property
    def _ahx_px(self):
        return mkvc(mkvc(self.f[self.src, "b_px"], 2).T / mu_0 * self.Pbx.T)

    @property
    def _ahy_px(self):
        return mkvc(mkvc(self.f[self.src, "b_px"], 2).T / mu_0 * self.Pby.T)

    @property
    def _ahz_px(self):
        return mkvc(mkvc(self.f[self.src, "b_px"], 2).T / mu_0 * self.Pbz.T)

    @property
    def _ahx_py(self):
        return mkvc(mkvc(self.f[self.src, "b_py"], 2).T / mu_0 * self.Pbx.T)

    @property
    def _ahy_py(self):
        return mkvc(mkvc(self.f[self.src, "b_py"], 2).T / mu_0 * self.Pby.T)

    @property
    def _ahz_py(self):
        return mkvc(mkvc(self.f[self.src, "b_py"], 2).T / mu_0 * self.Pbz.T)

    # NOTE: need to add a .T at the end for the output to be (nU,)
    def _aex_px_u(self, vec):
        """"""
        # vec is (nD,) and returns a (nU,)
        return self.f._e_pxDeriv_u(
            self.src,
            self.Pex.T * mkvc(vec, ),
            adjoint=True,
        )

    def _aey_px_u(self, vec):
        """"""
        # vec is (nD,) and returns a (nU,)
        return self.f._e_pxDeriv_u(
            self.src,
            self.Pey.T * mkvc(vec, ),
            adjoint=True,
        )

    def _aex_py_u(self, vec):
        """"""
        # vec is (nD,) and returns a (nU,)
        return self.f._e_pyDeriv_u(
            self.src,
            self.Pex.T * mkvc(vec, ),
            adjoint=True,
        )

    def _aey_py_u(self, vec):
        """"""
        # vec is (nD,) and returns a (nU,)
        return self.f._e_pyDeriv_u(
            self.src,
            self.Pey.T * mkvc(vec, ),
            adjoint=True,
        )

    def _ahx_px_u(self, vec):
        """"""
        # vec is (nD,) and returns a (nU,)
        return (self.f._b_pxDeriv_u(
            self.src,
            self.Pbx.T * mkvc(vec, ),
            adjoint=True,
        ) / mu_0)

    def _ahy_px_u(self, vec):
        """"""
        # vec is (nD,) and returns a (nU,)
        return (self.f._b_pxDeriv_u(
            self.src,
            self.Pby.T * mkvc(vec, ),
            adjoint=True,
        ) / mu_0)

    def _ahz_px_u(self, vec):
        """"""
        # vec is (nD,) and returns a (nU,)
        return (self.f._b_pxDeriv_u(
            self.src,
            self.Pbz.T * mkvc(vec, ),
            adjoint=True,
        ) / mu_0)

    def _ahx_py_u(self, vec):
        """"""
        # vec is (nD,) and returns a (nU,)
        return (self.f._b_pyDeriv_u(
            self.src,
            self.Pbx.T * mkvc(vec, ),
            adjoint=True,
        ) / mu_0)

    def _ahy_py_u(self, vec):
        """"""
        # vec is (nD,) and returns a (nU,)
        return (self.f._b_pyDeriv_u(
            self.src,
            self.Pby.T * mkvc(vec, ),
            adjoint=True,
        ) / mu_0)

    def _ahz_py_u(self, vec):
        """"""
        # vec is (nD,) and returns a (nU,)
        return (self.f._b_pyDeriv_u(
            self.src,
            self.Pbz.T * mkvc(vec, ),
            adjoint=True,
        ) / mu_0)

    # Define the components of the derivative
    @property
    def _aHd(self):
        return self._sDiag(1.0 / (self._sDiag(self._ahx_px) * self._ahy_py -
                                  self._sDiag(self._ahx_py) * self._ahy_px))

    def _aHd_uV(self, x):
        return (self._ahx_px_u(self._sDiag(self._ahy_py) * x) +
                self._ahx_px_u(self._sDiag(self._ahy_py) * x) -
                self._ahy_px_u(self._sDiag(self._ahx_py) * x) -
                self._ahx_py_u(self._sDiag(self._ahy_px) * x))

    def eval(self, src, mesh, f, return_complex=False):
        """
        Function to evaluate datum for this receiver
        """
        raise NotImplementedError(
            "SimPEG.EM.NSEM receiver has to have an eval method")

    def evalDeriv(self, src, mesh, f, v, adjoint=False):
        """
        Function to evaluate datum for this receiver
        """
        raise NotImplementedError(
            "SimPEG.EM.NSEM receiver has to have an evalDeriv method")
Exemple #20
0
class BaseRx(properties.HasProperties):
    """SimPEG Receiver Object"""

    # TODO: write a validator that checks against mesh dimension in the
    # BaseSimulation
    # TODO: location
    locations = RxLocationArray(
        "Locations of the receivers (nRx x nDim)", shape=("*", "*"), required=True
    )

    # TODO: project_grid?
    projGLoc = properties.StringChoice(
        "Projection grid location, default is CC",
        choices=["CC", "Fx", "Fy", "Fz", "Ex", "Ey", "Ez", "N"],
        default="CC",
    )

    # TODO: store_projections
    storeProjections = properties.Bool(
        "Store calls to getP (organized by mesh)", default=True
    )

    _uid = properties.Uuid("unique ID for the receiver")

    _Ps = properties.Dictionary("dictonary for storing projections",)

    def __init__(self, locations=None, **kwargs):
        super(BaseRx, self).__init__(**kwargs)
        if locations is not None:
            self.locations = locations
        rxType = kwargs.pop("rxType", None)
        if rxType is not None:
            warnings.warn(
                "BaseRx no longer has an rxType. Each rxType should instead "
                "be a different receiver class."
            )
        if getattr(self, "_Ps", None) is None:
            self._Ps = {}

    locs = deprecate_property(
        locations, "locs", new_name="locations", removal_version="0.16.0", error=True,
    )

    @property
    def nD(self):
        """Number of data in the receiver."""
        return self.locations.shape[0]

    def getP(self, mesh, projGLoc=None):
        """
        Returns the projection matrices as a
        list for all components collected by
        the receivers.

        .. note::

            Projection matrices are stored as a dictionary listed by meshes.
        """
        if projGLoc is None:
            projGLoc = self.projGLoc

        if (mesh, projGLoc) in self._Ps:
            return self._Ps[(mesh, projGLoc)]

        P = mesh.getInterpolationMat(self.locations, projGLoc)
        if self.storeProjections:
            self._Ps[(mesh, projGLoc)] = P
        return P

    def eval(self, **kwargs):
        raise NotImplementedError(
            "the eval method for {} has not been implemented".format(self)
        )

    def evalDeriv(self, **kwargs):
        raise NotImplementedError(
            "the evalDeriv method for {} has not been implemented".format(self)
        )
Exemple #21
0
class Point3DTipper(BaseRxNSEM_Point):
    """
    Natural source 3D tipper receiver base class

    :param numpy.ndarray locs: receiver locations (ie. :code:`np.r_[x,y,z]`)
    :param string orientation: receiver orientation 'x', 'y' or 'z'
    :param string component: real or imaginary component 'real' or 'imag'
    """

    orientation = properties.StringChoice(
        "orientation of the receiver. Must currently be 'zx', 'zy'",
        ["zx", "zy"])

    def __init__(self, locs, orientation="zx", component="real"):

        super().__init__(locs, orientation=orientation, component=component)

    def eval(self, src, mesh, f, return_complex=False):
        """
        Project the fields to natural source data.

        :param SimPEG.electromagnetics.frequency_domain.sources.BaseFDEMSrc src: The source of the fields to project
        :param discretize.TensorMesh mesh: Mesh defining the topology of the problem
        :param SimPEG.electromagnetics.frequency_domain.fields.FieldsFDEM f: Natural source fields object to project
        :rtype: numpy.ndarray
        :return: Evaluated component of the impedance data
        """
        # NOTE: Maybe set this as a property
        self.src = src
        self.mesh = mesh
        self.f = f

        if "zx" in self.orientation:
            Tij = -self._hy_px * self._hz_py + self._hy_py * self._hz_px
        if "zy" in self.orientation:
            Tij = self._hx_px * self._hz_py - self._hx_py * self._hz_px
        rx_eval_complex = self._Hd * Tij

        # Return the full impedance
        if return_complex:
            return rx_eval_complex
        return getattr(rx_eval_complex, self.component)

    def evalDeriv(self, src, mesh, f, v, adjoint=False):
        """
        The derivative of the projection wrt u

        :param SimPEG.electromagnetics.frequency_domain.sources.BaseFDEMSrc src: NSEM source
        :param discretize.TensorMesh mesh: Mesh defining the topology of the problem
        :param SimPEG.electromagnetics.frequency_domain.fields.FieldsFDEM f: NSEM fields object of the source
        :param numpy.ndarray v: Random vector of size
        :rtype: numpy.ndarray
        :return: Calculated derivative (nD,) (adjoint=False) and (nP,2) (adjoint=True)
            for both polarizations
        """
        self.src = src
        self.mesh = mesh
        self.f = f

        if adjoint:
            if "zx" in self.orientation:
                Tij = self._sDiag(self._aHd *
                                  (-self._sDiag(self._ahz_py) * self._ahy_px +
                                   self._sDiag(self._ahz_px) * self._ahy_py))

                def TijN_uV(x):
                    return (-self._ahz_py_u(self._sDiag(self._ahy_px) * x) -
                            self._ahy_px_u(self._sDiag(self._ahz_py) * x) +
                            self._ahy_py_u(self._sDiag(self._ahz_px) * x) +
                            self._ahz_px_u(self._sDiag(self._ahy_py) * x))

            elif "zy" in self.orientation:
                Tij = self._sDiag(self._aHd *
                                  (self._sDiag(self._ahz_py) * self._ahx_px -
                                   self._sDiag(self._ahz_px) * self._ahx_py))

                def TijN_uV(x):
                    return (self._ahx_px_u(self._sDiag(self._ahz_py) * x) +
                            self._ahz_py_u(self._sDiag(self._ahx_px) * x) -
                            self._ahx_py_u(self._sDiag(self._ahz_px) * x) -
                            self._ahz_px_u(self._sDiag(self._ahx_py) * x))

            # Calculate the complex derivative
            rx_deriv_real = TijN_uV(self._aHd * v) - self._aHd_uV(
                Tij.T * self._aHd * v)
            # NOTE: Need to reshape the output to go from 2*nU array to a (nU,2) matrix for each polarization
            # rx_deriv_real = np.hstack((mkvc(rx_deriv_real[:len(rx_deriv_real)/2],2),mkvc(rx_deriv_real[len(rx_deriv_real)/2::],2)))
            rx_deriv_real = rx_deriv_real.reshape((2, self.mesh.nE)).T
            # Extract the data
            if self.component == "imag":
                rx_deriv_component = 1j * rx_deriv_real
            elif self.component == "real":
                rx_deriv_component = rx_deriv_real.astype(complex)
        else:
            if "zx" in self.orientation:
                TijN_uV = (-self._sDiag(self._hy_px) * self._hz_py_u(v) -
                           self._sDiag(self._hz_py) * self._hy_px_u(v) +
                           self._sDiag(self._hy_py) * self._hz_px_u(v) +
                           self._sDiag(self._hz_px) * self._hy_py_u(v))
            elif "zy" in self.orientation:
                TijN_uV = (self._sDiag(self._hz_py) * self._hx_px_u(v) +
                           self._sDiag(self._hx_px) * self._hz_py_u(v) -
                           self._sDiag(self._hx_py) * self._hz_px_u(v) -
                           self._sDiag(self._hz_px) * self._hx_py_u(v))
            Tij = self.eval(src, mesh, f, True)
            # Calculate the complex derivative
            rx_deriv_complex = self._Hd * (TijN_uV -
                                           self._sDiag(Tij) * self._Hd_uV(v))
            rx_deriv_component = np.array(
                getattr(rx_deriv_complex, self.component))

        return rx_deriv_component
Exemple #22
0
class BaseIPSimulation(BaseDCSimulation):

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

    _data_type = properties.StringChoice(
        "IP data type", default="volt", choices=["volt", "apparent_chargeability"],
    )

    data_type = deprecate_property(
        _data_type,
        "data_type",
        new_name="receiver.data_type",
        removal_version="0.17.0",
        future_warn=True,
    )

    Ainv = None
    _f = None
    _Jmatrix = None
    gtgdiag = None
    _sign = None
    _pred = None
    _scale = None

    def fields(self, m=None):

        if m is not None:
            self.model = m
            # sensitivity matrix is fixed
            # self._Jmatrix = None

        if self._f is None:
            if self.verbose is True:
                print(">> Solve DC problem")
            self._f = super().fields(m=None)

        if self._scale is None:
            scale = Data(self.survey, np.full(self.survey.nD, self._sign))
            # loop through receievers to check if they need to set the _dc_voltage
            for src in self.survey.source_list:
                for rx in src.receiver_list:
                    if (
                        rx.data_type == "apparent_chargeability"
                        or self._data_type == "apparent_chargeability"
                    ):
                        scale[src, rx] = self._sign / rx.eval(src, self.mesh, self._f)
            self._scale = scale.dobs

        if self.verbose is True:
            print(">> Compute predicted data")

        self._pred = self.forward(m, f=self._f)

        # if not self.storeJ:
        #     self.Ainv.clean()

        return self._f

    def dpred(self, m=None, f=None):
        """
        Predicted data.

        .. math::

            d_\\text{pred} = Pf(m)

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

        return self._pred

    def getJtJdiag(self, m, W=None):
        """
        Return the diagonal of JtJ
        """

        if self.gtgdiag is None:
            J = self.getJ(m)
            if W is None:
                W = self._scale ** 2
            else:
                W = (self._scale * W.diagonal()) ** 2

            self.gtgdiag = np.einsum("i,ij,ij->j", W, J, J)

        return self.gtgdiag

    # @profile
    def Jvec(self, m, v, f=None):
        return self._scale * super().Jvec(m, v, f)

    def forward(self, m, f=None):
        return np.asarray(self.Jvec(m, m, f=f))

    def Jtvec(self, m, v, f=None):
        return super().Jtvec(m, v * self._scale, f)

    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 = 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 * (sdiag(u) * (dMfRhoI_dI.T * v))
            else:
                return dMfRhoI_dI * (sdiag(u) * (self.MfRhoDerivMat * v))
        else:
            dMf_drho = self.mesh.getFaceInnerProductDeriv(self.rho)(u)
            drho_dlogrho = 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 = 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 * (sdiag(u) * v)
            else:
                return sdiag(u) * (self.MeSigmaDerivMat * v)
        else:
            dsigma_dlogsigma = 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
                )
Exemple #23
0
class BaseIPSimulation2D(BaseDCSimulation2D):

    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)")

    data_type = properties.StringChoice(
        "IP data type", default="volt", choices=["volt", "apparent_chargeability"],
    )

    fieldsPair = Fields2D
    _Jmatrix = None
    _f = None
    sign = None
    _pred = None

    def fields(self, m):
        if self.verbose:
            print(">> Compute DC fields")
        if self._f is None:
            # re-uses the DC simulation's fields method
            self._f = super().fields(None)

            if self.verbose is True:
                print(">> Data type is apparaent chargeability")

            # call dpred function in 2D DC simulation
            # set data type as volt ... for DC simulation
            data_types = []
            for src in self.survey.source_list:
                for rx in src.receiver_list:
                    data_types.append(rx.data_type)
                    rx.data_type = "volt"

            dc_voltage = super().dpred(m=[], f=self._f)
            dc_data = Data(self.survey, dc_voltage)
            icount = 0
            for src in self.survey.source_list:
                for rx in src.receiver_list:
                    rx.data_type = data_types[icount]
                    rx._dc_voltage = dc_data[src, rx]
                    rx._Ps = {}
                    icount += 1

        self._pred = self.forward(m, f=self._f)

        return self._f

    def dpred(self, m=None, f=None):
        """
            Predicted data.

            .. math::

                d_\\text{pred} = Pf(m)

        """
        # return self.Jvec(m, m, f=f)
        if f is None:
            f = self.fields(m)

        return self._pred

    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 forward(self, m, f=None):
        return self.Jvec(m, m, f=f)

    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 = 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 * (sdiag(u) * v)
            else:
                return sdiag(u) * (self.MeSigmaDerivMat * v)
        else:
            dsigma_dlogsigma = 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 = sdiag(rho) * self.etaDeriv
            self._MccRhoiDerivMat = sdiag(vol * (-1.0 / 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 * (sdiag(u) * v)
            else:
                return sdiag(u) * (self.MccRhoiDerivMat * v)
        else:
            vol = self.mesh.vol
            rho = self.rho
            drho_dlogrho = sdiag(rho) * self.etaDeriv
            if adjoint:
                return drho_dlogrho.T * (sdia(u * vol * (-1.0 / rho ** 2)) * v)
            else:
                return sdiag(u * vol * (-1.0 / 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 = sdiag(sigma) * self.etaDeriv
            self._MnSigmaDerivMat = self.mesh.aveN2CC.T * 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 * (sdiag(u) * v)
            else:
                return sdiag(u) * (self.MnSigmaDerivMat * v)
        else:
            sigma = self.sigma
            vol = self.mesh.vol
            dsigma_dlogsigma = 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))
class Array(_BaseFile):
    """File resource for numeric array binary storage

    In addition to file metadata properties, Array instances have
    an :code:`array` attribute. This attribute can be set
    with a numeric list or a numpy array, and doing so will
    dynamically fill in the other properties.
    """

    SUB_TYPE = 'array'

    dtype = properties.StringChoice(
        'Data type of array',
        choices=ARRAY_DTYPES,
    )
    shape = properties.List(
        'Dimensions of the array',
        properties.Integer('', min=0),
        min_length=1,
    )
    content_type = properties.StringChoice(
        'Content type of the file',
        choices=['application/octet-stream'],
        default='application/octet-stream',
    )

    @property
    def array(self):
        """Reference to underlying numpy array

        This attribute does not need to be set; however, doing so will
        set the associated metadata.
        """
        return getattr(self, '_array', None)

    @array.setter
    def array(self, value):
        if not isinstance(value, (tuple, list, np.ndarray)):
            raise ValueError('Array must be numpy array, list, or tuple')
        self._array = np.array(value)
        kind = self._array.dtype.kind
        if kind not in NUMPY_DTYPES:
            raise ValueError('Invalid kind of array: {}'.format(kind))
        sizes = NUMPY_DTYPES[kind]
        itemsize = self._array.dtype.itemsize
        for size in sizes:
            if itemsize <= size:
                itemsize = size
                break
        else:
            itemsize = sizes[-1]
        dtype = '<{}{}'.format(kind, itemsize)
        if kind == 'f':
            mask = ~np.isnan(self._array)
            close = np.allclose(self._array[mask],
                                self._array.astype(dtype)[mask])
        else:
            close = (self._array == self._array.astype(dtype)).all()
        if not close:
            raise ValueError(
                'Converting array type {} to supported type {} failed'.format(
                    self._array.dtype, dtype))
        self._array = self._array.astype(dtype)
        self.dtype = dtype
        self.shape = list(self._array.shape)
        self.content_length = self._array.nbytes

    def __init__(self, array=None, **kwargs):
        """Array initialization includes the optional argument array

        By including this argument, assigning a numeric array to
        a :class:`properties.Instance` property will coerce the numeric
        array into an Array resource.
        """
        super(Array, self).__init__(**kwargs)
        if array is not None:
            self.array = array

    @properties.validator('content_length')
    def _validate_content_length(self, change):
        """Ensure content_length matches specified shape and dtype

        content_length may be smaller than the size expected from
        shape/dtype, if the data is compressed. However, it cannot be
        larger.
        """
        if not self.dtype or not self.shape:
            return
        length = np.dtype(ARRAY_DTYPES[self.dtype][0]).itemsize
        for dim in self.shape:
            length *= dim
        if change['value'] > length:
            raise properties.ValidationError(
                message='content_length is too large for given shape/dtype',
                reason='invalid',
                prop='content_length',
                instance=self,
            )

    def is_1d(self):
        """Helper method to indicate if array is 1D"""
        if self.shape is None:
            return False
        return all([dim == 1 for dim in self.shape[1:]])
Exemple #25
0
class Survey(BaseSurvey):
    """
    Base DC survey
    """

    source_list = properties.List(
        "A list of sources for the survey",
        properties.Instance("A DC source", Src.BaseSrc),
        default=[],
    )

    # Survey
    survey_geometry = properties.StringChoice(
        "Survey geometry of DC surveys",
        default="surface",
        choices=["surface", "borehole", "general"],
    )

    survey_type = properties.StringChoice(
        "DC-IP Survey type",
        default="dipole-dipole",
        choices=["dipole-dipole", "pole-dipole", "dipole-pole", "pole-pole"],
    )

    def __init__(self, source_list, **kwargs):
        super(Survey, self).__init__(source_list, **kwargs)

    def __repr__(self):
        return (f"{self.__class__.__name__}({self.survey_type}; "
                f"#sources: {self.nSrc}; #data: {self.nD})")

    @property
    def locations_a(self):
        """
        Location of the positive (+) current electrodes for each datum
        """
        if getattr(self, "_locations_a", None) is None:
            self._set_abmn_locations()
        return self._locations_a

    @property
    def locations_b(self):
        """
        Location of the negative (-) current electrodes for each datum
        """
        if getattr(self, "_locations_b", None) is None:
            self._set_abmn_locations()
        return self._locations_b

    @property
    def locations_m(self):
        """
        Location of the positive (+) potential electrodes for each datum
        """
        if getattr(self, "_locations_m", None) is None:
            self._set_abmn_locations()
        return self._locations_m

    @property
    def locations_n(self):
        """
        Location of the negative (-) potential electrodes for each datum
        """
        if getattr(self, "_locations_n", None) is None:
            self._set_abmn_locations()
        return self._locations_n

    a_locations = deprecate_property(
        locations_a,
        "a_locations",
        new_name="locations_a",
        removal_version="0.16.0",
        future_warn=True,
    )
    b_locations = deprecate_property(
        locations_b,
        "b_locations",
        new_name="locations_b",
        removal_version="0.16.0",
        future_warn=True,
    )
    m_locations = deprecate_property(
        locations_m,
        "m_locations",
        new_name="locations_m",
        removal_version="0.16.0",
        future_warn=True,
    )
    n_locations = deprecate_property(
        locations_n,
        "n_locations",
        new_name="locations_n",
        removal_version="0.16.0",
        future_warn=True,
    )

    @property
    def unique_electrode_locations(self):
        """
        Unique locations of the A, B, M, N electrodes
        """
        loc_a = self.locations_a
        loc_b = self.locations_b
        loc_m = self.locations_m
        loc_n = self.locations_n
        return np.unique(np.vstack((loc_a, loc_b, loc_m, loc_n)), axis=0)

    electrode_locations = deprecate_property(
        unique_electrode_locations,
        "electrode_locations",
        new_name="unique_electrode_locations",
        removal_version="0.16.0",
    )

    @property
    def source_locations(self):
        """
        Returns, in order, the source locations for all sources in the survey.

        Input:
        :param self: SimPEG.electromagnetics.static.resistivity.Survey

        Output:
        :return source_locations: List of np.ndarray containing the A and B
        electrode locations.
        """
        src_a = []
        src_b = []

        for src in self.source_list:

            src_a.append(src.location_a)
            src_b.append(src.location_b)

        return [np.vstack(src_a), np.vstack(src_b)]

    def set_geometric_factor(
        self,
        space_type="half-space",
        data_type=None,
        survey_type=None,
    ):
        if data_type is not None:
            warnings.warn(
                "The data_type kwarg is deprecated, please set the data_type on the "
                "receiver object itself. This behavoir will be removed in SimPEG "
                "0.16.0",
                DeprecationWarning,
            )
        if survey_type is not None:
            warnings.warn(
                "The survey_type parameter is no longer needed, and it will be removed "
                "in SimPEG 0.16.0.",
                FutureWarning,
            )

        geometric_factor = static_utils.geometric_factor(self,
                                                         space_type=space_type)

        geometric_factor = data.Data(self, geometric_factor)
        for source in self.source_list:
            for rx in source.receiver_list:
                if data_type is not None:
                    rx.data_type = data_type
                if rx.data_type == "apparent_resistivity":
                    rx._geometric_factor[source] = geometric_factor[source, rx]
        return geometric_factor

    def _set_abmn_locations(self):
        locations_a = []
        locations_b = []
        locations_m = []
        locations_n = []
        for source in self.source_list:
            for rx in source.receiver_list:
                nRx = rx.nD
                # Pole Source
                if isinstance(source, Src.Pole):
                    locations_a.append(
                        source.location.reshape([1, -1]).repeat(nRx, axis=0))
                    locations_b.append(
                        source.location.reshape([1, -1]).repeat(nRx, axis=0))
                # Dipole Source
                elif isinstance(source, Src.Dipole):
                    locations_a.append(source.location[0].reshape(
                        [1, -1]).repeat(nRx, axis=0))
                    locations_b.append(source.location[1].reshape(
                        [1, -1]).repeat(nRx, axis=0))
                # Pole RX
                if isinstance(rx, Rx.Pole):
                    locations_m.append(rx.locations)
                    locations_n.append(rx.locations)

                # Dipole RX
                elif isinstance(rx, Rx.Dipole):
                    locations_m.append(rx.locations[0])
                    locations_n.append(rx.locations[1])

        self._locations_a = np.vstack(locations_a)
        self._locations_b = np.vstack(locations_b)
        self._locations_m = np.vstack(locations_m)
        self._locations_n = np.vstack(locations_n)

    def getABMN_locations(self):
        warnings.warn(
            "The getABMN_locations method has been deprecated. Please instead "
            "ask for the property of interest: survey.locations_a, "
            "survey.locations_b, survey.locations_m, or survey.locations_n. "
            "This will be removed in version 0.16.0 of SimPEG",
            FutureWarning,
        )

    def drape_electrodes_on_topography(self,
                                       mesh,
                                       actind,
                                       option="top",
                                       topography=None,
                                       force=False):
        """Shift electrode locations to be on [top] of the active cells."""
        if self.survey_geometry == "surface":
            loc_a = self.locations_a
            loc_b = self.locations_b
            loc_m = self.locations_m
            loc_n = self.locations_n
            unique_electrodes, inv = np.unique(np.vstack(
                (loc_a, loc_b, loc_m, loc_n)),
                                               return_inverse=True,
                                               axis=0)
            inv_a, inv = inv[:len(loc_a)], inv[len(loc_a):]
            inv_b, inv = inv[:len(loc_b)], inv[len(loc_b):]
            inv_m, inv_n = inv[:len(loc_m)], inv[len(loc_m):]

            electrodes_shifted = drapeTopotoLoc(mesh,
                                                unique_electrodes,
                                                actind=actind,
                                                option=option)
            a_shifted = electrodes_shifted[inv_a]
            b_shifted = electrodes_shifted[inv_b]
            m_shifted = electrodes_shifted[inv_m]
            n_shifted = electrodes_shifted[inv_n]
            # These should all be in the same order as the survey datas
            ind = 0
            for src in self.source_list:
                a_loc, b_loc = a_shifted[ind], b_shifted[ind]
                if isinstance(src, Src.Pole):
                    src.location = a_loc
                else:
                    src.location = [a_loc, b_loc]
                for rx in src.receiver_list:
                    end = ind + rx.nD
                    m_locs, n_locs = m_shifted[ind:end], n_shifted[ind:end]
                    if isinstance(rx, Rx.Pole):
                        rx.locations = m_locs
                    else:
                        rx.locations = [m_locs, n_locs]
                    ind = end
            self._locations_a = a_shifted
            self._locations_b = b_shifted
            self._locations_m = m_shifted
            self._locations_n = n_shifted

        elif self.survey_geometry == "borehole":
            raise Exception("Not implemented yet for borehole survey_geometry")
        else:
            raise Exception(
                "Input valid survey survey_geometry: surface or borehole")

    def drapeTopo(self, *args, **kwargs):
        warnings.warn(
            "The drapeTopo method has been deprecated. Please instead "
            "use the drape_electrodes_on_topography method. "
            "This will be removed in version 0.16.0 of SimPEG",
            FutureWarning,
        )
        self.drape_electrodes_on_topography(*args, **kwargs)
class DataBasic(_BaseData):
    """Basic numeric attribute data

    This data type is defined by an array that directly maps
    to an element geometry. For no-data values, use NaN.

    Currently, this only accepts 1D arrays and, when applied to a grid,
    the array must be stored in row-major order.
    """
    BASE_TYPE = 'data'
    SUB_TYPE = 'basic'

    array = Pointer(
        'Array of numeric values at locations on an element geometry, '
        'specified by the location property',
        Array,
    )
    location = properties.StringChoice(
        'Location of the data on geometry',
        choices={
            'nodes': ['N', 'node', 'vertices', 'corners'],
            'cells': ['CC', 'cell', 'segments', 'faces', 'blocks'],
        })
    mappings = properties.List(
        'Mappings associated with the data',
        prop=properties.Union(
            '',
            props=[
                Pointer('', MappingContinuous),
                Pointer('', MappingDiscrete),
            ],
        ),
        max_length=100,
        default=list,
    )

    @properties.validator('array')
    def _validate_array_1d(self, change):
        """Ensure the array is 1D"""
        if (isinstance(change['value'], string_types)
                or change['value'] is properties.undefined):
            return
        if not change['value'].is_1d():
            raise properties.ValidationError(
                message='{} must use 1D array'.format(
                    self.__class__.__name__, ),
                reason='invalid',
                prop='array',
                instance=self,
            )

    def to_omf(self, cell_location):
        self.validate()
        if self.location == 'nodes':
            location = 'vertices'
        else:
            location = cell_location
        omf_data = omf.ScalarData(
            name=self.name or '',
            description=self.description or '',
            location=location,
            array=self.array.array,
        )
        return omf_data
Exemple #27
0
class SimulationNDCellCentered(BaseTimeSimulation):
    """Richards Simulation"""

    hydraulic_conductivity = properties.Instance(
        "hydraulic conductivity function", BaseHydraulicConductivity)
    water_retention = properties.Instance("water retention curve",
                                          BaseWaterRetention)

    # TODO: This can also be a function(time, u_ii)
    boundary_conditions = properties.Array("boundary conditions")
    initial_conditions = properties.Array("initial conditions")

    debug = properties.Bool("Show all messages", default=False)

    method = properties.StringChoice(
        "Formulation used, See notes in Celia et al., 1990",
        default="mixed",
        choices=["mixed", "head"],
    )

    do_newton = properties.Bool("Do a Newton iteration vs. a Picard iteration",
                                default=False)

    root_finder_max_iter = properties.Integer(
        "Maximum iterations for root_finder iteration", default=30)

    root_finder_tol = properties.Float("tolerance of the root_finder",
                                       default=1e-4)

    @properties.observer("model")
    def _on_model_change(self, change):
        """Update the nested model functions when the
        model of the problem changes.

        Specifically :code:`hydraulic_conductivity` and
        :code:`water_retention` models are updated iff they have mappings.
        """

        if (not self.hydraulic_conductivity.needs_model
                and not self.water_retention.needs_model):
            warnings.warn("There is no model to set.")
            return

        model = change["value"]

        if self.hydraulic_conductivity.needs_model:
            self.hydraulic_conductivity.model = model

        if self.water_retention.needs_model:
            self.water_retention.model = model

    def getBoundaryConditions(self, ii, u_ii):
        if isinstance(self.boundary_conditions, np.ndarray):
            return self.boundary_conditions

        time = self.time_mesh.vectorCCx[ii]

        return self.boundary_conditions(time, u_ii)

    @properties.observer(
        ["do_newton", "root_finder_max_iter", "root_finder_tol"])
    def _on_root_finder_update(self, change):
        """Setting do_newton etc. will clear the root_finder,
        which will be reinitialized when called
        """
        if hasattr(self, "_root_finder"):
            del self._root_finder

    @property
    def root_finder(self):
        """Root-finding Algorithm"""
        if getattr(self, "_root_finder", None) is None:
            self._root_finder = optimization.NewtonRoot(
                doLS=self.do_newton,
                maxIter=self.root_finder_max_iter,
                tol=self.root_finder_tol,
                Solver=self.solver,
            )
        return self._root_finder

    @utils.timeIt
    def fields(self, m=None):
        if self.water_retention.needs_model or self.hydraulic_conductivity.needs_model:
            assert m is not None
        else:
            assert m is None

        tic = time.time()
        u = list(range(self.nT + 1))
        u[0] = self.initial_conditions
        for ii, dt in enumerate(self.time_steps):
            bc = self.getBoundaryConditions(ii, u[ii])
            u[ii + 1] = self.root_finder.root(
                lambda hn1m, return_g=True: self.getResidual(
                    m, u[ii], hn1m, dt, bc, return_g=return_g),
                u[ii],
            )
            if self.debug:
                print("Solving Fields ({0:4d}/{1:d} - {2:3.1f}% Done) {3:d} "
                      "Iterations, {4:4.2f} seconds".format(
                          ii + 1,
                          self.nT,
                          100.0 * (ii + 1) / self.nT,
                          self.root_finder.iter,
                          time.time() - tic,
                      ))
        return u

    def dpred(self, m, f=None):
        """Create the projected data from a model.
        The field, f, (if provided) will be used for the predicted data
        instead of recalculating the fields (which may be expensive!).

        .. math::

            d_\\text{pred} = P(f(m), m)

        Where P is a projection of the fields onto the data space.
        """
        if f is None:
            f = self.fields(m)

        Ds = list(range(len(self.survey.receiver_list)))

        for ii, rx in enumerate(self.survey.receiver_list):
            Ds[ii] = rx(f, self)

        return np.concatenate(Ds)

    @property
    def Dz(self):
        if self.mesh.dim == 1:
            return self.mesh.faceDivx

        if self.mesh.dim == 2:
            mats = (utils.spzeros(self.mesh.nC,
                                  self.mesh.vnF[0]), self.mesh.faceDivy)
        elif self.mesh.dim == 3:
            mats = (
                utils.spzeros(self.mesh.nC,
                              self.mesh.vnF[0] + self.mesh.vnF[1]),
                self.mesh.faceDivz,
            )
        return sp.hstack(mats, format="csr")

    @utils.timeIt
    def diagsJacobian(self, m, hn, hn1, dt, bc):
        """Diagonals and rhs of the jacobian system

        The matrix that we are computing has the form::

            .-                                      -. .-  -.   .-  -.
            |  Adiag                                 | | h1 |   | b1 |
            |   Asub    Adiag                        | | h2 |   | b2 |
            |            Asub    Adiag               | | h3 | = | b3 |
            |                 ...     ...            | | .. |   | .. |
            |                         Asub    Adiag  | | hn |   | bn |
            '-                                      -' '-  -'   '-  -'
        """
        if m is not None:
            self.model = m

        DIV = self.mesh.faceDiv
        GRAD = self.mesh.cellGrad
        BC = self.mesh.cellGradBC
        AV = self.mesh.aveF2CC.T
        Dz = self.Dz

        dT = self.water_retention.derivU(hn)
        dT1 = self.water_retention.derivU(hn1)
        dTm = self.water_retention.derivM(hn)
        dTm1 = self.water_retention.derivM(hn1)

        K1 = self.hydraulic_conductivity(hn1)
        dK1 = self.hydraulic_conductivity.derivU(hn1)
        dKm1 = self.hydraulic_conductivity.derivM(hn1)

        # Compute part of the derivative of:
        #
        #       DIV*diag(GRAD*hn1+BC*bc)*(AV*(1.0/K))^-1

        DdiagGh1 = DIV * utils.sdiag(GRAD * hn1 + BC * bc)
        diagAVk2_AVdiagK2 = (utils.sdiag(
            (AV * (1.0 / K1))**(-2)) * AV * utils.sdiag(K1**(-2)))

        Asub = (-1.0 / dt) * dT

        Adiag = ((1.0 / dt) * dT1 - DdiagGh1 * diagAVk2_AVdiagK2 * dK1 -
                 DIV * utils.sdiag(1.0 / (AV * (1.0 / K1))) * GRAD -
                 Dz * diagAVk2_AVdiagK2 * dK1)

        B = (DdiagGh1 * diagAVk2_AVdiagK2 * dKm1 +
             Dz * diagAVk2_AVdiagK2 * dKm1 + (1.0 / dt) * (dTm - dTm1))

        return Asub, Adiag, B

    @utils.timeIt
    def getResidual(self, m, hn, h, dt, bc, return_g=True):
        """Used by the root finder when going between timesteps

        Where h is the proposed value for the next time iterate (h_{n+1})
        """
        if m is not None:
            self.model = m

        DIV = self.mesh.faceDiv
        GRAD = self.mesh.cellGrad
        BC = self.mesh.cellGradBC
        AV = self.mesh.aveF2CC.T
        Dz = self.Dz

        T = self.water_retention(h)
        dT = self.water_retention.derivU(h)
        Tn = self.water_retention(hn)
        K = self.hydraulic_conductivity(h)
        dK = self.hydraulic_conductivity.derivU(h)

        aveK = 1.0 / (AV * (1.0 / K))

        RHS = DIV * utils.sdiag(aveK) * (GRAD * h + BC * bc) + Dz * aveK
        if self.method == "mixed":
            r = (T - Tn) / dt - RHS
        elif self.method == "head":
            r = dT * (h - hn) / dt - RHS

        if not return_g:
            return r

        J = dT / dt - DIV * utils.sdiag(aveK) * GRAD
        if self.do_newton:
            DDharmAve = utils.sdiag(aveK**2) * AV * utils.sdiag(K**(-2)) * dK
            J = J - DIV * utils.sdiag(GRAD * h +
                                      BC * bc) * DDharmAve - Dz * DDharmAve

        return r, J

    @utils.timeIt
    def Jfull(self, m=None, f=None):
        if f is None:
            f = self.fields(m)

        nn = len(f) - 1
        Asubs, Adiags, Bs = list(range(nn)), list(range(nn)), list(range(nn))
        for ii in range(nn):
            dt = self.time_steps[ii]
            bc = self.getBoundaryConditions(ii, f[ii])
            Asubs[ii], Adiags[ii], Bs[ii] = self.diagsJacobian(
                m, f[ii], f[ii + 1], dt, bc)
        Ad = sp.block_diag(Adiags)
        zRight = utils.spzeros((len(Asubs) - 1) * Asubs[0].shape[0],
                               Adiags[0].shape[1])
        zTop = utils.spzeros(Adiags[0].shape[0],
                             len(Adiags) * Adiags[0].shape[1])
        As = sp.vstack((zTop, sp.hstack((sp.block_diag(Asubs[1:]), zRight))))
        A = As + Ad
        B = np.array(sp.vstack(Bs).todense())

        Ainv = self.solver(A, **self.solver_opts)
        AinvB = Ainv * B
        z = np.zeros((self.mesh.nC, B.shape[1]))
        du_dm = np.vstack((z, AinvB))
        J = self.survey.deriv(self, f, du_dm_v=du_dm)  # not multiplied by v
        return J

    @utils.timeIt
    def Jvec(self, m, v, f=None):
        if f is None:
            f = self.fields(m)

        JvC = list(range(len(f) -
                         1))  # Cell to hold each row of the long vector

        # This is done via forward substitution.
        bc = self.getBoundaryConditions(0, f[0])
        temp, Adiag, B = self.diagsJacobian(m, f[0], f[1], self.time_steps[0],
                                            bc)
        Adiaginv = self.solver(Adiag, **self.solver_opts)
        JvC[0] = Adiaginv * (B * v)

        for ii in range(1, len(f) - 1):
            bc = self.getBoundaryConditions(ii, f[ii])
            Asub, Adiag, B = self.diagsJacobian(m, f[ii], f[ii + 1],
                                                self.time_steps[ii], bc)
            Adiaginv = self.solver(Adiag, **self.solver_opts)
            JvC[ii] = Adiaginv * (B * v - Asub * JvC[ii - 1])

        du_dm_v = np.concatenate([np.zeros(self.mesh.nC)] + JvC)
        Jv = self.survey.deriv(self, f, du_dm_v=du_dm_v, v=v)
        return Jv

    @utils.timeIt
    def Jtvec(self, m, v, f=None):
        if f is None:
            f = self.field(m)

        PTv, PTdv = self.survey.derivAdjoint(self, f, v=v)

        # This is done via backward substitution.
        minus = 0
        BJtv = 0
        for ii in range(len(f) - 1, 0, -1):
            bc = self.getBoundaryConditions(ii - 1, f[ii - 1])
            Asub, Adiag, B = self.diagsJacobian(m, f[ii - 1], f[ii],
                                                self.time_steps[ii - 1], bc)
            # select the correct part of v
            vpart = list(
                range((ii) * Adiag.shape[0], (ii + 1) * Adiag.shape[0]))
            AdiaginvT = self.solver(Adiag.T, **self.solver_opts)
            JTvC = AdiaginvT * (PTv[vpart] - minus)
            minus = Asub.T * JTvC  # this is now the super diagonal.
            BJtv = BJtv + B.T * JTvC

        return BJtv + PTdv
Exemple #28
0
class Point(BaseRx):
    """Point receiver"""

    times = properties.Array("Observation times", dtype=float)

    fieldType = properties.StringChoice(
        "Field type", choices=["h", "b", "dhdt", "dbdt"]
    )

    orientation = properties.StringChoice(
        "Component of response", choices=["x", "y", "z"]
    )

    def __init__(self, locations=None, **kwargs):

        if locations.shape[1] != 3:
            raise ValueError(
                "Rx locations (xi,yi,zi) must be np.array(N,3) where N is the number of stations"
            )

        super(Point, self).__init__(locations, **kwargs)

    @property
    def n_times(self):
        """Number of measurements times."""
        if self.times is not None:
            return len(self.times)

    nTimes = deprecate_property(
        n_times,
        "nTimes",
        new_name="n_times",
        removal_version="0.16.0",
        future_warn=True,
    )

    @property
    def n_locations(self):
        """Number of locations."""
        return self.locations.shape[0]

    nLocs = deprecate_property(
        n_locations,
        "nLocs",
        new_name="n_locations",
        removal_version="0.16.0",
        future_warn=True,
    )

    @property
    def nD(self):
        """Number of data in the receiver."""
        if self.times is not None:
            return self.locations.shape[0] * len(self.times)

    fieldComp = deprecate_property(
        orientation,
        "fieldComp",
        new_name="orientation",
        removal_version="0.16.0",
        future_warn=True,
    )
Exemple #29
0
class BaseTDEMSrc(BaseEMSrc):

    # rxPair = Rx

    waveformPair = BaseWaveform  #: type of waveform to pair with
    waveform = None  #: source waveform
    srcType = properties.StringChoice(
        "is the source a galvanic of inductive source",
        choices=["inductive", "galvanic"],
    )

    def __init__(self, rxList, **kwargs):
        super(BaseTDEMSrc, self).__init__(rxList, **kwargs)

    @property
    def waveform(self):
        "A waveform instance is not None"
        return getattr(self, '_waveform', None)

    @waveform.setter
    def waveform(self, val):
        if self.waveform is None:
            val._assertMatchesPair(self.waveformPair)
            self._waveform = val
        else:
            self._waveform = self.StepOffWaveform(val)

    def __init__(self, rxList, waveform=StepOffWaveform(), **kwargs):
        self.waveform = waveform
        BaseEMSrc.__init__(self, rxList, **kwargs)

    def bInitial(self, prob):
        return Zero()

    def bInitialDeriv(self, prob, v=None, adjoint=False, f=None):
        return Zero()

    def eInitial(self, prob):
        return Zero()

    def eInitialDeriv(self, prob, v=None, adjoint=False, f=None):
        return Zero()

    def hInitial(self, prob):
        return Zero()

    def hInitialDeriv(self, prob, v=None, adjoint=False, f=None):
        return Zero()

    def jInitial(self, prob):
        return Zero()

    def jInitialDeriv(self, prob, v=None, adjoint=False, f=None):
        return Zero()

    def eval(self, prob, time):
        s_m = self.s_m(prob, time)
        s_e = self.s_e(prob, time)
        return s_m, s_e

    def evalDeriv(self, prob, time, v=None, adjoint=False):
        if v is not None:
            return (self.s_mDeriv(prob, time, v, adjoint),
                    self.s_eDeriv(prob, time, v, adjoint))
        else:
            return (lambda v: self.s_mDeriv(prob, time, v, adjoint),
                    lambda v: self.s_eDeriv(prob, time, v, adjoint))

    def s_m(self, prob, time):
        return Zero()

    def s_e(self, prob, time):
        return Zero()

    def s_mDeriv(self, prob, time, v=None, adjoint=False):
        return Zero()

    def s_eDeriv(self, prob, time, v=None, adjoint=False):
        return Zero()
Exemple #30
0
class BaseTDEMSrc(BaseEMSrc):

    # rxPair = Rx
    waveform = properties.Instance(
        "A source waveform", BaseWaveform, default=StepOffWaveform()
    )
    srcType = properties.StringChoice(
        "is the source a galvanic of inductive source",
        choices=["inductive", "galvanic"],
    )

    def __init__(self, receiver_list=None, **kwargs):
        if receiver_list is not None:
            kwargs["receiver_list"] = receiver_list
        super(BaseTDEMSrc, self).__init__(**kwargs)

    def bInitial(self, prob):
        return Zero()

    def bInitialDeriv(self, prob, v=None, adjoint=False, f=None):
        return Zero()

    def eInitial(self, prob):
        return Zero()

    def eInitialDeriv(self, prob, v=None, adjoint=False, f=None):
        return Zero()

    def hInitial(self, prob):
        return Zero()

    def hInitialDeriv(self, prob, v=None, adjoint=False, f=None):
        return Zero()

    def jInitial(self, prob):
        return Zero()

    def jInitialDeriv(self, prob, v=None, adjoint=False, f=None):
        return Zero()

    def eval(self, prob, time):
        s_m = self.s_m(prob, time)
        s_e = self.s_e(prob, time)
        return s_m, s_e

    def evalDeriv(self, prob, time, v=None, adjoint=False):
        if v is not None:
            return (
                self.s_mDeriv(prob, time, v, adjoint),
                self.s_eDeriv(prob, time, v, adjoint),
            )
        else:
            return (
                lambda v: self.s_mDeriv(prob, time, v, adjoint),
                lambda v: self.s_eDeriv(prob, time, v, adjoint),
            )

    def s_m(self, prob, time):
        return Zero()

    def s_e(self, prob, time):
        return Zero()

    def s_mDeriv(self, prob, time, v=None, adjoint=False):
        return Zero()

    def s_eDeriv(self, prob, time, v=None, adjoint=False):
        return Zero()