diff --git a/.travis.yml b/.travis.yml index 4b5e47ff..a4614a6b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,6 +18,7 @@ env: - TEST_DIR="tests/mesh tests/base tests/utils" - TEST_DIR=tests/em/fdem/inverse/derivs - TEST_DIR=tests/em/tdem + - TEST_DIR=tests/dcip - TEST_DIR=tests/flow - TEST_DIR=tests/mt - TEST_DIR=tests/examples diff --git a/SimPEG/DCIP/BaseDC.py b/SimPEG/DCIP/BaseDC.py new file mode 100644 index 00000000..e3d353d7 --- /dev/null +++ b/SimPEG/DCIP/BaseDC.py @@ -0,0 +1,294 @@ +from SimPEG import * + +class FieldsDC_CC(Problem.Fields): + knownFields = {'phi_sol':'CC'} + aliasFields = { + 'phi' : ['phi_sol','CC','_phi'], + 'e' : ['phi_sol','F','_e'], + 'j' : ['phi_sol','F','_j'] + } + + def __init__(self,mesh,survey,**kwargs): + super(FieldsDC_CC, self).__init__(mesh, survey, **kwargs) + + def startup(self): + self._cellGrad = self.survey.prob.mesh.cellGrad + self._Mfinv = self.survey.prob.mesh.getFaceInnerProduct(invMat=True) + + def _phi(self, phi_sol, srcList): + phi = phi_sol + # for i, src in enumerate(srcList): + # phi_p = src.phi_p(self.survey.prob) + # if phi_p is not None: + # phi[:,i] += phi_p + return phi + + def _e(self, phi_sol, srcList): + e = -self._cellGrad*phi_sol + # for i, src in enumerate(srcList): + # e_p = src.e_p(self.survey.prob) + # if e_p is not None: + # e[:,i] += e_p + return e + + def _j(self, phi_sol, srcList): + + j = -self._Mfinv*self.survey.prob.Msig*self._cellGrad*phi_sol + # for i, src in enumerate(srcList): + # j_p = src.j_p(self.survey.prob) + # if j_p is not None: + # j[:,i] += j_p + return j + + + +class SrcDipole(Survey.BaseSrc): + """A dipole source, locA and locB are moved to the closest cell-centers""" + + current = 1 + loc = None + # _rhsDict = None + + def __init__(self, rxList, locA, locB, **kwargs): + self.loc = (locA, locB) + super(SrcDipole, self).__init__(rxList, **kwargs) + + def eval(self, prob): + # Recompute rhs + # if getattr(self, '_rhsDict', None) is None: + # self._rhsDict = {} + # if mesh not in self._rhsDict: + pts = [self.loc[0], self.loc[1]] + inds = Utils.closestPoints(prob.mesh, pts) + q = np.zeros(prob.mesh.nC) + q[inds] = - self.current * ( np.r_[1., -1.] / prob.mesh.vol[inds] ) + # self._rhsDict[mesh] = q + # return self._rhsDict[mesh] + return q + + +class RxDipole(Survey.BaseRx): + """A dipole source, locA and locB are moved to the closest cell-centers""" + def __init__(self, locsM, locsN, **kwargs): + locs = (locsM, locsN) + assert locsM.shape == locsN.shape, 'locs must be the same shape.' + super(RxDipole, self).__init__(locs, 'dipole', storeProjections=False, **kwargs) + + @property + def nD(self): + """Number of data in the receiver.""" + return self.locs[0].shape[0] + + def getP(self, mesh): + P0 = mesh.getInterpolationMat(self.locs[0], self.projGLoc) + P1 = mesh.getInterpolationMat(self.locs[1], self.projGLoc) + return P0 - P1 + + +class SurveyDC(Survey.BaseSurvey): + """ + **SurveyDC** + + Geophysical DC resistivity data. + + """ + uncert = None + def __init__(self, srcList, **kwargs): + self.srcList = srcList + Survey.BaseSurvey.__init__(self, **kwargs) + # self._rhsDict = {} + self._Ps = {} + + def eval(self, u): + """ + Predicted data. + + .. math:: + d_\\text{pred} = Pu(m) + """ + P = self.getP(self.prob.mesh) + return P*mkvc(u[self.srcList, 'phi_sol']) + + def getP(self, mesh): + if mesh in self._Ps: + return self._Ps[mesh] + + P_src = [sp.vstack([rx.getP(mesh) for rx in src.rxList]) for src in self.srcList] + + self._Ps[mesh] = sp.block_diag(P_src) + return self._Ps[mesh] + + +class ProblemDC_CC(Problem.BaseProblem): + """ + **ProblemDC** + + Geophysical DC resistivity problem. + + """ + + surveyPair = SurveyDC + Solver = Solver + fieldsPair = FieldsDC_CC + Ainv = None + + def __init__(self, mesh, **kwargs): + Problem.BaseProblem.__init__(self, mesh) + self.mesh.setCellGradBC('neumann') + Utils.setKwargs(self, **kwargs) + + + deleteTheseOnModelUpdate = ['_A', '_Msig', '_dMdsig'] + + @property + def Msig(self): + if getattr(self, '_Msig', None) is None: + sigma = self.curModel.transform + Av = self.mesh.aveF2CC + self._Msig = Utils.sdiag(1/(self.mesh.dim * Av.T * (1/sigma))) + return self._Msig + + @property + def dMdsig(self): + if getattr(self, '_dMdsig', None) is None: + sigma = self.curModel.transform + Av = self.mesh.aveF2CC + dMdprop = self.mesh.dim * Utils.sdiag(self.Msig.diagonal()**2) * Av.T * Utils.sdiag(1./sigma**2) + self._dMdsig = lambda Gu: Utils.sdiag(Gu) * dMdprop + return self._dMdsig + + @property + def A(self): + """ + Makes the matrix A(m) for the DC resistivity problem. + + :param numpy.array m: model + :rtype: scipy.csc_matrix + :return: A(m) + + .. math:: + c(m,u) = A(m)u - q = G\\text{sdiag}(M(mT(m)))Du - q = 0 + + Where M() is the mass matrix and mT is the model transform. + """ + if getattr(self, '_A', None) is None: + D = self.mesh.faceDiv + G = self.mesh.cellGrad + self._A = D*self.Msig*G + # Remove the null space from the matrix. + self._A[0,0] /= self.mesh.vol[0] + self._A = self._A.tocsc() + return self._A + + def getRHS(self): + # if self.mesh not in self._rhsDict: + RHS = np.array([src.eval(self) for src in self.survey.srcList]).T + # self._rhsDict[mesh] = RHS + # return self._rhsDict[mesh] + return RHS + + def fields(self, m): + + F = self.fieldsPair(self.mesh, self.survey) + self.curModel = m + A = self.A + self.Ainv = self.Solver(A, **self.solverOpts) + RHS = self.getRHS() + Phi = self.Ainv * RHS + Srcs = self.survey.srcList + F[Srcs, 'phi_sol'] = Phi + + return F + + def Jvec(self, m, v, u=None): + """ + :param numpy.array m: model + :param numpy.array v: vector to multiply + :param numpy.array u: fields + :rtype: numpy.array + :return: Jv + + .. math:: + c(m,u) = A(m)u - q = G\\text{sdiag}(M(mT(m)))Du - q = 0 + + \\nabla_u (A(m)u - q) = A(m) + + \\nabla_m (A(m)u - q) = G\\text{sdiag}(Du)\\nabla_m(M(mT(m))) + + Where M() is the mass matrix and mT is the model transform. + + .. math:: + J = - P \left( \\nabla_u c(m, u) \\right)^{-1} \\nabla_m c(m, u) + + J(v) = - P ( A(m)^{-1} ( G\\text{sdiag}(Du)\\nabla_m(M(mT(m))) v ) ) + """ + # Set current model; clear dependent property $\mathbf{A(m)}$ + self.curModel = m + sigma = self.curModel.transform # $\sigma = \mathcal{M}(\m)$ + if u is None: + # Run forward simulation if $u$ not provided + u = self.fields(self.curModel)[self.survey.srcList, 'phi_sol'] + else: + u = u[self.survey.srcList, 'phi_sol'] + + D = self.mesh.faceDiv + G = self.mesh.cellGrad + # Derivative of model transform, $\deriv{\sigma}{\m}$ + dsigdm_x_v = self.curModel.transformDeriv * v + + # Take derivative of $C(m,u)$ w.r.t. $m$ + dCdm_x_v = np.empty_like(u) + # loop over fields for each source + for i in range(self.survey.nSrc): + # Derivative of inner product, $\left(\mathbf{M}_{1/\sigma}^f\right)^{-1}$ + dAdsig = D * self.dMdsig( G * u[:,i] ) + dCdm_x_v[:, i] = dAdsig * dsigdm_x_v + + # Take derivative of $C(m,u)$ w.r.t. $u$ + dA_du = self.A + # Solve for $\deriv{u}{m}$ + # dCdu_inv = self.Solver(dCdu, **self.solverOpts) + if self.Ainv is None: + self.Ainv = self.Solver(dA_du, **self.solverOpts) + + P = self.survey.getP(self.mesh) + Jv = - P * mkvc( self.Ainv * dCdm_x_v ) + return Jv + + def Jtvec(self, m, v, u=None): + + self.curModel = m + sigma = self.curModel.transform # $\sigma = \mathcal{M}(\m)$ + if u is None: + # Run forward simulation if $u$ not provided + u = self.fields(self.curModel)[self.survey.srcList, 'phi_sol'] + else: + u = u[self.survey.srcList, 'phi_sol'] + + shp = u.shape + P = self.survey.getP(self.mesh) + PT_x_v = (P.T*v).reshape(shp, order='F') + + D = self.mesh.faceDiv + G = self.mesh.cellGrad + dA_du = self.A + mT_dm = self.mapping.deriv(m) + + # We probably always need this due to the linesearch .. (?) + self.Ainv = self.Solver(dA_du.T, **self.solverOpts) + # if self.Ainv is None: + # self.Ainv = self.Solver(dCdu, **self.solverOpts) + + w = self.Ainv * PT_x_v + + Jtv = 0 + for i, ui in enumerate(u.T): # loop over each column + Jtv += self.dMdsig( G * ui ).T * ( D.T * w[:,i] ) + + Jtv = - mT_dm.T * ( Jtv ) + return Jtv + + + + + diff --git a/SimPEG/DCIP/BaseIP.py b/SimPEG/DCIP/BaseIP.py new file mode 100644 index 00000000..cec0ea2e --- /dev/null +++ b/SimPEG/DCIP/BaseIP.py @@ -0,0 +1,182 @@ +from SimPEG import * +from BaseDC import SurveyDC, FieldsDC_CC + +class SurveyIP(SurveyDC): + """ + **SurveyDC** + + Geophysical DC resistivity data. + + """ + + def __init__(self, srcList, **kwargs): + self.srcList = srcList + Survey.BaseSurvey.__init__(self, **kwargs) + self._Ps = {} + + def dpred(self, m, u=None): + """ + Predicted data. + + .. math:: + d_\\text{pred} = Pu(m) + """ + + return self.prob.forward(m) + + +class ProblemIP(Problem.BaseProblem): + """ + **ProblemIP** + + Geophysical IP resistivity problem. + + """ + + surveyPair = SurveyDC + Solver = Solver + sigma = None + Ainv = None + u = None + + def __init__(self, mesh, **kwargs): + Problem.BaseProblem.__init__(self, mesh) + self.mesh.setCellGradBC('neumann') + Utils.setKwargs(self, **kwargs) + + # deleteTheseOnModelUpdate = ['_A', '_Msig', '_dMdsig'] + + @property + def Msig(self): + if getattr(self, '_Msig', None) is None: + # sigma = self.curModel.transform + sigma = self.sigma + Av = self.mesh.aveF2CC + self._Msig = Utils.sdiag(1/(self.mesh.dim * Av.T * (1/sigma))) + return self._Msig + + @property + def dMdsig(self): + if getattr(self, '_dMdsig', None) is None: + # sigma = self.curModel.transform + sigma = self.sigma + Av = self.mesh.aveF2CC + dMdprop = self.mesh.dim * Utils.sdiag(self.Msig.diagonal()**2) * Av.T * Utils.sdiag(1./sigma**2) + self._dMdsig = lambda Gu: Utils.sdiag(Gu) * dMdprop + return self._dMdsig + + @property + def A(self): + """ + Makes the matrix A(m) for the DC resistivity problem. + + :param numpy.array m: model + :rtype: scipy.csc_matrix + :return: A(m) + + .. math:: + c(m,u) = A(m)u - q = G\\text{sdiag}(M(mT(m)))Du - q = 0 + + Where M() is the mass matrix and mT is the model transform. + """ + if getattr(self, '_A', None) is None: + D = self.mesh.faceDiv + G = self.mesh.cellGrad + self._A = D*self.Msig*G + # Remove the null space from the matrix. + self._A[-1,-1] /= self.mesh.vol[-1] + self._A = self._A.tocsc() + return self._A + + def getRHS(self): + # if self.mesh not in self._rhsDict: + RHS = np.array([src.eval(self) for src in self.survey.srcList]).T + # self._rhsDict[mesh] = RHS + # return self._rhsDict[mesh] + return RHS + + def fields(self, m): + if self.u is None: + A = self.A + if self.Ainv == None: + self.Ainv = self.Solver(A, **self.solverOpts) + Q = self.getRHS() + self.u = self.Ainv * Q + return self.u + + def forward(self, m, u=None): + # Set current model; clear dependent property $\mathbf{A(m)}$ + self.curModel = m + # sigma = self.curModel.transform # $\sigma = \mathcal{M}(\m)$ + sigma = self.sigma + if self.u is None: + # Run forward simulation if $u$ not provided + u = self.fields(sigma) + + shp = (self.mesh.nC, self.survey.nSrc) + u = self.u.reshape(shp, order='F') + + D = self.mesh.faceDiv + G = self.mesh.cellGrad + # Derivative of model transform, $\deriv{\sigma}{\m}$ + # dsigdm_x_v = self.curModel.transformDeriv * v + + dsigdm_x_v = Utils.sdiag(sigma) * self.curModel.transformDeriv * m + + # Take derivative of $C(m,u)$ w.r.t. $m$ + dCdm_x_v = np.empty_like(u) + # loop over fields for each source + for i in range(self.survey.nSrc): + # Derivative of inner product, $\left(\mathbf{M}_{1/\sigma}^f\right)^{-1}$ + dAdsig = D * self.dMdsig( G * u[:,i] ) + dCdm_x_v[:, i] = dAdsig * dsigdm_x_v + + # Take derivative of $C(m,u)$ w.r.t. $u$ + + if self.Ainv == None: + self.Ainv = self.Solver(A, **self.solverOpts) + + # dCdu = self.A + # Solve for $\deriv{u}{m}$ + # dCdu_inv = self.Solver(dCdu, **self.solverOpts) + P = self.survey.getP(self.mesh) + J_x_v = - P * mkvc( self.Ainv * dCdm_x_v ) + return -J_x_v + + def Jvec(self, m, v, u=None): + return self.forward(v) + + def Jtvec(self, m, v, u=None): + + self.curModel = m + # sigma = self.curModel.transform # $\sigma = \mathcal{M}(\m)$ + sigma = self.sigma + if self.u is None: + u = self.fields(sigma) + else: + u = self.u + shp = (self.mesh.nC, self.survey.nSrc) + u = u.reshape(shp, order='F') + P = self.survey.getP(self.mesh) + PT_x_v = (P.T*v).reshape(shp, order='F') + + D = self.mesh.faceDiv + G = self.mesh.cellGrad + A = self.A + mT_dm = Utils.sdiag(sigma)*self.mapping.deriv(m) + # mT_dm = self.mapping.deriv(m) + + # dCdu = A.T + # Ainv = self.Solver(dCdu, **self.solverOpts) + # if self.Ainv == None: + self.Ainv = self.Solver(A.T, **self.solverOpts) + + w = self.Ainv * PT_x_v + + Jtv = 0 + for i, ui in enumerate(u.T): # loop over each column + Jtv += self.dMdsig( G * ui ).T * ( D.T * w[:,i] ) + + Jtv = - mT_dm.T * ( Jtv ) + return -Jtv + diff --git a/SimPEG/DCIP/DCIPUtils.py b/SimPEG/DCIP/DCIPUtils.py new file mode 100644 index 00000000..90bbbca0 --- /dev/null +++ b/SimPEG/DCIP/DCIPUtils.py @@ -0,0 +1,934 @@ +from SimPEG import np +import BaseDC as DC +import BaseDC as IP + +def getActiveindfromTopo(mesh, topo): +# def genActiveindfromTopo(mesh, topo): + """ + Get active indices from topography + """ + from scipy.interpolate import NearestNDInterpolator + if mesh.dim==3: + nCxy = mesh.nCx*mesh.nCy + Zcc = mesh.gridCC[:,2].reshape((nCxy, mesh.nCz), order='F') + Ftopo = NearestNDInterpolator(topo[:,:2], topo[:,2]) + XY = Utils.ndgrid(mesh.vectorCCx, mesh.vectorCCy) + XY.shape + topo = Ftopo(XY) + actind = [] + for ixy in range(nCxy): + actind.append(topo[ixy] <= Zcc[ixy,:]) + else: + raise NotImplementedError("Only 3D is working") + + return Utils.mkvc(np.vstack(actind)) + +def gettopoCC(mesh, airind): +# def gettopoCC(mesh, airind): + """ + Get topography from active indices of mesh. + """ + mesh2D = Mesh.TensorMesh([mesh.hx, mesh.hy], mesh.x0[:2]) + zc = mesh.gridCC[:,2] + AIRIND = airind.reshape((mesh.vnC[0]*mesh.vnC[1],mesh.vnC[2]), order='F') + ZC = zc.reshape((mesh.vnC[0]*mesh.vnC[1], mesh.vnC[2]), order='F') + topo = np.zeros(ZC.shape[0]) + topoCC = np.zeros(ZC.shape[0]) + for i in range(ZC.shape[0]): + ind = np.argmax(ZC[i,:][~AIRIND[i,:]]) + topo[i] = ZC[i,:][~AIRIND[i,:]].max() + mesh.hz[~AIRIND[i,:]][ind]*0.5 + topoCC[i] = ZC[i,:][~AIRIND[i,:]].max() + XY = Utils.ndgrid(mesh.vectorCCx, mesh.vectorCCy) + return mesh2D, topoCC + +def readUBC_DC3Dobstopo(filename,mesh,topo,probType="CC"): + """ + Seogi's personal readObs function. + + """ + text_file = open(filename, "r") + lines = text_file.readlines() + text_file.close() + SRC = [] + DATA = [] + srcLists = [] + isrc = 0 + # airind = getActiveindfromTopo(mesh, topo) + # mesh2D, topoCC = gettopoCC(mesh, airind) + + for line in lines: + if "!" in line.split(): continue + elif line == '\n': continue + elif line == ' \n': continue + temp = map(float, line.split()) + # Read a line for the current electrode + if len(temp) == 5: # SRC: Only X and Y are provided (assume no topography) + #TODO consider topography and assign the closest cell center in the earth + if isrc == 0: + DATA_temp = [] + else: + DATA.append(np.asarray(DATA_temp)) + DATA_temp = [] + indM = Utils.closestPoints(mesh2D, DATA[isrc-1][:,1:3]) + indN = Utils.closestPoints(mesh2D, DATA[isrc-1][:,3:5]) + rx = DCIP.RxDipole(np.c_[DATA[isrc-1][:,1:3], topoCC[indM]], np.c_[DATA[isrc-1][:,3:5], topoCC[indN]]) + temp = np.asarray(temp) + if [SRC[isrc-1][0], SRC[isrc-1][1]] == [SRC[isrc-1][2], SRC[isrc-1][3]]: + indA = Utils.closestPoints(mesh2D, [SRC[isrc-1][0], SRC[isrc-1][1]]) + tx = DCIP.SrcDipole([rx], [SRC[isrc-1][0], SRC[isrc-1][1], topoCC[indA]],[mesh.vectorCCx.max(), mesh.vectorCCy.max(), topoCC[-1]]) + else: + indA = Utils.closestPoints(mesh2D, [SRC[isrc-1][0], SRC[isrc-1][1]]) + indB = Utils.closestPoints(mesh2D, [SRC[isrc-1][2], SRC[isrc-1][3]]) + tx = DCIP.SrcDipole([rx], [SRC[isrc-1][0], SRC[isrc-1][1], topoCC[indA]],[SRC[isrc-1][2], SRC[isrc-1][3], topoCC[indB]]) + srcLists.append(tx) + SRC.append(temp) + isrc += 1 + elif len(temp) == 7: # SRC: X, Y and Z are provided + SRC.append(temp) + isrc += 1 + elif len(temp) == 6: # + DATA_temp.append(np.r_[isrc, np.asarray(temp)]) + elif len(temp) > 7: + DATA_temp.append(np.r_[isrc, np.asarray(temp)]) + + DATA.append(np.asarray(DATA_temp)) + DATA_temp = [] + indM = Utils.closestPoints(mesh2D, DATA[isrc-1][:,1:3]) + indN = Utils.closestPoints(mesh2D, DATA[isrc-1][:,3:5]) + rx = DCIP.RxDipole(np.c_[DATA[isrc-1][:,1:3], topoCC[indM]], np.c_[DATA[isrc-1][:,3:5], topoCC[indN]]) + temp = np.asarray(temp) + if [SRC[isrc-1][0], SRC[isrc-1][1]] == [SRC[isrc-1][2], SRC[isrc-1][3]]: + indA = Utils.closestPoints(mesh2D, [SRC[isrc-1][0], SRC[isrc-1][1]]) + tx = DCIP.SrcDipole([rx], [SRC[isrc-1][0], SRC[isrc-1][1], topoCC[indA]],[mesh.vectorCCx.max(), mesh.vectorCCy.max(), topoCC[-1]]) + else: + indA = Utils.closestPoints(mesh2D, [SRC[isrc-1][0], SRC[isrc-1][1]]) + indB = Utils.closestPoints(mesh2D, [SRC[isrc-1][2], SRC[isrc-1][3]]) + tx = DCIP.SrcDipole([rx], [SRC[isrc-1][0], SRC[isrc-1][1], topoCC[indA]],[SRC[isrc-1][2], SRC[isrc-1][3], topoCC[indB]]) + srcLists.append(tx) + text_file.close() + survey = DCIP.SurveyDC(srcLists) + + # Do we need this? + SRC = np.asarray(SRC) + DATA = np.vstack(DATA) + survey.dobs = np.vstack(DATA)[:,-2] + + + return {'DCsurvey':survey, 'airind':airind, 'topoCC':topoCC, 'SRC':SRC} + +def readUBC_DC2DModel(fileName): + """ + Read UBC GIF 2DTensor model and generate 2D Tensor model in simpeg + + Input: + :param fileName, path to the UBC GIF 2D model file + + Output: + :param SimPEG TensorMesh 2D object + :return + + Created on Thu Nov 12 13:14:10 2015 + + @author: dominiquef + + """ + from SimPEG import np, mkvc + + # Open fileand skip header... assume that we know the mesh already + obsfile = np.genfromtxt(fileName,delimiter=' \n',dtype=np.str,comments='!') + + dim = np.array(obsfile[0].split(),dtype=float) + + temp = np.array(obsfile[1].split(),dtype=float) + + if len(temp) > 1: + model = np.zeros(dim) + + for ii in range(len(obsfile)-1): + mm = np.array(obsfile[ii+1].split(),dtype=float) + model[:,ii] = mm + + model = model[:,::-1] + + else: + + if len(obsfile[1:])==1: + mm = np.array(obsfile[1:].split(),dtype=float) + + else: + mm = np.array(obsfile[1:],dtype=float) + + # Permute the second dimension to flip the order + model = mm.reshape(dim[1],dim[0]) + + model = model[::-1,:] + model = np.transpose(model, (1, 0)) + + model = mkvc(model) + + + return model + +def plot_pseudoSection(DCsurvey, axs, stype): + """ + Read list of 2D tx-rx location and plot a speudo-section of apparent + resistivity. + + Assumes flat topo for now... + + Input: + :param d2D, z0 + :switch stype -> Either 'pdp' (pole-dipole) | 'dpdp' (dipole-dipole) + + Output: + :figure scatter plot overlayed on image + + Edited Feb 17th, 2016 + + @author: dominiquef + + """ + from SimPEG import np + from scipy.interpolate import griddata + import pylab as plt + + # Set depth to 0 for now + z0 = 0. + + # Pre-allocate + midx = [] + midz = [] + rho = [] + count = 0 # Counter for data + for ii in range(DCsurvey.nSrc): + + Tx = DCsurvey.srcList[ii].loc + Rx = DCsurvey.srcList[ii].rxList[0].locs + + nD = DCsurvey.srcList[ii].rxList[0].nD + + data = DCsurvey.dobs[count:count+nD] + count += nD + + # Get distances between each poles A-B-M-N + MA = np.abs(Tx[0][0] - Rx[0][:,0]) + MB = np.abs(Tx[1][0] - Rx[0][:,0]) + NB = np.abs(Tx[1][0] - Rx[1][:,0]) + NA = np.abs(Tx[0][0] - Rx[1][:,0]) + MN = np.abs(Rx[1][:,0] - Rx[0][:,0]) + + # Create mid-point location + Cmid = (Tx[0][0] + Tx[1][0])/2 + Pmid = (Rx[0][:,0] + Rx[1][:,0])/2 + + # Compute pant leg of apparent rho + if stype == 'pdp': + leg = data * 2*np.pi * MA * ( MA + MN ) / MN + + leg = np.log10(abs(1/leg)) + + elif stype == 'dpdp': + leg = data * 2*np.pi / ( 1/MA - 1/MB - 1/NB + 1/NA ) + + + midx = np.hstack([midx, ( Cmid + Pmid )/2 ]) + midz = np.hstack([midz, -np.abs(Cmid-Pmid)/2 + z0 ]) + rho = np.hstack([rho,leg]) + + + ax = axs + + # Grid points + grid_x, grid_z = np.mgrid[np.min(midx):np.max(midx), np.min(midz):np.max(midz)] + grid_rho = griddata(np.c_[midx,midz], rho.T, (grid_x, grid_z), method='linear') + + + plt.imshow(grid_rho.T, extent = (np.min(midx),np.max(midx),np.min(midz),np.max(midz)), origin='lower', alpha=0.8, vmin = np.min(rho), vmax = np.max(rho)) + cbar = plt.colorbar(format = '%.2f',fraction=0.04,orientation="horizontal") + + cmin,cmax = cbar.get_clim() + ticks = np.linspace(cmin,cmax,3) + cbar.set_ticks(ticks) + + # Plot apparent resistivity + plt.scatter(midx,midz,s=50,c=rho.T) + + ax.set_xticklabels([]) + + ax.set_ylabel('Z') + ax.yaxis.tick_right() + ax.yaxis.set_label_position('right') + plt.gca().set_aspect('equal', adjustable='box') + + + return ax + +def gen_DCIPsurvey(endl, mesh, stype, a, b, n): + """ + Load in endpoints and survey specifications to generate Tx, Rx location + stations. + + Assumes flat topo for now... + + Input: + :param endl -> input endpoints [x1, y1, z1, x2, y2, z2] + :object mesh -> SimPEG mesh object + :switch stype -> "dpdp" (dipole-dipole) | "pdp" (pole-dipole) | 'gradient' + : param a, n -> pole seperation, number of rx dipoles per tx + + Output: + :param Tx, Rx -> List objects for each tx location + Lines: P1x, P1y, P1z, P2x, P2y, P2z + + Created on Wed December 9th, 2015 + + @author: dominiquef + !! Require clean up to deal with DCsurvey + """ + + from SimPEG import np + + def xy_2_r(x1,x2,y1,y2): + r = np.sqrt( np.sum((x2 - x1)**2 + (y2 - y1)**2) ) + return r + + ## Evenly distribute electrodes and put on surface + # Mesure survey length and direction + dl_len = xy_2_r(endl[0,0],endl[1,0],endl[0,1],endl[1,1]) + + dl_x = ( endl[1,0] - endl[0,0] ) / dl_len + dl_y = ( endl[1,1] - endl[0,1] ) / dl_len + + nstn = np.floor( dl_len / a ) + + # Compute discrete pole location along line + stn_x = endl[0,0] + np.array(range(int(nstn)))*dl_x*a + stn_y = endl[0,1] + np.array(range(int(nstn)))*dl_y*a + + # Create line of P1 locations + M = np.c_[stn_x, stn_y, np.ones(nstn).T*mesh.vectorNz[-1]] + + # Create line of P2 locations + N = np.c_[stn_x+a*dl_x, stn_y+a*dl_y, np.ones(nstn).T*mesh.vectorNz[-1]] + + ## Build list of Tx-Rx locations depending on survey type + # Dipole-dipole: Moving tx with [a] spacing -> [AB a MN1 a MN2 ... a MNn] + # Pole-dipole: Moving pole on one end -> [A a MN1 a MN2 ... MNn a B] + Tx = [] + Rx = [] + SrcList = [] + + + if stype != 'gradient': + + for ii in range(0, int(nstn)-1): + + + if stype == 'dpdp': + tx = np.c_[M[ii,:],N[ii,:]] + elif stype == 'pdp': + tx = np.c_[M[ii,:],M[ii,:]] + + # Rx.append(np.c_[M[ii+1:indx,:],N[ii+1:indx,:]]) + + # Current elctrode seperation + AB = xy_2_r(tx[0,1],endl[1,0],tx[1,1],endl[1,1]) + + # Number of receivers to fit + nstn = np.min([np.floor( (AB - b) / a ) , n]) + + # Check if there is enough space, else break the loop + if nstn <= 0: + continue + + # Compute discrete pole location along line + stn_x = N[ii,0] + dl_x*b + np.array(range(int(nstn)))*dl_x*a + stn_y = N[ii,1] + dl_y*b + np.array(range(int(nstn)))*dl_y*a + + # Create receiver poles + # Create line of P1 locations + P1 = np.c_[stn_x, stn_y, np.ones(nstn).T*mesh.vectorNz[-1]] + + # Create line of P2 locations + P2 = np.c_[stn_x+a*dl_x, stn_y+a*dl_y, np.ones(nstn).T*mesh.vectorNz[-1]] + + Rx.append(np.c_[P1,P2]) + rxClass = DC.RxDipole(P1, P2) + Tx.append(tx) + if stype == 'dpdp': + srcClass = DC.SrcDipole([rxClass], M[ii,:],N[ii,:]) + elif stype == 'pdp': + srcClass = DC.SrcDipole([rxClass], M[ii,:],M[ii,:]) + SrcList.append(srcClass) + +#============================================================================== +# elif re.match(stype,'dpdp'): +# +# for ii in range(0, int(nstn)-2): +# +# indx = np.min([ii+n+1,nstn]) +# Tx.append(np.c_[M[ii,:],N[ii,:]]) +# Rx.append(np.c_[M[ii+2:indx,:],N[ii+2:indx,:]]) +#============================================================================== + + elif stype == 'gradient': + + # Gradient survey only requires Tx at end of line and creates a square + # grid of receivers at in the middle at a pre-set minimum distance + + Tx.append(np.c_[M[0,:],N[-1,:]]) + + # Get the edge limit of survey area + min_x = endl[0,0] + dl_x * b + min_y = endl[0,1] + dl_y * b + + max_x = endl[1,0] - dl_x * b + max_y = endl[1,1] - dl_y * b + + box_l = np.sqrt( (min_x - max_x)**2 + (min_y - max_y)**2 ) + box_w = box_l/2. + + nstn = np.floor( box_l / a ) + + # Compute discrete pole location along line + stn_x = min_x + np.array(range(int(nstn)))*dl_x*a + stn_y = min_y + np.array(range(int(nstn)))*dl_y*a + + # Define number of cross lines + nlin = int(np.floor( box_w / a )) + lind = range(-nlin,nlin+1) + + ngrad = nstn * len(lind) + + rx = np.zeros([ngrad,6]) + for ii in range( len(lind) ): + + # Move line in perpendicular direction by dipole spacing + lxx = stn_x - lind[ii]*a*dl_y + lyy = stn_y + lind[ii]*a*dl_x + + + M = np.c_[ lxx, lyy , np.ones(nstn).T*mesh.vectorNz[-1]] + N = np.c_[ lxx+a*dl_x, lyy+a*dl_y, np.ones(nstn).T*mesh.vectorNz[-1]] + + rx[(ii*nstn):((ii+1)*nstn),:] = np.c_[M,N] + + Rx.append(rx) + rxClass = DC.RxDipole(rx[:,:3], rx[:,3:]) + srcClass = DC.SrcDipole([rxClass], M[0,:], N[-1,:]) + SrcList.append(srcClass) + else: + print """stype must be either 'pdp', 'dpdp' or 'gradient'. """ + + survey = DC.SurveyDC(SrcList) + return survey, Tx, Rx + +def writeUBC_DCobs(fileName, DCsurvey, dtype, stype): + """ + Write UBC GIF DCIP 2D or 3D observation file + + Input: + :string fileName -> including path where the file is written out + :DCsurvey -> DC survey class object + :string dtype -> either '2D' | '3D' + :string stype -> either 'SURFACE' | 'GENERAL' + + Output: + :param UBC2D-Data file + :return + + Last edit: February 16th, 2016 + + @author: dominiquef + + """ + from SimPEG import mkvc + + assert (dtype=='2D') | (dtype=='3D'), "Data must be either '2D' | '3D'" + assert (stype=='SURFACE') | (stype=='GENERAL') | (stype=='SIMPLE'), "Data must be either 'SURFACE' | 'GENERAL' | 'SIMPLE'" + + fid = open(fileName,'w') + fid.write('! ' + stype + ' FORMAT\n') + + count = 0 + + for ii in range(DCsurvey.nSrc): + + tx = np.c_[DCsurvey.srcList[ii].loc] + + rx = DCsurvey.srcList[ii].rxList[0].locs + + nD = DCsurvey.srcList[ii].nD + + M = rx[0] + N = rx[1] + + # Adapt source-receiver location for dtype and stype + if dtype=='2D': + + if stype == 'SIMPLE': + + #fid.writelines("%e " % ii for ii in mkvc(tx[0,:])) + A = np.repeat(tx[0,0],M.shape[0],axis=0) + B = np.repeat(tx[0,1],M.shape[0],axis=0) + M = M[:,0] + N = N[:,0] + + np.savetxt(fid, np.c_[A, B, M, N , DCsurvey.dobs[count:count+nD], DCsurvey.std[count:count+nD] ], fmt='%e',delimiter=' ',newline='\n') + + + else: + + if stype == 'SURFACE': + + fid.writelines("%e " % ii for ii in mkvc(tx[0,:])) + M = M[:,0] + N = N[:,0] + + if stype == 'GENERAL': + + fid.writelines("%e " % ii for ii in mkvc(tx[::2,:])) + M = M[:,0::2] + N = N[:,0::2] + + fid.write('%i\n'% nD) + np.savetxt(fid, np.c_[ M, N , DCsurvey.dobs[count:count+nD], DCsurvey.std[count:count+nD] ], fmt='%e',delimiter=' ',newline='\n') + + if dtype=='3D': + + if stype == 'SURFACE': + + fid.writelines("%e " % ii for ii in mkvc(tx[0:2,:])) + M = M[:,0:2] + N = N[:,0:2] + + if stype == 'GENERAL': + + fid.writelines("%e " % ii for ii in mkvc(tx)) + + fid.write('%i\n'% nD) + np.savetxt(fid, np.c_[ M, N , DCsurvey.dobs[count:count+nD], DCsurvey.std[count:count+nD] ], fmt='%e',delimiter=' ',newline='\n') + + count += nD + + fid.close() + +def convertObs_DC3D_to_2D(DCsurvey,lineID): + """ + Read DC survey and data and change + coordinate system to distance along line assuming + all data is acquired along line. + First transmitter pole is assumed to be at the origin + + Assumes flat topo for now... + + Input: + :param Tx, Rx + + Output: + :figure Tx2d, Rx2d + + Edited Feb 17th, 2016 + + @author: dominiquef + + """ + from SimPEG import np + + def stn_id(v0,v1,r): + """ + Compute station ID along line + """ + + dl = int(v0.dot(v1)) * r + + return dl + + srcLists = [] + + srcMat = getSrc_locs(DCsurvey) + + # Find all unique line id + uniqueID = np.unique(lineID) + + for jj in range(len(uniqueID)): + + indx = np.where(lineID==uniqueID[jj])[0] + + # Find origin of survey + r = 1e+8 # Initialize to some large number + + Tx = srcMat[indx] + + x0 = Tx[0][0,0:2] # Define station zero along line + + vecTx, r1 = r_unit(x0,Tx[-1][1,0:2]) + + for ii in range(len(indx)): + + # Get all receivers + Rx = DCsurvey.srcList[indx[ii]].rxList[0].locs + nrx = Rx[0].shape[0] + + # Find A electrode along line + vec, r = r_unit(x0,Tx[ii][0,0:2]) + A = stn_id(vecTx,vec,r) + + # Find B electrode along line + vec, r = r_unit(x0,Tx[ii][1,0:2]) + B = stn_id(vecTx,vec,r) + + M = np.zeros(nrx) + N = np.zeros(nrx) + for kk in range(nrx): + + # Find all M electrodes along line + vec, r = r_unit(x0,Rx[0][kk,0:2]) + M[kk] = stn_id(vecTx,vec,r) + + # Find all N electrodes along line + vec, r = r_unit(x0,Rx[1][kk,0:2]) + N[kk] = stn_id(vecTx,vec,r) + + Rx = DC.RxDipole(np.c_[M,np.zeros(nrx),Rx[0][:,2]],np.c_[N,np.zeros(nrx),Rx[1][:,2]]) + + srcLists.append( DC.SrcDipole( [Rx], np.asarray([A,0,Tx[ii][0,2]]),np.asarray([B,0,Tx[ii][1,2]]) ) ) + + + DCsurvey2D = DC.SurveyDC(srcLists) + + DCsurvey2D.dobs = np.asarray(DCsurvey.dobs) + DCsurvey2D.std = np.asarray(DCsurvey.std) + + return DCsurvey2D + +def readUBC_DC3Dobs(fileName): + """ + Read UBC GIF DCIP 3D observation file and generate arrays for tx-rx location + + Input: + :param fileName, path to the UBC GIF 3D obs file + + Output: + :param rx, tx, d, wd + :return + + Created on Mon December 7th, 2015 + + @author: dominiquef + + """ + + # Load file + obsfile = np.genfromtxt(fileName,delimiter=' \n',dtype=np.str,comments='!') + + # Pre-allocate + srcLists = [] + Rx = [] + d = [] + wd = [] + zflag = True # Flag for z value provided + + # Countdown for number of obs/tx + count = 0 + for ii in range(obsfile.shape[0]): + + if not obsfile[ii]: + continue + + # First line is transmitter with number of receivers + if count==0: + + temp = (np.fromstring(obsfile[ii], dtype=float,sep=' ').T) + count = int(temp[-1]) + + # Check if z value is provided, if False -> nan + if len(temp)==5: + tx = np.r_[temp[0:2],np.nan,temp[0:2],np.nan] + zflag = False + + else: + tx = temp[:-1] + + rx = [] + continue + + temp = np.fromstring(obsfile[ii], dtype=float,sep=' ') + + if zflag: + + rx.append(temp[:-2]) + # Check if there is data with the location + if len(temp)==8: + d.append(temp[-2]) + wd.append(temp[-1]) + + else: + rx.append(np.r_[temp[0:2],np.nan,temp[0:2],np.nan] ) + # Check if there is data with the location + if len(temp)==6: + d.append(temp[-2]) + wd.append(temp[-1]) + + count = count -1 + + # Reach the end of transmitter block + if count == 0: + rx = np.asarray(rx) + Rx = DC.RxDipole(rx[:,:3],rx[:,3:]) + srcLists.append( DC.SrcDipole( [Rx], tx[:3],tx[3:]) ) + + # Create survey class + survey = DC.SurveyDC(srcLists) + + survey.dobs = np.asarray(d) + survey.std = np.asarray(wd) + + return {'DCsurvey':survey} + +def readUBC_DC2Dobs(fileName): + """ + Read UBC GIF 2D observation file and generate arrays for tx-rx location + + Input: + :param fileName, path to the UBC GIF 2D model file + + Output: + :param rx, tx + :return + + Created on Thu Nov 12 13:14:10 2015 + + @author: dominiquef + + """ + + from SimPEG import np + + # Load file + obsfile = np.genfromtxt(fileName,delimiter=' \n',dtype=np.str,comments='!') + + # Check first line and figure out if 2D or 3D file format + line = np.array(obsfile[0].split(),dtype=float) + + tx_A = [] + tx_B = [] + rx_M = [] + rx_N = [] + d = [] + wd = [] + + for ii in range(obsfile.shape[0]): + + # If len==3, then simple format where tx-rx is listed on each line + if len(line) == 4: + + temp = np.fromstring(obsfile[ii], dtype=float,sep=' ') + tx_A = np.hstack((tx_A,temp[0])) + tx_B = np.hstack((tx_B,temp[1])) + rx_M = np.hstack((rx_M,temp[2])) + rx_N = np.hstack((rx_N,temp[3])) + + + rx = np.transpose(np.array((rx_M,rx_N))) + tx = np.transpose(np.array((tx_A,tx_B))) + + return tx, rx, d, wd + +def readUBC_DC2DMesh(fileName): + """ + Read UBC GIF 2DTensor mesh and generate 2D Tensor mesh in simpeg + + Input: + :param fileName, path to the UBC GIF mesh file + + Output: + :param SimPEG TensorMesh 2D object + :return + + Created on Thu Nov 12 13:14:10 2015 + + @author: dominiquef + + """ + + from SimPEG import np + # Open file + fopen = open(fileName,'r') + + # Read down the file and unpack dx vector + def unpackdx(fid,nrows): + for ii in range(nrows): + + line = fid.readline() + var = np.array(line.split(),dtype=float) + + if ii==0: + x0= var[0] + xvec = np.ones(int(var[2])) * (var[1] - var[0]) / int(var[2]) + xend = var[1] + + else: + xvec = np.hstack((xvec,np.ones(int(var[1])) * (var[0] - xend) / int(var[1]))) + xend = var[0] + + return x0, xvec + + #%% Start with dx block + # First line specifies the number of rows for x-cells + line = fopen.readline() + nl = np.array(line.split(),dtype=float) + + [x0, dx] = unpackdx(fopen,nl) + + + #%% Move down the file until reaching the z-block + line = fopen.readline() + if not line: + line = fopen.readline() + + #%% End with dz block + # First line specifies the number of rows for z-cells + line = fopen.readline() + nl = np.array(line.split(),dtype=float) + + [z0, dz] = unpackdx(fopen,nl) + + # Flip z0 to be the bottom of the mesh for SimPEG + z0 = z0 - sum(dz) + dz = dz[::-1] + #%% Make the mesh using SimPEG + + from SimPEG import Mesh + tensMsh = Mesh.TensorMesh([dx,dz],(x0, z0)) + return tensMsh + +def xy_2_lineID(DCsurvey): + """ + Read DC survey class and append line ID. + Assumes that the locations are listed in the order + they were collected. May need to generalize for random + point locations, but will be more expensive + + Input: + :param DCdict Vectors of station location + + Output: + :param LineID Vector of integers + :return + + Created on Thu Feb 11, 2015 + + @author: dominiquef + + """ + + # Compute unit vector between two points + nstn = DCsurvey.nSrc + + # Pre-allocate space + lineID = np.zeros(nstn) + + linenum = 0 + indx = 0 + + for ii in range(nstn): + + if ii == 0: + + A = DCsurvey.srcList[ii].loc[0] + B = DCsurvey.srcList[ii].loc[1] + + xout = np.mean([A[0:2],B[0:2]], axis = 0) + + xy0 = A[:2] + xym = xout + + # Deal with replicate pole location + if np.all(xy0==xym): + + xym[0] = xym[0] + 1e-3 + + continue + + A = DCsurvey.srcList[ii].loc[0] + B = DCsurvey.srcList[ii].loc[1] + + xin = np.mean([A[0:2],B[0:2]], axis = 0) + + # Compute vector between neighbours + vec1, r1 = r_unit(xout,xin) + + # Compute vector between current stn and mid-point + vec2, r2 = r_unit(xym,xin) + + # Compute vector between current stn and start line + vec3, r3 = r_unit(xy0,xin) + + # Compute vector between mid-point and start line + vec4, r4 = r_unit(xym,xy0) + + # Compute dot product + ang1 = np.abs(vec1.dot(vec2)) + ang2 = np.abs(vec3.dot(vec4)) + + # If the angles are smaller then 45d, than next point is on a new line + if ((ang1 < np.cos(np.pi/4.)) | (ang2 < np.cos(np.pi/4.))) & (np.all(np.r_[r1,r2,r3,r4] > 0)): + + # Re-initiate start and mid-point location + xy0 = A[:2] + xym = xin + + # Deal with replicate pole location + if np.all(xy0==xym): + + xym[0] = xym[0] + 1e-3 + + linenum += 1 + indx = ii + + else: + xym = np.mean([xy0,xin], axis = 0) + + lineID[ii] = linenum + xout = xin + + return lineID + +def r_unit(p1,p2): + """ + r_unit(x,y) : Function computes the unit vector + between two points with coordinates p1(x1,y1) and p2(x2,y2) + + """ + + assert len(p1)==len(p2), 'locs must be the same shape.' + + dx = [] + for ii in range(len(p1)): + dx.append((p2[ii] - p1[ii])) + + # Compute length of vector + r = np.linalg.norm(np.asarray(dx)) + + + if r!=0: + vec = dx/r + + else: + vec = np.zeros(len(p1)) + + return vec, r + +def getSrc_locs(DCsurvey): + """ + + + """ + + srcMat = np.zeros((DCsurvey.nSrc,2,3)) + for ii in range(DCsurvey.nSrc): + print np.asarray(DCsurvey.srcList[ii].loc).shape + srcMat[ii,:,:] = np.asarray(DCsurvey.srcList[ii].loc) + + return srcMat diff --git a/SimPEG/DCIP/Utils.py b/SimPEG/DCIP/Utils.py new file mode 100644 index 00000000..702a2333 --- /dev/null +++ b/SimPEG/DCIP/Utils.py @@ -0,0 +1,38 @@ +import numpy as np + +def WennerSrcList(nElecs, aSpacing, in2D=False, plotIt=False): + + import SimPEG.DCIP as DC + + elocs = np.arange(0,aSpacing*nElecs,aSpacing) + elocs -= (nElecs*aSpacing - aSpacing)/2 + space = 1 + WENNER = np.zeros((0,),dtype=int) + for ii in range(nElecs): + for jj in range(nElecs): + test = np.r_[jj,jj+space,jj+space*2,jj+space*3] + if np.any(test >= nElecs): + break + WENNER = np.r_[WENNER, test] + space += 1 + WENNER = WENNER.reshape((-1,4)) + + + if plotIt: + for i, s in enumerate('rbkg'): + plt.plot(elocs[WENNER[:,i]],s+'.') + plt.show() + + # Create sources and receivers + i = 0 + if in2D: + getLoc = lambda ii, abmn: np.r_[elocs[WENNER[ii,abmn]],0] + else: + getLoc = lambda ii, abmn: np.r_[elocs[WENNER[ii,abmn]],0, 0] + srcList = [] + for i in range(WENNER.shape[0]): + rx = DC.RxDipole(getLoc(i,1),getLoc(i,2)) + src = DC.SrcDipole([rx], getLoc(i,0),getLoc(i,3)) + srcList += [src] + + return srcList diff --git a/SimPEG/DCIP/__init__.py b/SimPEG/DCIP/__init__.py new file mode 100644 index 00000000..08d1fe12 --- /dev/null +++ b/SimPEG/DCIP/__init__.py @@ -0,0 +1,4 @@ +from BaseDC import * +from BaseIP import * +from DCIPUtils import * +import Utils diff --git a/SimPEG/EM/TDEM/BaseTDEM.py b/SimPEG/EM/TDEM/BaseTDEM.py index 1ad44889..0da22072 100644 --- a/SimPEG/EM/TDEM/BaseTDEM.py +++ b/SimPEG/EM/TDEM/BaseTDEM.py @@ -27,6 +27,7 @@ class FieldsTDEM(Problem.TimeFields): else: e = np.zeros((nE,nSrc)) # if nSrc == 1 else (nE, nSrc)) u = np.concatenate((u, b, e)) + return Utils.mkvc(u,nSrc) diff --git a/SimPEG/Examples/DC_Analytic_Dipole.py b/SimPEG/Examples/DC_Analytic_Dipole.py new file mode 100644 index 00000000..aed5eed4 --- /dev/null +++ b/SimPEG/Examples/DC_Analytic_Dipole.py @@ -0,0 +1,68 @@ +from SimPEG import * +import SimPEG.DCIP as DC + +def run(plotIt=False): + cs = 25. + hx = [(cs,7, -1.3),(cs,21),(cs,7, 1.3)] + hy = [(cs,7, -1.3),(cs,21),(cs,7, 1.3)] + hz = [(cs,7, -1.3),(cs,20)] + mesh = Mesh.TensorMesh([hx, hy, hz], 'CCN') + sighalf = 1e-2 + sigma = np.ones(mesh.nC)*sighalf + xtemp = np.linspace(-150, 150, 21) + ytemp = np.linspace(-150, 150, 21) + xyz_rxP = Utils.ndgrid(xtemp-10., ytemp, np.r_[0.]) + xyz_rxN = Utils.ndgrid(xtemp+10., ytemp, np.r_[0.]) + xyz_rxM = Utils.ndgrid(xtemp, ytemp, np.r_[0.]) + + # if plotIt: + # fig, ax = plt.subplots(1,1, figsize = (5,5)) + # mesh.plotSlice(sigma, grid=True, ax = ax) + # ax.plot(xyz_rxP[:,0],xyz_rxP[:,1], 'w.') + # ax.plot(xyz_rxN[:,0],xyz_rxN[:,1], 'r.', ms = 3) + + rx = DC.RxDipole(xyz_rxP, xyz_rxN) + src = DC.SrcDipole([rx], [-200, 0, -12.5], [+200, 0, -12.5]) + survey = DC.SurveyDC([src]) + problem = DC.ProblemDC_CC(mesh) + problem.pair(survey) + try: + from pymatsolver import MumpsSolver + problem.Solver = MumpsSolver + except Exception, e: + pass + data = survey.dpred(sigma) + + def DChalf(srclocP, srclocN, rxloc, sigma, I=1.): + rp = (srclocP.reshape([1,-1])).repeat(rxloc.shape[0], axis = 0) + rn = (srclocN.reshape([1,-1])).repeat(rxloc.shape[0], axis = 0) + rP = np.sqrt(((rxloc-rp)**2).sum(axis=1)) + rN = np.sqrt(((rxloc-rn)**2).sum(axis=1)) + return I/(sigma*2.*np.pi)*(1/rP-1/rN) + + data_anaP = DChalf(np.r_[-200, 0, 0.],np.r_[+200, 0, 0.], xyz_rxP, sighalf) + data_anaN = DChalf(np.r_[-200, 0, 0.],np.r_[+200, 0, 0.], xyz_rxN, sighalf) + data_ana = data_anaP-data_anaN + Data_ana = data_ana.reshape((21, 21), order = 'F') + Data = data.reshape((21, 21), order = 'F') + X = xyz_rxM[:,0].reshape((21, 21), order = 'F') + Y = xyz_rxM[:,1].reshape((21, 21), order = 'F') + + if plotIt: + import matplotlib.pyplot as plt + fig, ax = plt.subplots(1,2, figsize = (12, 5)) + vmin = np.r_[data, data_ana].min() + vmax = np.r_[data, data_ana].max() + dat1 = ax[1].contourf(X, Y, Data, 60, vmin = vmin, vmax = vmax) + dat0 = ax[0].contourf(X, Y, Data_ana, 60, vmin = vmin, vmax = vmax) + cb0 = plt.colorbar(dat1, orientation = 'horizontal', ax = ax[0]) + cb1 = plt.colorbar(dat1, orientation = 'horizontal', ax = ax[1]) + ax[1].set_title('Analytic') + ax[0].set_title('Computed') + plt.show() + + return np.linalg.norm(data-data_ana)/np.linalg.norm(data_ana) + + +if __name__ == '__main__': + print run(plotIt=True) diff --git a/SimPEG/Examples/DC_Forward_PseudoSection.py b/SimPEG/Examples/DC_Forward_PseudoSection.py new file mode 100644 index 00000000..2f198238 --- /dev/null +++ b/SimPEG/Examples/DC_Forward_PseudoSection.py @@ -0,0 +1,187 @@ +from SimPEG import Mesh, Utils, np, sp +import SimPEG.DCIP as DC +import time + +def run(loc=None, sig=None, radi=None, param=None, stype='dpdp', plotIt=True): + """ + DC Forward Simulation + ===================== + + Forward model conductive spheres in a half-space and plot a pseudo-section + + Created by @fourndo on Mon Feb 01 19:28:06 2016 + + """ + + assert stype in ['pdp', 'dpdp'], "Source type (stype) must be pdp or dpdp (pole dipole or dipole dipole)" + + + if loc is None: + loc = np.c_[[-50.,0.,-50.],[50.,0.,-50.]] + if sig is None: + sig = np.r_[1e-2,1e-1,1e-3] + if radi is None: + radi = np.r_[25.,25.] + if param is None: + param = np.r_[30.,30.,5] + + + # First we need to create a mesh and a model. + + # This is our mesh + dx = 5. + + hxind = [(dx,15,-1.3), (dx, 75), (dx,15,1.3)] + hyind = [(dx,15,-1.3), (dx, 10), (dx,15,1.3)] + hzind = [(dx,15,-1.3),(dx, 15)] + + mesh = Mesh.TensorMesh([hxind, hyind, hzind], 'CCN') + + + # Set background conductivity + model = np.ones(mesh.nC) * sig[0] + + # First anomaly + ind = Utils.ModelBuilder.getIndicesSphere(loc[:,0],radi[0],mesh.gridCC) + model[ind] = sig[1] + + # Second anomaly + ind = Utils.ModelBuilder.getIndicesSphere(loc[:,1],radi[1],mesh.gridCC) + model[ind] = sig[2] + + # Get index of the center + indy = int(mesh.nCy/2) + + + # Plot the model for reference + # Define core mesh extent + xlim = 200 + zlim = 125 + + # Specify the survey type: "pdp" | "dpdp" + + + # Then specify the end points of the survey. Let's keep it simple for now and survey above the anomalies, top of the mesh + ends = [(-175,0),(175,0)] + ends = np.c_[np.asarray(ends),np.ones(2).T*mesh.vectorNz[-1]] + + # Snap the endpoints to the grid. Easier to create 2D section. + indx = Utils.closestPoints(mesh, ends ) + locs = np.c_[mesh.gridCC[indx,0],mesh.gridCC[indx,1],np.ones(2).T*mesh.vectorNz[-1]] + + # We will handle the geometry of the survey for you and create all the combination of tx-rx along line + # [Tx, Rx] = DC.gen_DCIPsurvey(locs, mesh, stype, param[0], param[1], param[2]) + survey, Tx, Rx = DC.gen_DCIPsurvey(locs, mesh, stype, param[0], param[1], param[2]) + + # Define some global geometry + dl_len = np.sqrt( np.sum((locs[0,:] - locs[1,:])**2) ) + dl_x = ( Tx[-1][0,1] - Tx[0][0,0] ) / dl_len + dl_y = ( Tx[-1][1,1] - Tx[0][1,0] ) / dl_len + azm = np.arctan(dl_y/dl_x) + + #Set boundary conditions + mesh.setCellGradBC('neumann') + + # Define the differential operators needed for the DC problem + Div = mesh.faceDiv + Grad = mesh.cellGrad + Msig = Utils.sdiag(1./(mesh.aveF2CC.T*(1./model))) + + A = Div*Msig*Grad + + # Change one corner to deal with nullspace + A[0,0] = 1 + A = sp.csc_matrix(A) + + # We will solve the system iteratively, so a pre-conditioner is helpful + # This is simply a Jacobi preconditioner (inverse of the main diagonal) + dA = A.diagonal() + P = sp.spdiags(1/dA,0,A.shape[0],A.shape[0]) + + # Now we can solve the system for all the transmitters + # We want to store the data + data = [] + + # There is probably a more elegant way to do this, but we can just for-loop through the transmitters + for ii in range(len(Tx)): + + start_time = time.time() # Let's time the calculations + + #print("Transmitter %i / %i\r" % (ii+1,len(Tx))) + + # Select dipole locations for receiver + rxloc_M = np.asarray(Rx[ii][:,0:3]) + rxloc_N = np.asarray(Rx[ii][:,3:]) + + + # For usual cases "dpdp" or "gradient" + if stype == 'pdp': + # Create an "inifinity" pole + tx = np.squeeze(Tx[ii][:,0:1]) + tinf = tx + np.array([dl_x,dl_y,0])*dl_len*2 + inds = Utils.closestPoints(mesh, np.c_[tx,tinf].T) + RHS = mesh.getInterpolationMat(np.asarray(Tx[ii]).T, 'CC').T*( [-1] / mesh.vol[inds] ) + else: + inds = Utils.closestPoints(mesh, np.asarray(Tx[ii]).T ) + RHS = mesh.getInterpolationMat(np.asarray(Tx[ii]).T, 'CC').T*( [-1,1] / mesh.vol[inds] ) + + # Iterative Solve + Ainvb = sp.linalg.bicgstab(P*A,P*RHS, tol=1e-5) + + # We now have the potential everywhere + phi = Utils.mkvc(Ainvb[0]) + + # Solve for phi on pole locations + P1 = mesh.getInterpolationMat(rxloc_M, 'CC') + P2 = mesh.getInterpolationMat(rxloc_N, 'CC') + + # Compute the potential difference + dtemp = (P1*phi - P2*phi)*np.pi + + data.append( dtemp ) + print '\rTransmitter {0} of {1} -> Time:{2} sec'.format(ii,len(Tx),time.time()- start_time), + + print 'Transmitter {0} of {1}'.format(ii,len(Tx)) + print 'Forward completed' + + # Let's just convert the 3D format into 2D (distance along line) and plot + # [Tx2d, Rx2d] = DC.convertObs_DC3D_to_2D(survey, np.ones(survey.nSrc)) + survey2D = DC.convertObs_DC3D_to_2D(survey, np.ones(survey.nSrc)) + survey2D.dobs =np.hstack(data) + # Here is an example for the first tx-rx array + if plotIt: + import matplotlib.pyplot as plt + fig = plt.figure() + ax = plt.subplot(2,1,1, aspect='equal') + mesh.plotSlice(np.log10(model), ax =ax, normal = 'Y', ind = indy,grid=True) + ax.set_title('E-W section at '+str(mesh.vectorCCy[indy])+' m') + plt.gca().set_aspect('equal', adjustable='box') + + plt.scatter(Tx[0][0,:],Tx[0][2,:],s=40,c='g', marker='v') + plt.scatter(Rx[0][:,0::3],Rx[0][:,2::3],s=40,c='y') + plt.xlim([-xlim,xlim]) + plt.ylim([-zlim,mesh.vectorNz[-1]+dx]) + + + ax = plt.subplot(2,1,2, aspect='equal') + + # Plot the location of the spheres for reference + circle1=plt.Circle((loc[0,0]-Tx[0][0,0],loc[2,0]),radi[0],color='w',fill=False, lw=3) + circle2=plt.Circle((loc[0,1]-Tx[0][0,0],loc[2,1]),radi[1],color='k',fill=False, lw=3) + ax.add_artist(circle1) + ax.add_artist(circle2) + + # Add the speudo section + DC.plot_pseudoSection(survey2D,ax,stype) + + # plt.scatter(Tx2d[0][:],Tx[0][2,:],s=40,c='g', marker='v') + # plt.scatter(Rx2d[0][:],Rx[0][:,2::3],s=40,c='y') + # plt.plot(np.r_[Tx2d[0][0],Rx2d[-1][-1,-1]],np.ones(2)*mesh.vectorNz[-1], color='k') + plt.ylim([-zlim,mesh.vectorNz[-1]+dx]) + + plt.show() + + return fig, ax + +if __name__ == '__main__': + run() diff --git a/SimPEG/Examples/Inversion_Linear.py b/SimPEG/Examples/Inversion_Linear.py index 6d342a04..47d49c7f 100644 --- a/SimPEG/Examples/Inversion_Linear.py +++ b/SimPEG/Examples/Inversion_Linear.py @@ -10,28 +10,6 @@ def run(N=100, plotIt=True): """ - class LinearSurvey(Survey.BaseSurvey): - def eval(self, u): - return u - - class LinearProblem(Problem.BaseProblem): - - surveyPair = LinearSurvey - - def __init__(self, mesh, G, **kwargs): - Problem.BaseProblem.__init__(self, mesh, **kwargs) - self.G = G - - def fields(self, m, u=None): - return self.G.dot(m) - - def Jvec(self, m, v, u=None): - return self.G.dot(v) - - def Jtvec(self, m, v, u=None): - return self.G.T.dot(v) - - np.random.seed(1) mesh = Mesh.TensorMesh([N]) @@ -53,8 +31,8 @@ def run(N=100, plotIt=True): mtrue[mesh.vectorCCx > 0.45] = -0.5 mtrue[mesh.vectorCCx > 0.6] = 0 - prob = LinearProblem(mesh, G) - survey = LinearSurvey() + prob = Problem.LinearProblem(mesh, G) + survey = Survey.LinearSurvey() survey.pair(prob) survey.makeSyntheticData(mtrue, std=0.01) diff --git a/SimPEG/Examples/__init__.py b/SimPEG/Examples/__init__.py index dd35c0da..cce22296 100644 --- a/SimPEG/Examples/__init__.py +++ b/SimPEG/Examples/__init__.py @@ -1,6 +1,8 @@ # Run this file to add imports. ##### AUTOIMPORTS ##### +import DC_Analytic_Dipole +import DC_Forward_PseudoSection import EM_FDEM_1D_Inversion import EM_FDEM_Analytic_MagDipoleWholespace import EM_TDEM_1D_Inversion @@ -17,7 +19,7 @@ import Mesh_Tensor_Creation import MT_1D_ForwardAndInversion import MT_3D_Foward -__examples__ = ["EM_FDEM_1D_Inversion", "EM_FDEM_Analytic_MagDipoleWholespace", "EM_TDEM_1D_Inversion", "FLOW_Richards_1D_Celia1990", "Forward_BasicDirectCurrent", "Inversion_Linear", "Mesh_Basic_PlotImage", "Mesh_Basic_Types", "Mesh_Operators_CahnHilliard", "Mesh_QuadTree_Creation", "Mesh_QuadTree_FaceDiv", "Mesh_QuadTree_HangingNodes", "Mesh_Tensor_Creation", "MT_1D_ForwardAndInversion", "MT_3D_Foward"] +__examples__ = ["DC_Analytic_Dipole", "DC_Forward_PseudoSection", "EM_FDEM_1D_Inversion", "EM_FDEM_Analytic_MagDipoleWholespace", "EM_TDEM_1D_Inversion", "FLOW_Richards_1D_Celia1990", "Forward_BasicDirectCurrent", "Inversion_Linear", "Mesh_Basic_PlotImage", "Mesh_Basic_Types", "Mesh_Operators_CahnHilliard", "Mesh_QuadTree_Creation", "Mesh_QuadTree_FaceDiv", "Mesh_QuadTree_HangingNodes", "Mesh_Tensor_Creation", "MT_1D_ForwardAndInversion", "MT_3D_Foward"] ##### AUTOIMPORTS ##### diff --git a/SimPEG/Mesh/DiffOperators.py b/SimPEG/Mesh/DiffOperators.py index 00bf0259..793b2581 100644 --- a/SimPEG/Mesh/DiffOperators.py +++ b/SimPEG/Mesh/DiffOperators.py @@ -746,4 +746,3 @@ class DiffOperators(object): kron3(av(n[2]), speye(n[1]+1), av(n[0])), kron3(speye(n[2]+1), av(n[1]), av(n[0]))), format="csr") return self._aveN2F - diff --git a/SimPEG/Problem.py b/SimPEG/Problem.py index f24b57de..cd8a4aaa 100644 --- a/SimPEG/Problem.py +++ b/SimPEG/Problem.py @@ -213,5 +213,20 @@ class BaseTimeProblem(BaseProblem): if hasattr(self, '_timeMesh'): del self._timeMesh +class LinearProblem(BaseProblem): + surveyPair = Survey.LinearSurvey + + def __init__(self, mesh, G, **kwargs): + BaseProblem.__init__(self, mesh, **kwargs) + self.G = G + + def fields(self, m): + return self.G.dot(m) + + def Jvec(self, m, v, u=None): + return self.G.dot(v) + + def Jtvec(self, m, v, u=None): + return self.G.T.dot(v) diff --git a/SimPEG/Regularization.py b/SimPEG/Regularization.py index 677fba5c..5b41b91b 100644 --- a/SimPEG/Regularization.py +++ b/SimPEG/Regularization.py @@ -282,3 +282,243 @@ class Tikhonov(BaseRegularization): out = mD.T * ( self.W.T * r ) return out +# <<<<<<< HEAD + +# class Simple(BaseRegularization): +# """ +# Only for tensor mesh +# """ + +# smoothModel = True #: SMOOTH and SMOOTH_MOD_DIF options +# alpha_s = Utils.dependentProperty('_alpha_s', 1.0, ['_W', '_Ws'], "Smallness weight") +# alpha_x = Utils.dependentProperty('_alpha_x', 1.0, ['_W', '_Wx'], "Weight for the first derivative in the x direction") +# alpha_y = Utils.dependentProperty('_alpha_y', 1.0, ['_W', '_Wy'], "Weight for the first derivative in the y direction") +# alpha_z = Utils.dependentProperty('_alpha_z', 1.0, ['_W', '_Wz'], "Weight for the first derivative in the z direction") +# alpha_xx = Utils.dependentProperty('_alpha_xx', 0.0, ['_W', '_Wxx'], "Weight for the second derivative in the x direction") +# alpha_yy = Utils.dependentProperty('_alpha_yy', 0.0, ['_W', '_Wyy'], "Weight for the second derivative in the y direction") +# alpha_zz = Utils.dependentProperty('_alpha_zz', 0.0, ['_W', '_Wzz'], "Weight for the second derivative in the z direction") + +# def __init__(self, mesh, mapping=None, **kwargs): +# BaseRegularization.__init__(self, mesh, mapping=mapping, **kwargs) + + + +# @property +# def Ws(self): +# """Regularization matrix Ws""" +# if getattr(self,'_Ws', None) is None: +# self._Ws = Utils.sdiag((self.mesh.vol*self.alpha_s)**0.5) +# return self._Ws + +# @property +# def Wx(self): +# """Regularization matrix Wx""" +# if getattr(self, '_Wx', None) is None: +# self._Wx = Utils.sdiag((self.mesh.vol*self.alpha_x)**0.5)*self.mesh.unitCellGradx +# return self._Wx + +# @property +# def Wy(self): +# """Regularization matrix Wy""" +# if getattr(self, '_Wy', None) is None: +# self._Wy = Utils.sdiag((self.mesh.vol*self.alpha_y)**0.5)*self.mesh.unitCellGrady +# return self._Wy + +# @property +# def Wz(self): +# """Regularization matrix Wz""" +# if getattr(self, '_Wz', None) is None: +# self._Wz = Utils.sdiag((self.mesh.vol*self.alpha_z)**0.5)*self.mesh.unitCellGradz +# return self._Wz + +# @property +# def Wxx(self): +# """Regularization matrix Wxx""" +# if getattr(self, '_Wxx', None) is None: +# self._Wxx = Utils.sdiag((self.mesh.vol*self.alpha_xx)**0.5)*self.mesh.faceDivx*self.mesh.cellGradx +# return self._Wxx + +# @property +# def Wyy(self): +# """Regularization matrix Wyy""" +# if getattr(self, '_Wyy', None) is None: +# self._Wyy = Utils.sdiag((self.mesh.vol*self.alpha_yy)**0.5)*self.mesh.faceDivy*self.mesh.cellGrady +# return self._Wyy + +# @property +# def Wzz(self): +# """Regularization matrix Wzz""" +# if getattr(self, '_Wzz', None) is None: +# self._Wzz = Utils.sdiag((self.mesh.vol*self.alpha_zz)**0.5)*self.mesh.faceDivz*self.mesh.cellGradz +# return self._Wzz + +# @property +# def Wsmooth(self): +# """Full smoothness regularization matrix W""" +# if getattr(self, '_Wsmooth', None) is None: +# wlist = (self.Wx, self.Wxx) +# if self.mesh.dim > 1: +# wlist += (self.Wy, self.Wyy) +# if self.mesh.dim > 2: +# wlist += (self.Wz, self.Wzz) +# self._Wsmooth = sp.vstack(wlist) +# return self._Wsmooth + +# @property +# def W(self): +# """Full regularization matrix W""" +# if getattr(self, '_W', None) is None: +# wlist = (self.Ws, self.Wsmooth) +# self._W = sp.vstack(wlist) +# return self._W + +# @Utils.timeIt +# def eval(self, m): +# if self.smoothModel == True: +# r1 = self.Wsmooth * ( self.mapping * (m) ) +# r2 = self.Ws * ( self.mapping * (m - self.mref) ) +# return 0.5*(r1.dot(r1)+r2.dot(r2)) +# elif self.smoothModel == False: +# r = self.W * ( self.mapping * (m - self.mref) ) +# return 0.5*r.dot(r) + + +# @Utils.timeIt +# def evalDeriv(self, m): +# """ + +# The regularization is: + +# .. math:: + +# R(m) = \\frac{1}{2}\mathbf{(m-m_\\text{ref})^\\top W^\\top W(m-m_\\text{ref})} + +# So the derivative is straight forward: + +# .. math:: + +# R(m) = \mathbf{W^\\top W (m-m_\\text{ref})} + +# """ +# if self.smoothModel == True: +# mD1 = self.mapping.deriv(m) +# mD2 = self.mapping.deriv(m - self.mref) +# r1 = self.Wsmooth * ( self.mapping * (m)) +# r2 = self.Ws * ( self.mapping * (m - self.mref) ) +# out1 = mD1.T * ( self.Wsmooth.T * r1 ) +# out2 = mD2.T * ( self.Ws.T * r2 ) +# out = out1+out2 +# elif self.smoothModel == False: +# mD = self.mapping.deriv(m - self.mref) +# r = self.W * ( self.mapping * (m - self.mref) ) +# out = mD.T * ( self.W.T * r ) +# return out + +# class SparseRegularization(Simple): + +# eps = 1e-1 + +# m = None +# gamma = 1. +# p = 0. +# qx = 2. +# qy = 2. +# qz = 2. + +# def __init__(self, mesh, mapping=None, **kwargs): +# Simple.__init__(self, mesh, mapping=mapping, **kwargs) + + +# @property +# def Wsmooth(self): +# """Full smoothness regularization matrix W""" +# if getattr(self, '_Wsmooth', None) is None: +# wlist = (self.Wx, self.Wxx) +# if self.mesh.dim > 1: +# wlist += (self.Wy, self.Wyy) +# if self.mesh.dim > 2: +# wlist += (self.Wz, self.Wzz) +# self._Wsmooth = sp.vstack(wlist) +# return self._Wsmooth + +# @property +# def W(self): +# """Full regularization matrix W""" +# if getattr(self, '_W', None) is None: +# wlist = (self.Ws, self.Wsmooth) +# self._W = sp.vstack(wlist) +# return self._W + +# @property +# def Ws(self): +# """Regularization matrix Ws""" +# if getattr(self, 'm', None) is None: +# self.Rs = Utils.speye(self.mesh.nC) + +# else: +# f_m = self.m +# self.rs = self.R(f_m , self.p, self.eps) +# #print "Min rs: " + str(np.max(self.rs)) + "Max rs: " + str(np.min(self.rs)) +# self.Rs = Utils.sdiag( self.rs ) + +# self._Ws = Utils.sdiag((self.mesh.vol*self.alpha_s*self.gamma)**0.5)*self.Rs + +# return self._Ws + +# @property +# def Wx(self): +# """Regularization matrix Wx""" + +# if getattr(self, 'm', None) is None: +# self.Rx = Utils.speye(self.mesh.unitCellGradx.shape[0]) + +# else: +# f_m = self.mesh.unitCellGradx * self.m +# self.rx = self.R( f_m , self.qx, self.eps) +# self.Rx = Utils.sdiag( self.rx ) + +# if getattr(self, '_Wx', None) is None: +# self._Wx = Utils.sdiag((self.mesh.vol*self.alpha_x*self.gamma)**0.5)*self.Rx*self.mesh.unitCellGradx +# return self._Wx + +# @property +# def Wy(self): +# """Regularization matrix Wy""" + +# if getattr(self, 'm', None) is None: +# self.Ry = Utils.speye(self.mesh.unitCellGrady.shape[0]) + +# else: +# f_m = self.mesh.unitCellGrady * self.m +# self.ry = self.R( f_m , self.qy, self.eps) +# self.Ry = Utils.sdiag( self.ry ) + +# if getattr(self, '_Wy', None) is None: +# self._Wy = Utils.sdiag((self.mesh.vol*self.alpha_y*self.gamma)**0.5)*self.Ry*self.mesh.unitCellGrady +# return self._Wy + +# @property +# def Wz(self): +# """Regularization matrix Wz""" + +# if getattr(self, 'm', None) is None: +# self.Rz = Utils.speye(self.mesh.unitCellGradz.shape[0]) + +# else: +# f_m = self.mesh.unitCellGradz * self.m +# self.rz = self.R( f_m , self.qz, self.eps) +# self.Rz = Utils.sdiag( self.rz ) + +# if getattr(self, '_Wz', None) is None: +# self._Wz = Utils.sdiag((self.mesh.vol*self.alpha_z*self.gamma)**0.5)*self.Rz*self.mesh.unitCellGradz +# return self._Wz + + +# def R(self, f_m , p, dec): + +# eta = (self.eps**(1-p/2.))**0.5 +# r = eta / (f_m**2.+self.eps**2.)**((1-p/2.)/2.) + +# return r +# ======= +# >>>>>>> 834de582844e8e1eac95819fbe03eed55dbeb001 diff --git a/SimPEG/Survey.py b/SimPEG/Survey.py index 1484b6c8..9f307c3f 100644 --- a/SimPEG/Survey.py +++ b/SimPEG/Survey.py @@ -1,6 +1,5 @@ import Utils, numpy as np, scipy.sparse as sp, uuid - class BaseRx(object): """SimPEG Receiver Object""" @@ -375,3 +374,11 @@ class BaseSurvey(object): self.dobs = self.dtrue+noise self.std = self.dobs*0 + std return self.dobs + +class LinearSurvey(BaseSurvey): + def eval(self, u): + return u + + @property + def nD(self): + return self.prob.G.shape[0] diff --git a/SimPEG/Utils/ModelBuilder.py b/SimPEG/Utils/ModelBuilder.py index 52d13820..435856f7 100644 --- a/SimPEG/Utils/ModelBuilder.py +++ b/SimPEG/Utils/ModelBuilder.py @@ -118,6 +118,44 @@ def defineElipse(ccMesh, center=[0,0,0], anisotropy=[1,1,1], slope=10., theta=0. D = np.sqrt(np.sum(G**2,axis=1)) return -np.arctan((D-1)*slope)*(2./np.pi)/2.+0.5 +def getIndicesSphere(center,radius,ccMesh): + """ + Creates a vector containing the sphere indices in the cell centers mesh. + Returns a tuple + + The sphere is defined by the points + + p0, describe the position of the center of the cell + + r, describe the radius of the sphere. + + ccMesh represents the cell-centered mesh + + The points p0 must live in the the same dimensional space as the mesh. + + """ + + # Validation: mesh and point (p0) live in the same dimensional space + dimMesh = np.size(ccMesh[0,:]) + assert len(center) == dimMesh, "Dimension mismatch. len(p0) != dimMesh" + + if dimMesh == 1: + # Define the reference points + + ind = np.abs(center[0] - ccMesh[:,0]) < radius + + elif dimMesh == 2: + # Define the reference points + + ind = np.sqrt( ( center[0] - ccMesh[:,0] )**2 + ( center[1] - ccMesh[:,1] )**2 ) < radius + + elif dimMesh == 3: + # Define the points + ind = np.sqrt( ( center[0] - ccMesh[:,0] )**2 + ( center[1] - ccMesh[:,1] )**2 + ( center[2] - ccMesh[:,2] )**2 ) < radius + + # Return a tuple + return ind + def defineTwoLayers(ccMesh,depth,vals=[0,1]): """ Define a two layered model. Depth of the first layer must be specified. diff --git a/docs/api_DC.rst b/docs/api_DC.rst new file mode 100644 index 00000000..f6c98d09 --- /dev/null +++ b/docs/api_DC.rst @@ -0,0 +1,150 @@ +.. _api_DC: + +.. math:: + + \renewcommand{\div}{\nabla\cdot\,} + \newcommand{\grad}{\vec \nabla} + \newcommand{\curl}{{\vec \nabla}\times\,} + \newcommand{\dcurl}{{\mathbf C}} + \newcommand{\dgrad}{{\mathbf G}} + \newcommand{\Acf}{{\mathbf A_c^f}} + \newcommand{\Ace}{{\mathbf A_c^e}} + \renewcommand{\S}{{\mathbf \Sigma}} + \renewcommand{\Div}{{\mathbf {Div}}} + \renewcommand{\Grad}{{\mathbf {Grad}}} + \newcommand{\St}{{\mathbf \Sigma_\tau}} + \newcommand{\diag}{\mathbf{diag}} + \newcommand{\M}{{\mathbf M}} + \newcommand{\Me}{{\M^e}} + \newcommand{\Mes}[1]{{\M^e_{#1}}} + \newcommand{\be}{\mathbf{e}} + \newcommand{\bj}{\mathbf{j}} + \newcommand{\bphi}{\mathbf{\phi}} + \newcommand{\bq}{\mathbf{q}} + \newcommand{\bJ}{\mathbf{J}} + \newcommand{\bG}{\mathbf{G}} + \newcommand{\bP}{\mathbf{P}} + \newcommand{\bA}{\mathbf{A}} + \newcommand{\bm}{\mathbf{m}} + \newcommand{\B}{\vec{B}} + \newcommand{\D}{\vec{D}} + \renewcommand{\H}{\vec{H}} + \renewcommand {\j} { {\vec j} } + \newcommand {\h} { {\vec h} } + \renewcommand {\b} { {\vec b} } + \newcommand {\e} { {\vec e} } + \newcommand {\c} { {\vec c} } + \renewcommand {\d} { {\vec d} } + \renewcommand {\u} { {\vec u} } + \newcommand{\I}{\vec{I}} + +DC resistivity survey +********************* + +Electrical resistivity of subsurface materials is measured by causing an electrical current to flow in the earth between one pair of electrodes while the voltage across a second pair of electrodes is measured. The result is an "apparent" resistivity which is a value representing the weighted average resistivity over a volume of the earth. Variations in this measurement are caused by variations in the soil, rock, and pore fluid electrical resistivity. Surveys require contact with the ground, so they can be labour intensive. Results are sometimes interpreted directly, but more commonly, 1D, 2D or 3D models are estimated using inversion procedures (`GPG `_). + + +Background +========== + +As direct current (DC) implies, in DC resistivity survey, we assume steady-state. We consider Maxwell's equations in steady state as + +.. math:: + + \curl \frac{1}{\mu} \vec{b} - \j = \j_s \\ + + \curl \e = 0 + +Then by taking \\(\\curl\\) for the first equation, we have + +.. math:: + + - \div\j = q \\ + + +where + +.. math:: + + \div \j_s = q = I(\delta(\vec{r}-\vec{r}_{s+})-\delta(\vec{r}-\vec{r}_{s-})) + +Since \\(\\curl \\e = 0\\), we have + +.. math:: + + \e = \grad \phi + +And by Ohm's law, we have + +.. math:: + + \j = \sigma \grad \phi + +Finally, we can compute the solution of the system: + +.. math:: + + - \div\j = q + + \j = \sigma \grad \phi + + \frac{\partial \phi}{\partial r}\Big|_{\partial \Omega_{BC}} = 0 + + +Discretization +============== + +By using finite volume method (FVM), we discretize our system as + +.. math:: + + -\Div \bj = \bq + + \diag(\Acf^{T}\sigma^{-1}) \bj = \Grad \bphi + +Here boundary condtions are embedded in the discrete differential operators. With some linear algebra we have + +.. math:: + + \bA\bphi = -\bq + +where + +.. math:: + + \bA = \Div (\diag(\Acf^{T}\sigma^{-1}))^{-1} \Grad + +By solving this linear equation, we can compute the solution of \\(\\phi\\). Based on this discretization, we derive sensitivity in discretized space. Sensitivity matrix can be in general can be written as + +.. math :: + + \bJ = -\bP\bA^{-1}\bG + +where + +.. math :: + + \bP: \text{Projection} + + \bJ = \bP\frac{\partial \phi}{\partial \bm} + +Here \\(\\bm\\) indicates model parameters in discretized space. + +Verification +============ + +Comparing to the analytic function: + +.. plot:: + + import simpegDC as DC + DC.Examples.Verification.run(plotIt=True) + +API +=== + +.. automodule:: simpegDC.BaseDC + :show-inheritance: + :members: + :undoc-members: + :inherited-members: diff --git a/docs/examples/DC_Analytic_Dipole.rst b/docs/examples/DC_Analytic_Dipole.rst new file mode 100644 index 00000000..f3d06058 --- /dev/null +++ b/docs/examples/DC_Analytic_Dipole.rst @@ -0,0 +1,21 @@ +.. _examples_DC_Analytic_Dipole: + +.. --------------------------------- .. +.. .. +.. THIS FILE IS AUTO GENEREATED .. +.. .. +.. SimPEG/Examples/__init__.py .. +.. .. +.. --------------------------------- .. + +DC Analytic Dipole +================== + +.. plot:: + + from SimPEG import Examples + Examples.DC_Analytic_Dipole.run() + +.. literalinclude:: ../../SimPEG/Examples/DC_Analytic_Dipole.py + :language: python + :linenos: diff --git a/docs/examples/DC_Forward_PseudoSection.rst b/docs/examples/DC_Forward_PseudoSection.rst new file mode 100644 index 00000000..1a500cae --- /dev/null +++ b/docs/examples/DC_Forward_PseudoSection.rst @@ -0,0 +1,28 @@ +.. _examples_DC_Forward_PseudoSection: + +.. --------------------------------- .. +.. .. +.. THIS FILE IS AUTO GENEREATED .. +.. .. +.. SimPEG/Examples/__init__.py .. +.. .. +.. --------------------------------- .. + + +DC Forward Simulation +===================== + +Forward model conductive spheres in a half-space and plot a pseudo-section + +Created by @fourndo on Mon Feb 01 19:28:06 2016 + + + +.. plot:: + + from SimPEG import Examples + Examples.DC_Forward_PseudoSection.run() + +.. literalinclude:: ../../SimPEG/Examples/DC_Forward_PseudoSection.py + :language: python + :linenos: diff --git a/tests/dcip/__init__.py b/tests/dcip/__init__.py new file mode 100644 index 00000000..420388ef --- /dev/null +++ b/tests/dcip/__init__.py @@ -0,0 +1,12 @@ +import os +import glob +import unittest + +if __name__ == '__main__': + test_file_strings = glob.glob('test_*.py') + module_strings = [str[0:len(str)-3] for str in test_file_strings] + suites = [unittest.defaultTestLoader.loadTestsFromName(str) for str + in module_strings] + testSuite = unittest.TestSuite(suites) + + unittest.TextTestRunner(verbosity=2).run(testSuite) diff --git a/tests/dcip/test_forward_DCproblem.py b/tests/dcip/test_forward_DCproblem.py new file mode 100644 index 00000000..a2708936 --- /dev/null +++ b/tests/dcip/test_forward_DCproblem.py @@ -0,0 +1,77 @@ +import unittest +from SimPEG import * +import SimPEG.DCIP as DC + + +class DCProblemTests(unittest.TestCase): + + def setUp(self): + + aSpacing=2.5 + nElecs=10 + + surveySize = nElecs*aSpacing - aSpacing + cs = surveySize/nElecs/4 + + mesh = Mesh.TensorMesh([ + [(cs,10, -1.3),(cs,surveySize/cs),(cs,10, 1.3)], + [(cs,3, -1.3),(cs,3,1.3)], + # [(cs,5, -1.3),(cs,10)] + ],'CN') + + srcList = DC.Utils.WennerSrcList(nElecs, aSpacing, in2D=True) + survey = DC.SurveyDC(srcList) + problem = DC.ProblemDC_CC(mesh) + problem.pair(survey) + + mSynth = np.ones(mesh.nC) + survey.makeSyntheticData(mSynth) + + # Now set up the problem to do some minimization + dmis = DataMisfit.l2_DataMisfit(survey) + reg = Regularization.Tikhonov(mesh) + opt = Optimization.InexactGaussNewton(maxIterLS=20, maxIter=10, tolF=1e-6, tolX=1e-6, tolG=1e-6, maxIterCG=6) + invProb = InvProblem.BaseInvProblem(dmis, reg, opt, beta=1e4) + inv = Inversion.BaseInversion(invProb) + + self.inv = inv + self.reg = reg + self.p = problem + self.mesh = mesh + self.m0 = mSynth + self.survey = survey + self.dmis = dmis + + def test_misfit(self): + derChk = lambda m: [self.survey.dpred(m), lambda mx: self.p.Jvec(self.m0, mx)] + passed = Tests.checkDerivative(derChk, self.m0, plotIt=False) + self.assertTrue(passed) + + def test_adjoint(self): + # Adjoint Test + u = np.random.rand(self.mesh.nC*self.survey.nSrc) + v = np.random.rand(self.mesh.nC) + w = np.random.rand(self.survey.dobs.shape[0]) + wtJv = w.dot(self.p.Jvec(self.m0, v)) + vtJtw = v.dot(self.p.Jtvec(self.m0, w)) + passed = np.abs(wtJv - vtJtw) < 1e-10 + print 'Adjoint Test', np.abs(wtJv - vtJtw), passed + self.assertTrue(passed) + + def test_dataObj(self): + derChk = lambda m: [self.dmis.eval(m), self.dmis.evalDeriv(m)] + passed = Tests.checkDerivative(derChk, self.m0, plotIt=False) + self.assertTrue(passed) + + + def test_massMatrices(self): + Gu = np.random.rand(self.mesh.nF) + def derChk(m): + self.p.curModel = m + return [self.p.Msig * Gu, self.p.dMdsig(Gu)] + passed = Tests.checkDerivative(derChk, self.m0, plotIt=False) + self.assertTrue(passed) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/dcip/test_forward_IPproblem.py b/tests/dcip/test_forward_IPproblem.py new file mode 100644 index 00000000..ff7b1ab6 --- /dev/null +++ b/tests/dcip/test_forward_IPproblem.py @@ -0,0 +1,65 @@ +import unittest +import SimPEG.DCIP as DC +from SimPEG import * + +class IPforwardTests(unittest.TestCase): + + def test_IPforward(self): + + cs = 12.5 + nc = 200/cs+1 + hx = [(cs,7, -1.3),(cs,nc),(cs,7, 1.3)] + hy = [(cs,7, -1.3),(cs,int(nc/2+1)),(cs,7, 1.3)] + hz = [(cs,7, -1.3),(cs,int(nc/2+1))] + mesh = Mesh.TensorMesh([hx, hy, hz], 'CCN') + sighalf = 1e-2 + sigma = np.ones(mesh.nC)*sighalf + p0 = np.r_[-50., 50., -50.] + p1 = np.r_[ 50.,-50., -150.] + blk_ind = Utils.ModelBuilder.getIndicesBlock(p0, p1, mesh.gridCC) + sigma[blk_ind] = 1e-3 + eta = np.zeros_like(sigma) + eta[blk_ind] = 0.1 + sigmaInf = sigma.copy() + sigma0 = sigma*(1-eta) + + nElecs = 11 + x_temp = np.linspace(-100, 100, nElecs) + aSpacing = x_temp[1]-x_temp[0] + y_temp = 0. + xyz = Utils.ndgrid(x_temp, np.r_[y_temp], np.r_[0.]) + srcList = DC.Utils.WennerSrcList(nElecs,aSpacing) + survey = DC.SurveyDC(srcList) + + imap = Maps.IdentityMap(mesh) + problem = DC.ProblemDC_CC(mesh, mapping=imap) + + try: + from pymatsolver import MumpsSolver + solver = MumpsSolver + except ImportError, e: + solver = SolverLU + + problem.Solver = solver + problem.pair(survey) + + phi0 = survey.dpred(sigma0) + phiInf = survey.dpred(sigmaInf) + + phiIP_true = phi0-phiInf + + surveyIP = DC.SurveyIP(srcList) + problemIP = DC.ProblemIP(mesh, sigma=sigma) + problemIP.pair(surveyIP) + + problemIP.Solver = solver + + phiIP_approx = surveyIP.dpred(eta) + + err = np.linalg.norm(phiIP_true-phiIP_approx) / np.linalg.norm(phiIP_true) + + self.assertTrue(err < 0.02) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/dcip/test_sens_IPproblem.py b/tests/dcip/test_sens_IPproblem.py new file mode 100644 index 00000000..9ba18031 --- /dev/null +++ b/tests/dcip/test_sens_IPproblem.py @@ -0,0 +1,82 @@ +import unittest +from SimPEG import * +import SimPEG.DCIP as DC + +class IPProblemTests(unittest.TestCase): + + def setUp(self): + + cs = 12.5 + nc = 500/cs+1 + hx = [(cs,0, -1.3),(cs,nc),(cs,0, 1.3)] + hy = [(cs,0, -1.3),(cs,int(nc/2+1)),(cs,0, 1.3)] + hz = [(cs,0, -1.3),(cs,int(nc/2+1))] + mesh = Mesh.TensorMesh([hx, hy, hz], 'CCN') + sighalf = 1e-2 + sigma = np.ones(mesh.nC)*sighalf + p0 = np.r_[-50., 50., -50.] + p1 = np.r_[ 50.,-50., -150.] + blk_ind = Utils.ModelBuilder.getIndicesBlock(p0, p1, mesh.gridCC) + sigma[blk_ind] = 1e-3 + eta = np.zeros_like(sigma) + eta[blk_ind] = 0.1 + + nElecs = 5 + x_temp = np.linspace(-250, 250, nElecs) + aSpacing = x_temp[1]-x_temp[0] + y_temp = 0. + xyz = Utils.ndgrid(x_temp, np.r_[y_temp], np.r_[0.]) + srcList = DC.Utils.WennerSrcList(nElecs,aSpacing) + survey = DC.SurveyIP(srcList) + imap = Maps.IdentityMap(mesh) + problem = DC.ProblemIP(mesh, sigma=sigma, mapping= imap) + problem.pair(survey) + + try: + from pymatsolver import MumpsSolver + problem.Solver = MumpsSolver + except ImportError, e: + problem.Solver = SolverLU + + mSynth = eta + survey.makeSyntheticData(mSynth) + + # Now set up the problem to do some minimization + dmis = DataMisfit.l2_DataMisfit(survey) + reg = Regularization.Tikhonov(mesh) + opt = Optimization.InexactGaussNewton(maxIterLS=20, maxIter=10, tolF=1e-6, tolX=1e-6, tolG=1e-6, maxIterCG=6) + invProb = InvProblem.BaseInvProblem(dmis, reg, opt, beta=1e4) + inv = Inversion.BaseInversion(invProb) + + self.inv = inv + self.reg = reg + self.p = problem + self.mesh = mesh + self.m0 = mSynth + self.survey = survey + self.dmis = dmis + + def test_misfit(self): + derChk = lambda m: [self.survey.dpred(m), lambda mx: self.p.Jvec(self.m0, mx)] + passed = Tests.checkDerivative(derChk, self.m0*0, plotIt=False) + self.assertTrue(passed) + + def test_adjoint(self): + # Adjoint Test + u = np.random.rand(self.mesh.nC*self.survey.nSrc) + v = np.random.rand(self.mesh.nC) + w = np.random.rand(self.survey.dobs.shape[0]) + wtJv = w.dot(self.p.Jvec(self.m0, v)) + vtJtw = v.dot(self.p.Jtvec(self.m0, w)) + passed = np.abs(wtJv - vtJtw) < 1e-10 + print 'Adjoint Test', np.abs(wtJv - vtJtw), passed + self.assertTrue(passed) + + def test_dataObj(self): + derChk = lambda m: [self.dmis.eval(m), self.dmis.evalDeriv(m)] + passed = Tests.checkDerivative(derChk, self.m0, plotIt=False) + self.assertTrue(passed) + + +if __name__ == '__main__': + unittest.main()