From 0d298af1d7e3e5fe82026b8463306827ca1adfc7 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Tue, 23 Jul 2013 19:35:57 -0700 Subject: [PATCH 01/51] Initial commit of Logically orthogonal mesh. --- SimPEG/LogicallyOrthogonalMesh.py | 115 ++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 SimPEG/LogicallyOrthogonalMesh.py diff --git a/SimPEG/LogicallyOrthogonalMesh.py b/SimPEG/LogicallyOrthogonalMesh.py new file mode 100644 index 00000000..b530fcb1 --- /dev/null +++ b/SimPEG/LogicallyOrthogonalMesh.py @@ -0,0 +1,115 @@ +import numpy as np +from BaseMesh import BaseMesh +from DiffOperators import DiffOperators +from utils import mkvc + + +class LogicallyOrthogonalMesh(BaseMesh, DiffOperators): # , LOMGrid + """ + LogicallyOrthogonalMesh is a mesh class that deals with logically orthogonal meshes. + + """ + def __init__(self, nodes, x0=None): + # Start with some error checking: + assert type(nodes) == list, "'nodes' variable must be a list of np.ndarray" + + for i, nodes_i in enumerate(nodes): + assert type(nodes_i) == np.ndarray, ("nodes[%i] is not a numpy array." % i) + assert nodes_i.shape == nodes[0].shape, ("nodes[%i] is not the same shape as nodes[0]" % i) + + super(LogicallyOrthogonalMesh, self).__init__(np.array(nodes[0].shape), x0) + + assert len(nodes[0].shape) == len(self.x0), "Dimension mismatch. x0 != len(h)" + + # Save nodes to private variable _gridN as vectors + self._gridN = np.ones((nodes[0].size, self.dim)) + for i, node_i in enumerate(nodes): + self._gridN[:, i] = mkvc(node_i) + + def gridCC(): + doc = "Cell-centered grid." + + def fget(self): + if self._gridCC is None: + self._gridCC = ndgrid([x for x in [self.vectorCCx, self.vectorCCy, self.vectorCCz] if not x is None]) + return self._gridCC + return locals() + _gridCC = None # Store grid by default + gridCC = property(**gridCC()) + + def gridN(): + doc = "Nodal grid." + + def fget(self): + if self._gridN is None: + self._gridN = ndgrid([x for x in [self.vectorNx, self.vectorNy, self.vectorNz] if not x is None]) + return self._gridN + return locals() + _gridN = None # Store grid by default + gridN = property(**gridN()) + + def gridFx(): + doc = "Face staggered grid in the x direction." + + def fget(self): + if self._gridFx is None: + self._gridFx = ndgrid([x for x in [self.vectorNx, self.vectorCCy, self.vectorCCz] if not x is None]) + return self._gridFx + return locals() + _gridFx = None # Store grid by default + gridFx = property(**gridFx()) + + def gridFy(): + doc = "Face staggered grid in the y direction." + + def fget(self): + if self._gridFy is None: + self._gridFy = ndgrid([x for x in [self.vectorCCx, self.vectorNy, self.vectorCCz] if not x is None]) + return self._gridFy + return locals() + _gridFy = None # Store grid by default + gridFy = property(**gridFy()) + + def gridFz(): + doc = "Face staggered grid in the z direction." + + def fget(self): + if self._gridFz is None: + self._gridFz = ndgrid([x for x in [self.vectorCCx, self.vectorCCy, self.vectorNz] if not x is None]) + return self._gridFz + return locals() + _gridFz = None # Store grid by default + gridFz = property(**gridFz()) + + def gridEx(): + doc = "Edge staggered grid in the x direction." + + def fget(self): + if self._gridEx is None: + self._gridEx = ndgrid([x for x in [self.vectorCCx, self.vectorNy, self.vectorNz] if not x is None]) + return self._gridEx + return locals() + _gridEx = None # Store grid by default + gridEx = property(**gridEx()) + + def gridEy(): + doc = "Edge staggered grid in the y direction." + + def fget(self): + if self._gridEy is None: + self._gridEy = ndgrid([x for x in [self.vectorNx, self.vectorCCy, self.vectorNz] if not x is None]) + return self._gridEy + return locals() + _gridEy = None # Store grid by default + gridEy = property(**gridEy()) + + def gridEz(): + doc = "Edge staggered grid in the z direction." + + def fget(self): + if self._gridEz is None: + self._gridEz = ndgrid([x for x in [self.vectorNx, self.vectorNy, self.vectorCCz] if not x is None]) + return self._gridEz + return locals() + _gridEz = None # Store grid by default + gridEz = property(**gridEz()) From dc9a92c83a5a36c44a1be1bb717c66bfded34ea5 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Wed, 24 Jul 2013 12:25:31 -0700 Subject: [PATCH 02/51] Cell centered grid working in LOM --- SimPEG/DiffOperators.py | 42 +++++++++++++++++++++++++++++++ SimPEG/LogicallyOrthogonalMesh.py | 22 +++++++++++++--- 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/SimPEG/DiffOperators.py b/SimPEG/DiffOperators.py index c5daa96d..5d8356bd 100644 --- a/SimPEG/DiffOperators.py +++ b/SimPEG/DiffOperators.py @@ -245,6 +245,48 @@ class DiffOperators(object): _edgeAve = None edgeAve = property(**edgeAve()) + def nodalAve(): + doc = "Construct the averaging operator on cell nodes to cell centers." + + def fget(self): + if(self._nodalAve is None): + # The number of cell centers in each direction + n = self.n + if(self.dim == 1): + self._nodalAve = av(n[0]) + elif(self.dim == 2): + self._nodalAve = sp.hstack((sp.kron(av(n[1]), av(n[0])), + sp.kron(av(n[1]), av(n[0]))), format="csr") + elif(self.dim == 3): + self._nodalAve = sp.hstack((kron3(av(n[2]), av(n[1]), av(n[0])), + kron3(av(n[2]), av(n[1]), av(n[0])), + kron3(av(n[2]), av(n[1]), av(n[0]))), format="csr") + return self._nodalAve + return locals() + _nodalAve = None + nodalAve = property(**nodalAve()) + + def nodalVectorAve(): + doc = "Construct the averaging operator on cell nodes to cell centers, keeping each dimension seperate." + + def fget(self): + if(self._nodalVectorAve is None): + # The number of cell centers in each direction + n = self.n + if(self.dim == 1): + self._nodalVectorAve = av(n[0]) + elif(self.dim == 2): + self._nodalVectorAve = sp.block_diag((sp.kron(av(n[1]), av(n[0])), + sp.kron(av(n[1]), av(n[0]))), format="csr") + elif(self.dim == 3): + self._nodalVectorAve = sp.block_diag((kron3(av(n[2]), av(n[1]), av(n[0])), + kron3(av(n[2]), av(n[1]), av(n[0])), + kron3(av(n[2]), av(n[1]), av(n[0]))), format="csr") + return self._nodalVectorAve + return locals() + _nodalVectorAve = None + nodalVectorAve = property(**nodalVectorAve()) + def getEdgeMass(self, materialProp=None): """mass matix for products of edge functions w'*M(materialProp)*e""" if(materialProp is None): diff --git a/SimPEG/LogicallyOrthogonalMesh.py b/SimPEG/LogicallyOrthogonalMesh.py index b530fcb1..b49d595c 100644 --- a/SimPEG/LogicallyOrthogonalMesh.py +++ b/SimPEG/LogicallyOrthogonalMesh.py @@ -1,7 +1,7 @@ import numpy as np from BaseMesh import BaseMesh from DiffOperators import DiffOperators -from utils import mkvc +from utils import mkvc, ndgrid class LogicallyOrthogonalMesh(BaseMesh, DiffOperators): # , LOMGrid @@ -17,7 +17,9 @@ class LogicallyOrthogonalMesh(BaseMesh, DiffOperators): # , LOMGrid assert type(nodes_i) == np.ndarray, ("nodes[%i] is not a numpy array." % i) assert nodes_i.shape == nodes[0].shape, ("nodes[%i] is not the same shape as nodes[0]" % i) - super(LogicallyOrthogonalMesh, self).__init__(np.array(nodes[0].shape), x0) + assert len(nodes[0].shape) == len(nodes), "Dimension mismatch" + + super(LogicallyOrthogonalMesh, self).__init__(np.array(nodes[0].shape)-1, x0) assert len(nodes[0].shape) == len(self.x0), "Dimension mismatch. x0 != len(h)" @@ -31,7 +33,8 @@ class LogicallyOrthogonalMesh(BaseMesh, DiffOperators): # , LOMGrid def fget(self): if self._gridCC is None: - self._gridCC = ndgrid([x for x in [self.vectorCCx, self.vectorCCy, self.vectorCCz] if not x is None]) + ccV = (self.nodalVectorAve*mkvc(self.gridN)) + self._gridCC = ccV.reshape((-1, self.dim), order='F') return self._gridCC return locals() _gridCC = None # Store grid by default @@ -42,7 +45,7 @@ class LogicallyOrthogonalMesh(BaseMesh, DiffOperators): # , LOMGrid def fget(self): if self._gridN is None: - self._gridN = ndgrid([x for x in [self.vectorNx, self.vectorNy, self.vectorNz] if not x is None]) + raise Exception("Someone deleted this. I blame you.") return self._gridN return locals() _gridN = None # Store grid by default @@ -113,3 +116,14 @@ class LogicallyOrthogonalMesh(BaseMesh, DiffOperators): # , LOMGrid return locals() _gridEz = None # Store grid by default gridEz = property(**gridEz()) + +if __name__ == '__main__': + nc = 5 + h1 = np.cumsum(np.r_[0, np.ones(nc)/(nc)]) + h2 = np.cumsum(np.r_[0, np.ones(nc)/(nc)]) + h3 = np.cumsum(np.r_[0, np.ones(nc)/(nc)]) + h = [h1, h2, h3] + X, Y, Z = ndgrid(h1, h2, h3, vector=False) + M = LogicallyOrthogonalMesh([X, Y, Z]) + print M.gridCC[:,0] + print M.gridN[:,0] From de0f5a7b1fe5eb7bd5360663124edeebc94f19b8 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Wed, 24 Jul 2013 15:36:28 -0700 Subject: [PATCH 03/51] Cleaned up utility codes. --- SimPEG/sputils.py | 47 +---------------------------------------------- SimPEG/utils.py | 4 ---- 2 files changed, 1 insertion(+), 50 deletions(-) diff --git a/SimPEG/sputils.py b/SimPEG/sputils.py index b078fff2..d9aab184 100644 --- a/SimPEG/sputils.py +++ b/SimPEG/sputils.py @@ -1,10 +1,9 @@ -import numpy as np from scipy import sparse as sp def sdiag(h): """Sparse diagonal matrix""" - return sp.spdiags(h, 0, np.size(h), np.size(h), format="csr") + return sp.spdiags(h, 0, h.size, h.size, format="csr") def speye(n): @@ -20,47 +19,3 @@ def kron3(A, B, C): def spzeros(n1, n2): """spzeros""" return sp.coo_matrix((n1, n2)).tocsr() - - -def appendBottom(A, B): - """append on bottom""" - C = sp.vstack((A, B)) - C = C.tocsr() - return C - - -def appendBottom3(A, B, C): - """append on bottom""" - C = appendBottom(appendBottom(A, B), C) - C = C.tocsr() - return C - - -def appendRight(A, B): - """append on right""" - C = sp.hstack((A, B)) - C = C.tocsr() - return C - - -def appendRight3(A, B, C): - """append on right""" - C = appendRight(appendRight(A, B), C) - C = C.tocsr() - return C - - -def blkDiag(A, B): - """blockdigonal""" - O12 = sp.coo_matrix((np.shape(A)[0], np.shape(B)[1])) - O21 = sp.coo_matrix((np.shape(B)[0], np.shape(A)[1])) - C = sp.vstack((sp.hstack((A, O12)), sp.hstack((O21, B)))) - C = C.tocsr() - return C - - -def blkDiag3(A, B, C): - """blockdigonal 3""" - ABC = blkDiag(blkDiag(A, B), C) - ABC = ABC.tocsr() - return ABC diff --git a/SimPEG/utils.py b/SimPEG/utils.py index 4937062b..fdeb2742 100644 --- a/SimPEG/utils.py +++ b/SimPEG/utils.py @@ -1,10 +1,6 @@ import numpy as np -def reshapeF(x, size): - return np.reshape(x, size, order='F') - - def mkvc(x, numDims=1): """Creates a vector with the number of dimension specified From 87331c4c9238db17f8f9ca44591485b8dcbc7f0f Mon Sep 17 00:00:00 2001 From: ehaber99 Date: Fri, 26 Jul 2013 11:22:52 -0700 Subject: [PATCH 04/51] mass matrices for anisotropic sigma --- SimPEG/getEdgeInnerProducts.py | 181 ++++++++++++++++++++++++++++++ SimPEG/massMatrices.py | 94 ++++++++++++++++ SimPEG/subArray.py | 8 ++ SimPEG/tests/test_massMatrices.py | 48 ++++++++ SimPEG/utils.py | 31 ++++- 5 files changed, 361 insertions(+), 1 deletion(-) create mode 100644 SimPEG/getEdgeInnerProducts.py create mode 100644 SimPEG/massMatrices.py create mode 100644 SimPEG/subArray.py create mode 100644 SimPEG/tests/test_massMatrices.py diff --git a/SimPEG/getEdgeInnerProducts.py b/SimPEG/getEdgeInnerProducts.py new file mode 100644 index 00000000..5a95f146 --- /dev/null +++ b/SimPEG/getEdgeInnerProducts.py @@ -0,0 +1,181 @@ +from scipy.sparse import linalg +from scipy import sparse +from sputils import * +from utils import * +from numpy import * +from TensorMesh import * + +# [A] = getEdgeInnerProduct(X,Y,Z,sigma) +# + +# node(i,j,k+1) ------ edge2(i,j,k+1) ----- node(i,j+1,k+1) +# / / +# / / | +# edge3(i,j,k) face1(i,j,k) edge3(i,j+1,k) +# / / | +# / / | +# node(i,j,k) ------ edge2(i,j,k) ----- node(i,j+1,k) +# | | | +# | | node(i+1,j+1,k+1) +# | | / +# edge1(i,j,k) face3(i,j,k) edge1(i,j+1,k) +# | | / +# | | / +# | |/ +# node(i+1,j,k) ------ edge2(i+1,j,k) ----- node(i+1,j+1,k) + +# no | node | e1 | e2 | e3 +# 000 | i ,j ,k | i ,j ,k | i ,j ,k | i ,j ,k +# 100 | i+1,j ,k | i ,j ,k | i+1,j ,k | i+1,j ,k +# 010 | i ,j+1,k | i ,j+1,k | i ,j ,k | i ,j+1,k +# 110 | i+1,j+1,k | i ,j+1,k | i+1,j ,k | i+1,j+1,k +# 001 | i ,j ,k+1 | i ,j ,k+1 | i ,j ,k+1 | i ,j ,k +# 101 | i+1,j ,k+1 | i ,j ,k+1 | i+1,j ,k+1 | i+1,j ,k +# 011 | i ,j+1,k+1 | i ,j+1,k+1 | i ,j ,k+1 | i ,j+1,k +# 111 | i+1,j+1,k+1 | i ,j+1,k+1 | i+1,j ,k+1 | i+1,j+1,k + + +def subarray(T,i1,i2,i3): + return take(take(take(T,i1,0),i2,1),i3,2) + + +def getEdgeInnerProduct(mesh,sigma): + + h = mesh.h + m = array([size(h[0]),size(h[1]),size(h[2])]) + nc = prod(m) + + me1 = m + array([0, 1, 1]); ne1 = prod(me1) + me2 = m + array([1, 0, 1]); ne2 = prod(me2) + me3 = m + array([1, 1, 0]); ne3 = prod(me3) + + i = int64(linspace(0,m[0]-1,m[0])) + j = int64(linspace(0,m[1]-1,m[1])) + k = int64(linspace(0,m[2]-1,m[2])) + + ii,jj,kk = ndgrid(i,j,k,vector=False) + ii = mkvc(ii); jj = mkvc(jj); kk = mkvc(kk) + + ## -------- + # no | node | e1 | e2 | e3 + # 000 | i ,j ,k | i ,j ,k | i ,j ,k | i ,j ,k + ind1 = sub2ind(me1,c_[ii,jj,kk]) + ind2 = sub2ind(me2,c_[ii,jj,kk]) + ne1 + ind3 = sub2ind(me3,c_[ii,jj,kk]) + ne1 + ne2 + + IND = vstack((vstack((ind1,ind2)),ind3)) + IND = array(IND).flatten() + + P000 = sparse.coo_matrix((ones(3*nc),(linspace(0,3*nc-1,3*nc),IND)),shape=(3*nc,ne1+ne2+ne3)).tocsr() + + ## -------- + # no | node | e1 | e2 | e3 + # 100 | i+1,j ,k | i ,j ,k | i+1,j ,k | i+1,j ,k + ind1 = sub2ind(me1,c_[ii,jj,kk]) + ind2 = sub2ind(me2,c_[ii+1,jj,kk]) + ne1 + ind3 = sub2ind(me3,c_[ii+1,jj,kk]) + ne1 + ne2 + + IND = vstack((vstack((ind1,ind2)),ind3)) + IND = array(IND).flatten() + + P100 = sparse.coo_matrix((ones(3*nc),(linspace(0,3*nc-1,3*nc),IND)),shape=(3*nc,ne1+ne2+ne3)).tocsr() + + ## -------- + # no | node | e1 | e2 | e3 + # 010 | i ,j+1,k | i ,j+1,k | i ,j ,k | i ,j+1,k + ind1 = sub2ind(me1,c_[ii,jj+1,kk]) + ind2 = sub2ind(me2,c_[ii,jj,kk]) + ne1 + ind3 = sub2ind(me3,c_[ii,jj+1,kk]) + ne1 + ne2 + + IND = vstack((vstack((ind1,ind2)),ind3)) + IND = array(IND).flatten() + + P010 = sparse.coo_matrix((ones(3*nc),(linspace(0,3*nc-1,3*nc),IND)),shape=(3*nc,ne1+ne2+ne3)).tocsr() + + ## -------- + # no | node | e1 | e2 | e3 + # 110 | i+1,j+1,k | i ,j+1,k | i+1,j ,k | i+1,j+1,k + ind1 = sub2ind(me1,c_[ii,jj+1,kk]) + ind2 = sub2ind(me2,c_[ii+1,jj,kk]) + ne1 + ind3 = sub2ind(me3,c_[ii+1,jj+1,kk]) + ne1 + ne2 + + IND = vstack((vstack((ind1,ind2)),ind3)) + IND = array(IND).flatten() + + P110 = sparse.coo_matrix((ones(3*nc),(linspace(0,3*nc-1,3*nc),IND)),shape=(3*nc,ne1+ne2+ne3)).tocsr() + + ###### + + ## -------- + # no | node | e1 | e2 | e3 + # 001 | i ,j ,k+1 | i ,j ,k+1 | i ,j ,k+1 | i ,j ,k + ind1 = sub2ind(me1,c_[ii,jj,kk+1]) + ind2 = sub2ind(me2,c_[ii,jj,kk+1]) + ne1 + ind3 = sub2ind(me3,c_[ii,jj,kk]) + ne1 + ne2 + + IND = vstack((vstack((ind1,ind2)),ind3)) + IND = array(IND).flatten() + + P001 = sparse.coo_matrix((ones(3*nc),(linspace(0,3*nc-1,3*nc),IND)),shape=(3*nc,ne1+ne2+ne3)).tocsr() + + ## -------- + # no | node | e1 | e2 | e3 + # 101 | i+1,j ,k+1 | i ,j ,k+1 | i+1,j ,k+1 | i+1,j ,k+1 + ind1 = sub2ind(me1,c_[ii,jj,kk+1]) + ind2 = sub2ind(me2,c_[ii+1,jj,kk+1]) + ne1 + ind3 = sub2ind(me3,c_[ii+1,jj,kk]) + ne1 + ne2 + + IND = vstack((vstack((ind1,ind2)),ind3)) + IND = array(IND).flatten() + + P101 = sparse.coo_matrix((ones(3*nc),(linspace(0,3*nc-1,3*nc),IND)),shape=(3*nc,ne1+ne2+ne3)).tocsr() + + ## -------- + # no | node | e1 | e2 | e3 + # 011 | i ,j+1,k+1 | i ,j+1,k+1 | i ,j ,k+1 | i ,j+1,k+1 + ind1 = sub2ind(me1,c_[ii,jj+1,kk+1]) + ind2 = sub2ind(me2,c_[ii,jj,kk+1]) + ne1 + ind3 = sub2ind(me3,c_[ii,jj+1,kk]) + ne1 + ne2 + + IND = vstack((vstack((ind1,ind2)),ind3)) + IND = array(IND).flatten() + + P011 = sparse.coo_matrix((ones(3*nc),(linspace(0,3*nc-1,3*nc),IND)),shape=(3*nc,ne1+ne2+ne3)).tocsr() + + ## -------- + # no | node | e1 | e2 | e3 + # 111 | i+1,j+1,k+1 | i ,j+1,k+1 | i+1,j ,k+1 | i+1,j+1,k+1 + ind1 = sub2ind(me1,c_[ii,jj+1,kk+1]) + ind2 = sub2ind(me2,c_[ii+1,jj,kk+1]) + ne1 + ind3 = sub2ind(me3,c_[ii+1,jj+1,kk]) + ne1 + ne2 + + IND = vstack((vstack((ind1,ind2)),ind3)) + IND = array(IND).flatten() + + P111 = sparse.coo_matrix((ones(3*nc),(linspace(0,3*nc-1,3*nc),IND)),shape=(3*nc,ne1+ne2+ne3)).tocsr() + + + + # Cell volume + v = sqrt(mesh.vol) + row1 = sp.hstack((sdiag(sigma[:,0]),sdiag(sigma[:,3]),sdiag(sigma[:,4]))) + row2 = sp.hstack((sdiag(sigma[:,3]),sdiag(sigma[:,1]),sdiag(sigma[:,5]))) + row3 = sp.hstack((sdiag(sigma[:,4]),sdiag(sigma[:,5]),sdiag(sigma[:,2]))) + Sigma = sp.vstack((row1, row2, row3)) + + v3 = r_[v,v,v] + V = sdiag(v3)*Sigma*sdiag(v3) + + A = P000.T*V*P000 + P001.T*V*P001 + P010.T*V*P010 + P011.T*V*P011 + P100.T*V*P100 + P101.T*V*P101 + P110.T*V*P110 + P111.T*V*P111 + + A = 0.125*A + + return A + + +if __name__ == '__main__': + + h = [array([1,2,3,4]),array([1,2,1,4,2]),array([1,1,4,1])] + mesh = TensorMesh(h) + sigma = ones((mesh.nC,6)) + A = getEdgeInnerProduct(mesh,sigma) \ No newline at end of file diff --git a/SimPEG/massMatrices.py b/SimPEG/massMatrices.py new file mode 100644 index 00000000..ead21e3a --- /dev/null +++ b/SimPEG/massMatrices.py @@ -0,0 +1,94 @@ +import numpy as np +from scipy import sparse as sp +from sputils import sdiag, speye, kron3, spzeros +from utils import mkvc + + + +def getEdgeMassMatrix(sigma,mesh): + """Get anisotropic mass matrix""" + + n = array([size(mesh.h[0]),size(mesh.h[1]),size(mesh.h[2])]) + nx = prod(n + [1, 0, 0]) + ex = reshape(arange(0,nx),[n[0]+1,n[1],n[2]]) + ny = prod(n + [0, 1, 0]) + ey = reshape(arange(0,ny),[n[0],n[1]+1,n[2]]) + nz = prod(n + [0, 0, 1]); + ez = reshape(arange(0,nz),[n[0],n[1],n[2]+1]) + + + i = arange(0,n[0]-1); j = arange(0,n[1]-1); k = arange(0,n[2]-1) + + # corner i,j,k + Px1 = take(ex,[i,j,k]); Py1 = take(ey,[i,j,k]); Pz1 = take(ez,[i,j,k]) + # corner i+1,j,k + Px2 = take(ex,[i,j,k]); Py2 = take(ey,[i+1,j,k]); Pz2 = take(ez,[i+1,j,k]) + # corner i,j+1,k + Px3 = take(ex,[i,j+1,k]); Py3 = take(ey,[i,j,k]); Pz3 = take(ez,[i,j+1,k]) + # corner i+1,j+1,k + Px4 = take(ex,[i,j+1,k]); Py4 = take(ey,[i+1,j,k]); Pz4 = take(ez,[i+1,j+1,k]); + + # corner i,j,k+1 + Px5 = take(ex,[i,j,k+1]); Py5 = take(ey,[i,j,k+1]); Pz5 = take(ez,[i,j,k]) + # corner i+1,j,k+1 + Px6 = take(ex,[i,j,k+1]); Py6 = take(ey,[i+1,j,k+1]); Pz6 = take(ez,[i+1,j,k]) + # corner i,j+1,k+1 + Px7 = take(ex,[i,j+1,k+1]); Py7 = take(ey,[i,j,k+1]); Pz7 = take(ez,[i,j+1,k]) + # corner i+1,j+1,k+1 + Px8 = take(ex,[i,j+1,k+1]); Py8 = take(ey,[i+1,j,k+1]); Pz8 = take(ez,[i+1,j+1,k]) + + + nx1 = size(Px1); ny1 = size(Py1); nz1 = size(Pz1) + #sparse.coo_matrix((V,(I,J)),shape=(4,4)) + P1 = block_diag(( sparse.coo_matrix(arange(0,nx1),Px1(:), e(nx1), nx1,nx), + sparse.coo_matrix(arange(0,ny1),Py1(:),e(ny1), ny1,ny), + sparse.coo_matrix(arange(0,nz1),Pz1(:),e(nz1), nz1,nz))) + + nx2 = numel(Px2); ny2 = numel(Py2); nz2 = numel(Pz2); + P2 = blkdiag( sparse(1:nx2,Px2(:), e(nx2), nx2,nx) , ... + sparse(1:ny2,Py2(:),e(ny2), ny2,ny), ... + sparse(1:nz2,Pz2(:),e(nz2), nz2,nz)); + + nx3 = numel(Px3); ny3 = numel(Py3); nz3 = numel(Pz3); + P3 = blkdiag( sparse(1:nx3,Px3(:), e(nx3), nx3,nx) , ... + sparse(1:ny3,Py3(:),e(ny3), ny3,ny), ... + sparse(1:nz3,Pz3(:),e(nz3), nz3,nz)); + + nx4 = numel(Px4); ny4 = numel(Py4); nz4 = numel(Pz4); + P4 = blkdiag( sparse(1:nx4,Px4(:), e(nx4), nx4,nx) , ... + sparse(1:ny4,Py4(:), e(ny4), ny4,ny), ... + sparse(1:nz4,Pz4(:), e(nz4), nz4,nz)); + + nx5 = numel(Px5); ny5 = numel(Py5); nz5 = numel(Pz5); + P5 = blkdiag( sparse(1:nx5,Px5(:), e(nx5), nx5,nx) , ... + sparse(1:ny5,Py5(:), e(ny5), ny5,ny), ... + sparse(1:nz5,Pz5(:), e(nz5), nz5,nz)); + + nx6 = numel(Px6); ny6 = numel(Py6); nz6 = numel(Pz6); + P6 = blkdiag( sparse(1:nx6,Px6(:), e(nx6), nx6,nx) , ... + sparse(1:ny6,Py6(:), e(ny6), ny6,ny), ... + sparse(1:nz6,Pz6(:), e(nz6), nz6,nz)); + + nx7 = numel(Px7); ny7 = numel(Py7); nz7 = numel(Pz7); + P7 = blkdiag( sparse(1:nx7,Px7(:), e(nx7), nx7,nx) , ... + sparse(1:ny7,Py7(:), e(ny7), ny7,ny), ... + sparse(1:nz7,Pz7(:), e(nz7), nz7,nz)); + + nx8 = numel(Px8); ny8 = numel(Py8); nz8 = numel(Pz8); + P8 = blkdiag( sparse(1:nx8,Px8(:), e(nx8), nx8,nx) , ... + sparse(1:ny8,Py8(:), e(ny8), ny8,ny), ... + sparse(1:nz8,Pz8(:), e(nz8), nz8,nz)); + + V = sdiag(sqrt([v(:); v(:); v(:)])); + + # generate the conductivity + S = [sdiag(sig(:,1)) , sdiag(sig(:,4)) , sdiag(sig(:,5)); ... + sdiag(sig(:,4)) , sdiag(sig(:,2)) , sdiag(sig(:,6)); ... + sdiag(sig(:,5)) , sdiag(sig(:,6)) , sdiag(sig(:,3))]; + + # scale by the volume + S = V*S*V; + + M = 1/8*(P1'*S*P1 + P2'*S*P2 + P3'*S*P3 + P4'*S*P4 + ... + P5'*S*P5 + P6'*S*P6 + P7'*S*P7 + P8'*S*P8); + \ No newline at end of file diff --git a/SimPEG/subArray.py b/SimPEG/subArray.py new file mode 100644 index 00000000..2811f350 --- /dev/null +++ b/SimPEG/subArray.py @@ -0,0 +1,8 @@ +import numpy as np + +def getSubArray(A,ind): + """subArray""" + i = ind[0]; j = ind[1]; k = ind[2] + + return A[i,:,:][:,j,:][:,:,k] + \ No newline at end of file diff --git a/SimPEG/tests/test_massMatrices.py b/SimPEG/tests/test_massMatrices.py new file mode 100644 index 00000000..08fa4caf --- /dev/null +++ b/SimPEG/tests/test_massMatrices.py @@ -0,0 +1,48 @@ +import numpy as np +import unittest +import sys +sys.path.append('../') +from TensorMesh import TensorMesh +from OrderTest import OrderTest +from scipy.sparse.linalg import dsolve +from getEdgeInnerProducts import getEdgeInnerProducts + + +class TestNodalGrad(OrderTest): + name = "Nodal Gradient" + + meshSizes = [4, 8, 16, 32] + + def getError(self): + ex = lambda x, y, z: x**2+y*z + ey = lambda x, y, z: (z**2)*x+y*z + ez = lambda x, y, z: y**2+x*z + + sigma1 = lambda x, y, z: x*y+1 + sigma2 = lambda x, y, z: x*z+2 + sigma3 = lambda x, y, z: 3+z*y + sigma4 = lambda x, y, z: 0.1*x*y*z + sigma5 = lambda x, y, z: 0.2*x*y + sigma6 = lambda x, y, z: 0.1*z + + Ex = ex(self.M.gridEx[:, 0],self.M.gridEx[:, 1],self.M.gridEx[:, 2]) + Ey = ey(self.M.gridEy[:, 0],self.M.gridEy[:, 1],self.M.gridEy[:, 2]) + Ez = ez(self.M.gridEz[:, 0],self.M.gridEz[:, 1],self.M.gridEz[:, 2]) + + E = np.r_[Ex,Ey,Ez] + Gc = self.M.gridCC + sigma = np.c_[sigma1(Gc[:,0],Gc[:,1],Gc[:,2]), + sigma2(Gc[:,0],Gc[:,1],Gc[:,2]), + sigma3(Gc[:,0],Gc[:,1],Gc[:,2]), + sigma4(Gc[:,0],Gc[:,1],Gc[:,2]), + sigma5(Gc[:,0],Gc[:,1],Gc[:,2]), + sigma6(Gc[:,0],Gc[:,1],Gc[:,2])] + + A = getEdgeInnerProducts(self.M, sigma) + + err = np.abs(E.T*A*E - 69881./21600) + + return err + + def test_order(self): + self.orderTest() diff --git a/SimPEG/utils.py b/SimPEG/utils.py index 4937062b..b9e17847 100644 --- a/SimPEG/utils.py +++ b/SimPEG/utils.py @@ -1,5 +1,5 @@ import numpy as np - +from numpy import * def reshapeF(x, size): return np.reshape(x, size, order='F') @@ -97,3 +97,32 @@ def ndgrid(*args, **kwargs): return np.c_[X1, X2, X3] else: return XYZ[2], XYZ[1], XYZ[0] + + +def ind2sub(shape, ind): + """From the given shape, returns the subscrips of the given index""" + revshp = [] + revshp.extend(shape) + mult = [1] + for i in range(0, len(revshp)-1): + mult.extend([mult[i]*revshp[i]]) + mult = array(mult).reshape(len(mult)) + + sub = [] + + for i in range(0, len(shape)): + sub.extend([math.floor(ind / mult[i])]) + ind = ind - (math.floor(ind/mult[i]) * mult[i]) + return sub + + +def sub2ind(shape, subs): + """From the given shape, returns the index of the given subscript""" + revshp = list(shape) + mult = [1] + for i in range(0, len(revshp)-1): + mult.extend([mult[i]*revshp[i]]) + mult = array(mult).reshape(len(mult), 1) + + idx = dot((subs), (mult)) + return idx \ No newline at end of file From 1909af17755768934b46e3bd96f97cf61e35d2b4 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Fri, 26 Jul 2013 11:40:36 -0700 Subject: [PATCH 05/51] Error checking and volume. --- SimPEG/LogicallyOrthogonalMesh.py | 84 ++++++++----------------------- SimPEG/utils.py | 30 +++++++++++ 2 files changed, 50 insertions(+), 64 deletions(-) diff --git a/SimPEG/LogicallyOrthogonalMesh.py b/SimPEG/LogicallyOrthogonalMesh.py index b49d595c..7369237c 100644 --- a/SimPEG/LogicallyOrthogonalMesh.py +++ b/SimPEG/LogicallyOrthogonalMesh.py @@ -10,7 +10,6 @@ class LogicallyOrthogonalMesh(BaseMesh, DiffOperators): # , LOMGrid """ def __init__(self, nodes, x0=None): - # Start with some error checking: assert type(nodes) == list, "'nodes' variable must be a list of np.ndarray" for i, nodes_i in enumerate(nodes): @@ -18,6 +17,7 @@ class LogicallyOrthogonalMesh(BaseMesh, DiffOperators): # , LOMGrid assert nodes_i.shape == nodes[0].shape, ("nodes[%i] is not the same shape as nodes[0]" % i) assert len(nodes[0].shape) == len(nodes), "Dimension mismatch" + assert len(nodes[0].shape) > 1, "Not worth using LOM for a 1D mesh." super(LogicallyOrthogonalMesh, self).__init__(np.array(nodes[0].shape)-1, x0) @@ -51,71 +51,27 @@ class LogicallyOrthogonalMesh(BaseMesh, DiffOperators): # , LOMGrid _gridN = None # Store grid by default gridN = property(**gridN()) - def gridFx(): - doc = "Face staggered grid in the x direction." + # --------------- Geometries --------------------- + def vol(): + doc = "Construct cell volumes of the 3D model as 1d array." def fget(self): - if self._gridFx is None: - self._gridFx = ndgrid([x for x in [self.vectorNx, self.vectorCCy, self.vectorCCz] if not x is None]) - return self._gridFx + if(self._vol is None): + vh = self.h + # Compute cell volumes + if(self.dim == 1): + self._vol = mkvc(vh[0]) + elif(self.dim == 2): + # Cell sizes in each direction + self._vol = mkvc(np.outer(vh[0], vh[1])) + elif(self.dim == 3): + # Cell sizes in each direction + self._vol = mkvc(np.outer(mkvc(np.outer(vh[0], vh[1])), vh[2])) + return self._vol return locals() - _gridFx = None # Store grid by default - gridFx = property(**gridFx()) + _vol = None + vol = property(**vol()) - def gridFy(): - doc = "Face staggered grid in the y direction." - - def fget(self): - if self._gridFy is None: - self._gridFy = ndgrid([x for x in [self.vectorCCx, self.vectorNy, self.vectorCCz] if not x is None]) - return self._gridFy - return locals() - _gridFy = None # Store grid by default - gridFy = property(**gridFy()) - - def gridFz(): - doc = "Face staggered grid in the z direction." - - def fget(self): - if self._gridFz is None: - self._gridFz = ndgrid([x for x in [self.vectorCCx, self.vectorCCy, self.vectorNz] if not x is None]) - return self._gridFz - return locals() - _gridFz = None # Store grid by default - gridFz = property(**gridFz()) - - def gridEx(): - doc = "Edge staggered grid in the x direction." - - def fget(self): - if self._gridEx is None: - self._gridEx = ndgrid([x for x in [self.vectorCCx, self.vectorNy, self.vectorNz] if not x is None]) - return self._gridEx - return locals() - _gridEx = None # Store grid by default - gridEx = property(**gridEx()) - - def gridEy(): - doc = "Edge staggered grid in the y direction." - - def fget(self): - if self._gridEy is None: - self._gridEy = ndgrid([x for x in [self.vectorNx, self.vectorCCy, self.vectorNz] if not x is None]) - return self._gridEy - return locals() - _gridEy = None # Store grid by default - gridEy = property(**gridEy()) - - def gridEz(): - doc = "Edge staggered grid in the z direction." - - def fget(self): - if self._gridEz is None: - self._gridEz = ndgrid([x for x in [self.vectorNx, self.vectorNy, self.vectorCCz] if not x is None]) - return self._gridEz - return locals() - _gridEz = None # Store grid by default - gridEz = property(**gridEz()) if __name__ == '__main__': nc = 5 @@ -125,5 +81,5 @@ if __name__ == '__main__': h = [h1, h2, h3] X, Y, Z = ndgrid(h1, h2, h3, vector=False) M = LogicallyOrthogonalMesh([X, Y, Z]) - print M.gridCC[:,0] - print M.gridN[:,0] + print M.r(M.gridCC, format='M') + print M.gridN[:, 0] diff --git a/SimPEG/utils.py b/SimPEG/utils.py index fdeb2742..84bf11bf 100644 --- a/SimPEG/utils.py +++ b/SimPEG/utils.py @@ -93,3 +93,33 @@ def ndgrid(*args, **kwargs): return np.c_[X1, X2, X3] else: return XYZ[2], XYZ[1], XYZ[0] + + +def volTetra(xyz, A, B, C, D): + """ + Returns the volume for tetrahedras volume specified by the indexes A to D. + + + Input: + xyz - X,Y,Z vertex vector + A,B,C,D - vert index of the tetrahedra + + Output: + V - volume + + Algorithm: http://en.wikipedia.org/wiki/Tetrahedron#Volume + + V = 1/3 A * h + + V = 1/6 | ( a - d ) o ( ( b - d ) X ( c - d ) ) | + + """ + + AD = xyz[A, :] - xyz[D, :] + BD = xyz[B, :] - xyz[D, :] + CD = xyz[C, :] - xyz[D, :] + + + + V = (BD[:, 0]*CD[:, 1] - BD[:, 1]*CD[:, 0])*AD[:, 2] - (BD[:, 0]*CD[:, 2] - BD[:, 2]*CD[:, 0])*AD[:, 1] + (BD[:, 1]*CD[:, 2] - BD[:, 2]*CD[:, 1])*AD[:, 0] + return V/6 From 150cbc7df3b7c94e4173629b5130776079289aaa Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Fri, 26 Jul 2013 12:11:45 -0700 Subject: [PATCH 06/51] Test for edge inner products working. --- SimPEG/getEdgeInnerProducts.py | 122 +++++++++++++++--------------- SimPEG/tests/test_massMatrices.py | 47 ++++++------ 2 files changed, 86 insertions(+), 83 deletions(-) diff --git a/SimPEG/getEdgeInnerProducts.py b/SimPEG/getEdgeInnerProducts.py index 5a95f146..d3b91211 100644 --- a/SimPEG/getEdgeInnerProducts.py +++ b/SimPEG/getEdgeInnerProducts.py @@ -6,7 +6,7 @@ from numpy import * from TensorMesh import * # [A] = getEdgeInnerProduct(X,Y,Z,sigma) -# +# # node(i,j,k+1) ------ edge2(i,j,k+1) ----- node(i,j+1,k+1) # / / @@ -35,147 +35,147 @@ from TensorMesh import * # 111 | i+1,j+1,k+1 | i ,j+1,k+1 | i+1,j ,k+1 | i+1,j+1,k -def subarray(T,i1,i2,i3): - return take(take(take(T,i1,0),i2,1),i3,2) - +def subarray(T, i1, i2, i3): + return take(take(take(T, i1, 0), i2, 1), i3, 2) -def getEdgeInnerProduct(mesh,sigma): + +def getEdgeInnerProduct(mesh, sigma): h = mesh.h - m = array([size(h[0]),size(h[1]),size(h[2])]) + m = array([size(h[0]), size(h[1]), size(h[2])]) nc = prod(m) - + me1 = m + array([0, 1, 1]); ne1 = prod(me1) me2 = m + array([1, 0, 1]); ne2 = prod(me2) me3 = m + array([1, 1, 0]); ne3 = prod(me3) - + i = int64(linspace(0,m[0]-1,m[0])) j = int64(linspace(0,m[1]-1,m[1])) k = int64(linspace(0,m[2]-1,m[2])) - - ii,jj,kk = ndgrid(i,j,k,vector=False) + + ii,jj,kk = ndgrid(i,j,k,vector=False) ii = mkvc(ii); jj = mkvc(jj); kk = mkvc(kk) - + ## -------- # no | node | e1 | e2 | e3 # 000 | i ,j ,k | i ,j ,k | i ,j ,k | i ,j ,k - ind1 = sub2ind(me1,c_[ii,jj,kk]) + ind1 = sub2ind(me1,c_[ii,jj,kk]) ind2 = sub2ind(me2,c_[ii,jj,kk]) + ne1 ind3 = sub2ind(me3,c_[ii,jj,kk]) + ne1 + ne2 - + IND = vstack((vstack((ind1,ind2)),ind3)) IND = array(IND).flatten() - + P000 = sparse.coo_matrix((ones(3*nc),(linspace(0,3*nc-1,3*nc),IND)),shape=(3*nc,ne1+ne2+ne3)).tocsr() - + ## -------- # no | node | e1 | e2 | e3 # 100 | i+1,j ,k | i ,j ,k | i+1,j ,k | i+1,j ,k - ind1 = sub2ind(me1,c_[ii,jj,kk]) + ind1 = sub2ind(me1,c_[ii,jj,kk]) ind2 = sub2ind(me2,c_[ii+1,jj,kk]) + ne1 ind3 = sub2ind(me3,c_[ii+1,jj,kk]) + ne1 + ne2 - + IND = vstack((vstack((ind1,ind2)),ind3)) IND = array(IND).flatten() - + P100 = sparse.coo_matrix((ones(3*nc),(linspace(0,3*nc-1,3*nc),IND)),shape=(3*nc,ne1+ne2+ne3)).tocsr() - + ## -------- # no | node | e1 | e2 | e3 # 010 | i ,j+1,k | i ,j+1,k | i ,j ,k | i ,j+1,k - ind1 = sub2ind(me1,c_[ii,jj+1,kk]) + ind1 = sub2ind(me1,c_[ii,jj+1,kk]) ind2 = sub2ind(me2,c_[ii,jj,kk]) + ne1 ind3 = sub2ind(me3,c_[ii,jj+1,kk]) + ne1 + ne2 - + IND = vstack((vstack((ind1,ind2)),ind3)) IND = array(IND).flatten() - + P010 = sparse.coo_matrix((ones(3*nc),(linspace(0,3*nc-1,3*nc),IND)),shape=(3*nc,ne1+ne2+ne3)).tocsr() - + ## -------- # no | node | e1 | e2 | e3 # 110 | i+1,j+1,k | i ,j+1,k | i+1,j ,k | i+1,j+1,k - ind1 = sub2ind(me1,c_[ii,jj+1,kk]) + ind1 = sub2ind(me1,c_[ii,jj+1,kk]) ind2 = sub2ind(me2,c_[ii+1,jj,kk]) + ne1 ind3 = sub2ind(me3,c_[ii+1,jj+1,kk]) + ne1 + ne2 - + IND = vstack((vstack((ind1,ind2)),ind3)) IND = array(IND).flatten() - + P110 = sparse.coo_matrix((ones(3*nc),(linspace(0,3*nc-1,3*nc),IND)),shape=(3*nc,ne1+ne2+ne3)).tocsr() - + ###### - + ## -------- # no | node | e1 | e2 | e3 # 001 | i ,j ,k+1 | i ,j ,k+1 | i ,j ,k+1 | i ,j ,k - ind1 = sub2ind(me1,c_[ii,jj,kk+1]) + ind1 = sub2ind(me1,c_[ii,jj,kk+1]) ind2 = sub2ind(me2,c_[ii,jj,kk+1]) + ne1 ind3 = sub2ind(me3,c_[ii,jj,kk]) + ne1 + ne2 - + IND = vstack((vstack((ind1,ind2)),ind3)) IND = array(IND).flatten() - + P001 = sparse.coo_matrix((ones(3*nc),(linspace(0,3*nc-1,3*nc),IND)),shape=(3*nc,ne1+ne2+ne3)).tocsr() - + ## -------- # no | node | e1 | e2 | e3 # 101 | i+1,j ,k+1 | i ,j ,k+1 | i+1,j ,k+1 | i+1,j ,k+1 - ind1 = sub2ind(me1,c_[ii,jj,kk+1]) + ind1 = sub2ind(me1,c_[ii,jj,kk+1]) ind2 = sub2ind(me2,c_[ii+1,jj,kk+1]) + ne1 ind3 = sub2ind(me3,c_[ii+1,jj,kk]) + ne1 + ne2 - + IND = vstack((vstack((ind1,ind2)),ind3)) IND = array(IND).flatten() - + P101 = sparse.coo_matrix((ones(3*nc),(linspace(0,3*nc-1,3*nc),IND)),shape=(3*nc,ne1+ne2+ne3)).tocsr() - + ## -------- # no | node | e1 | e2 | e3 # 011 | i ,j+1,k+1 | i ,j+1,k+1 | i ,j ,k+1 | i ,j+1,k+1 - ind1 = sub2ind(me1,c_[ii,jj+1,kk+1]) + ind1 = sub2ind(me1,c_[ii,jj+1,kk+1]) ind2 = sub2ind(me2,c_[ii,jj,kk+1]) + ne1 ind3 = sub2ind(me3,c_[ii,jj+1,kk]) + ne1 + ne2 - + IND = vstack((vstack((ind1,ind2)),ind3)) IND = array(IND).flatten() - + P011 = sparse.coo_matrix((ones(3*nc),(linspace(0,3*nc-1,3*nc),IND)),shape=(3*nc,ne1+ne2+ne3)).tocsr() - + ## -------- # no | node | e1 | e2 | e3 # 111 | i+1,j+1,k+1 | i ,j+1,k+1 | i+1,j ,k+1 | i+1,j+1,k+1 - ind1 = sub2ind(me1,c_[ii,jj+1,kk+1]) + ind1 = sub2ind(me1,c_[ii,jj+1,kk+1]) ind2 = sub2ind(me2,c_[ii+1,jj,kk+1]) + ne1 ind3 = sub2ind(me3,c_[ii+1,jj+1,kk]) + ne1 + ne2 - + IND = vstack((vstack((ind1,ind2)),ind3)) IND = array(IND).flatten() - + P111 = sparse.coo_matrix((ones(3*nc),(linspace(0,3*nc-1,3*nc),IND)),shape=(3*nc,ne1+ne2+ne3)).tocsr() - - - + + + # Cell volume - v = sqrt(mesh.vol) - row1 = sp.hstack((sdiag(sigma[:,0]),sdiag(sigma[:,3]),sdiag(sigma[:,4]))) - row2 = sp.hstack((sdiag(sigma[:,3]),sdiag(sigma[:,1]),sdiag(sigma[:,5]))) - row3 = sp.hstack((sdiag(sigma[:,4]),sdiag(sigma[:,5]),sdiag(sigma[:,2]))) + row1 = sp.hstack((sdiag(sigma[:, 0]), sdiag(sigma[:, 3]), sdiag(sigma[:, 4]))) + row2 = sp.hstack((sdiag(sigma[:, 3]), sdiag(sigma[:, 1]), sdiag(sigma[:, 5]))) + row3 = sp.hstack((sdiag(sigma[:, 4]), sdiag(sigma[:, 5]), sdiag(sigma[:, 2]))) Sigma = sp.vstack((row1, row2, row3)) - - v3 = r_[v,v,v] + + v = sqrt(mesh.vol) + v3 = r_[v, v, v] V = sdiag(v3)*Sigma*sdiag(v3) - - A = P000.T*V*P000 + P001.T*V*P001 + P010.T*V*P010 + P011.T*V*P011 + P100.T*V*P100 + P101.T*V*P101 + P110.T*V*P110 + P111.T*V*P111 - + + A = P000.T*V*P000 + P001.T*V*P001 + P010.T*V*P010 + P011.T*V*P011 + P100.T*V*P100 + P101.T*V*P101 + P110.T*V*P110 + P111.T*V*P111 + A = 0.125*A - + return A - - + + if __name__ == '__main__': - h = [array([1,2,3,4]),array([1,2,1,4,2]),array([1,1,4,1])] + h = [array([1, 2, 3, 4]), array([1, 2, 1, 4, 2]), array([1, 1, 4, 1])] mesh = TensorMesh(h) - sigma = ones((mesh.nC,6)) - A = getEdgeInnerProduct(mesh,sigma) \ No newline at end of file + sigma = ones((mesh.nC, 6)) + A = getEdgeInnerProduct(mesh, sigma) diff --git a/SimPEG/tests/test_massMatrices.py b/SimPEG/tests/test_massMatrices.py index 08fa4caf..68ed8516 100644 --- a/SimPEG/tests/test_massMatrices.py +++ b/SimPEG/tests/test_massMatrices.py @@ -2,18 +2,21 @@ import numpy as np import unittest import sys sys.path.append('../') -from TensorMesh import TensorMesh from OrderTest import OrderTest -from scipy.sparse.linalg import dsolve -from getEdgeInnerProducts import getEdgeInnerProducts +from getEdgeInnerProducts import * -class TestNodalGrad(OrderTest): - name = "Nodal Gradient" - +class TestEdgeInnerProduct(OrderTest): + """Integrate a function over a unit cube domain.""" + + name = "Edge Inner Product" + meshSizes = [4, 8, 16, 32] def getError(self): + + call = lambda fun, xyz: fun(xyz[:, 0], xyz[:, 1], xyz[:, 2]) + ex = lambda x, y, z: x**2+y*z ey = lambda x, y, z: (z**2)*x+y*z ez = lambda x, y, z: y**2+x*z @@ -24,25 +27,25 @@ class TestNodalGrad(OrderTest): sigma4 = lambda x, y, z: 0.1*x*y*z sigma5 = lambda x, y, z: 0.2*x*y sigma6 = lambda x, y, z: 0.1*z - - Ex = ex(self.M.gridEx[:, 0],self.M.gridEx[:, 1],self.M.gridEx[:, 2]) - Ey = ey(self.M.gridEy[:, 0],self.M.gridEy[:, 1],self.M.gridEy[:, 2]) - Ez = ez(self.M.gridEz[:, 0],self.M.gridEz[:, 1],self.M.gridEz[:, 2]) - - E = np.r_[Ex,Ey,Ez] + + Ex = call(ex, self.M.gridEx) + Ey = call(ey, self.M.gridEy) + Ez = call(ez, self.M.gridEz) + + E = np.matrix(mkvc(np.r_[Ex, Ey, Ez], 2)) Gc = self.M.gridCC - sigma = np.c_[sigma1(Gc[:,0],Gc[:,1],Gc[:,2]), - sigma2(Gc[:,0],Gc[:,1],Gc[:,2]), - sigma3(Gc[:,0],Gc[:,1],Gc[:,2]), - sigma4(Gc[:,0],Gc[:,1],Gc[:,2]), - sigma5(Gc[:,0],Gc[:,1],Gc[:,2]), - sigma6(Gc[:,0],Gc[:,1],Gc[:,2])] - - A = getEdgeInnerProducts(self.M, sigma) - - err = np.abs(E.T*A*E - 69881./21600) + sigma = np.c_[call(sigma1, Gc), call(sigma2, Gc), call(sigma3, Gc), + call(sigma4, Gc), call(sigma5, Gc), call(sigma6, Gc)] + A = getEdgeInnerProduct(self.M, sigma) + numeric = E.T*A*E + analytic = 69881./21600 # Found using matlab symbolic toolbox. + err = np.abs(numeric - analytic) return err def test_order(self): self.orderTest() + + +if __name__ == '__main__': + unittest.main() From 90bf6d31390d6d89e8ae795cd46b9eae1f622eeb Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Fri, 26 Jul 2013 13:56:05 -0700 Subject: [PATCH 07/51] Refactored and cleaned up code. --- SimPEG/getEdgeInnerProducts.py | 198 ++++++++---------------------- SimPEG/tests/test_massMatrices.py | 2 +- 2 files changed, 51 insertions(+), 149 deletions(-) diff --git a/SimPEG/getEdgeInnerProducts.py b/SimPEG/getEdgeInnerProducts.py index d3b91211..858d5a72 100644 --- a/SimPEG/getEdgeInnerProducts.py +++ b/SimPEG/getEdgeInnerProducts.py @@ -1,160 +1,61 @@ -from scipy.sparse import linalg -from scipy import sparse -from sputils import * -from utils import * -from numpy import * -from TensorMesh import * - -# [A] = getEdgeInnerProduct(X,Y,Z,sigma) -# - -# node(i,j,k+1) ------ edge2(i,j,k+1) ----- node(i,j+1,k+1) -# / / -# / / | -# edge3(i,j,k) face1(i,j,k) edge3(i,j+1,k) -# / / | -# / / | -# node(i,j,k) ------ edge2(i,j,k) ----- node(i,j+1,k) -# | | | -# | | node(i+1,j+1,k+1) -# | | / -# edge1(i,j,k) face3(i,j,k) edge1(i,j+1,k) -# | | / -# | | / -# | |/ -# node(i+1,j,k) ------ edge2(i+1,j,k) ----- node(i+1,j+1,k) - -# no | node | e1 | e2 | e3 -# 000 | i ,j ,k | i ,j ,k | i ,j ,k | i ,j ,k -# 100 | i+1,j ,k | i ,j ,k | i+1,j ,k | i+1,j ,k -# 010 | i ,j+1,k | i ,j+1,k | i ,j ,k | i ,j+1,k -# 110 | i+1,j+1,k | i ,j+1,k | i+1,j ,k | i+1,j+1,k -# 001 | i ,j ,k+1 | i ,j ,k+1 | i ,j ,k+1 | i ,j ,k -# 101 | i+1,j ,k+1 | i ,j ,k+1 | i+1,j ,k+1 | i+1,j ,k -# 011 | i ,j+1,k+1 | i ,j+1,k+1 | i ,j ,k+1 | i ,j+1,k -# 111 | i+1,j+1,k+1 | i ,j+1,k+1 | i+1,j ,k+1 | i+1,j+1,k - - -def subarray(T, i1, i2, i3): - return take(take(take(T, i1, 0), i2, 1), i3, 2) +from scipy import sparse as sp +from sputils import sdiag +from utils import sub2ind, ndgrid +import numpy as np def getEdgeInnerProduct(mesh, sigma): - h = mesh.h - m = array([size(h[0]), size(h[1]), size(h[2])]) - nc = prod(m) + m = np.array([mesh.nCx, mesh.nCy, mesh.nCz]) + nc = mesh.nC - me1 = m + array([0, 1, 1]); ne1 = prod(me1) - me2 = m + array([1, 0, 1]); ne2 = prod(me2) - me3 = m + array([1, 1, 0]); ne3 = prod(me3) + i, j, k = np.int64(range(m[0])), np.int64(range(m[1])), np.int64(range(m[2])) - i = int64(linspace(0,m[0]-1,m[0])) - j = int64(linspace(0,m[1]-1,m[1])) - k = int64(linspace(0,m[2]-1,m[2])) + iijjkk = ndgrid(i, j, k) + ii, jj, kk = iijjkk[:, 0], iijjkk[:, 1], iijjkk[:, 2] - ii,jj,kk = ndgrid(i,j,k,vector=False) - ii = mkvc(ii); jj = mkvc(jj); kk = mkvc(kk) + def Pxxx(pos): + ind1 = sub2ind(mesh.nEx, np.c_[ii + pos[0][0], jj + pos[0][1], kk + pos[0][2]]) + ind2 = sub2ind(mesh.nEy, np.c_[ii + pos[1][0], jj + pos[1][1], kk + pos[1][2]]) + mesh.nE[0] + ind3 = sub2ind(mesh.nEz, np.c_[ii + pos[2][0], jj + pos[2][1], kk + pos[2][2]]) + mesh.nE[0] + mesh.nE[1] + + IND = np.r_[ind1, ind2, ind3].flatten() + + return sp.coo_matrix((np.ones(3*nc), (np.linspace(0, 3*nc-1, 3*nc), IND)), shape=(3*nc, np.sum(mesh.nE))).tocsr() + + # node(i,j,k+1) ------ edge2(i,j,k+1) ----- node(i,j+1,k+1) + # / / + # / / | + # edge3(i,j,k) face1(i,j,k) edge3(i,j+1,k) + # / / | + # / / | + # node(i,j,k) ------ edge2(i,j,k) ----- node(i,j+1,k) + # | | | + # | | node(i+1,j+1,k+1) + # | | / + # edge1(i,j,k) face3(i,j,k) edge1(i,j+1,k) + # | | / + # | | / + # | |/ + # node(i+1,j,k) ------ edge2(i+1,j,k) ----- node(i+1,j+1,k) - ## -------- # no | node | e1 | e2 | e3 # 000 | i ,j ,k | i ,j ,k | i ,j ,k | i ,j ,k - ind1 = sub2ind(me1,c_[ii,jj,kk]) - ind2 = sub2ind(me2,c_[ii,jj,kk]) + ne1 - ind3 = sub2ind(me3,c_[ii,jj,kk]) + ne1 + ne2 - - IND = vstack((vstack((ind1,ind2)),ind3)) - IND = array(IND).flatten() - - P000 = sparse.coo_matrix((ones(3*nc),(linspace(0,3*nc-1,3*nc),IND)),shape=(3*nc,ne1+ne2+ne3)).tocsr() - - ## -------- - # no | node | e1 | e2 | e3 # 100 | i+1,j ,k | i ,j ,k | i+1,j ,k | i+1,j ,k - ind1 = sub2ind(me1,c_[ii,jj,kk]) - ind2 = sub2ind(me2,c_[ii+1,jj,kk]) + ne1 - ind3 = sub2ind(me3,c_[ii+1,jj,kk]) + ne1 + ne2 - - IND = vstack((vstack((ind1,ind2)),ind3)) - IND = array(IND).flatten() - - P100 = sparse.coo_matrix((ones(3*nc),(linspace(0,3*nc-1,3*nc),IND)),shape=(3*nc,ne1+ne2+ne3)).tocsr() - - ## -------- - # no | node | e1 | e2 | e3 # 010 | i ,j+1,k | i ,j+1,k | i ,j ,k | i ,j+1,k - ind1 = sub2ind(me1,c_[ii,jj+1,kk]) - ind2 = sub2ind(me2,c_[ii,jj,kk]) + ne1 - ind3 = sub2ind(me3,c_[ii,jj+1,kk]) + ne1 + ne2 - - IND = vstack((vstack((ind1,ind2)),ind3)) - IND = array(IND).flatten() - - P010 = sparse.coo_matrix((ones(3*nc),(linspace(0,3*nc-1,3*nc),IND)),shape=(3*nc,ne1+ne2+ne3)).tocsr() - - ## -------- - # no | node | e1 | e2 | e3 # 110 | i+1,j+1,k | i ,j+1,k | i+1,j ,k | i+1,j+1,k - ind1 = sub2ind(me1,c_[ii,jj+1,kk]) - ind2 = sub2ind(me2,c_[ii+1,jj,kk]) + ne1 - ind3 = sub2ind(me3,c_[ii+1,jj+1,kk]) + ne1 + ne2 - - IND = vstack((vstack((ind1,ind2)),ind3)) - IND = array(IND).flatten() - - P110 = sparse.coo_matrix((ones(3*nc),(linspace(0,3*nc-1,3*nc),IND)),shape=(3*nc,ne1+ne2+ne3)).tocsr() - - ###### - - ## -------- - # no | node | e1 | e2 | e3 - # 001 | i ,j ,k+1 | i ,j ,k+1 | i ,j ,k+1 | i ,j ,k - ind1 = sub2ind(me1,c_[ii,jj,kk+1]) - ind2 = sub2ind(me2,c_[ii,jj,kk+1]) + ne1 - ind3 = sub2ind(me3,c_[ii,jj,kk]) + ne1 + ne2 - - IND = vstack((vstack((ind1,ind2)),ind3)) - IND = array(IND).flatten() - - P001 = sparse.coo_matrix((ones(3*nc),(linspace(0,3*nc-1,3*nc),IND)),shape=(3*nc,ne1+ne2+ne3)).tocsr() - - ## -------- - # no | node | e1 | e2 | e3 - # 101 | i+1,j ,k+1 | i ,j ,k+1 | i+1,j ,k+1 | i+1,j ,k+1 - ind1 = sub2ind(me1,c_[ii,jj,kk+1]) - ind2 = sub2ind(me2,c_[ii+1,jj,kk+1]) + ne1 - ind3 = sub2ind(me3,c_[ii+1,jj,kk]) + ne1 + ne2 - - IND = vstack((vstack((ind1,ind2)),ind3)) - IND = array(IND).flatten() - - P101 = sparse.coo_matrix((ones(3*nc),(linspace(0,3*nc-1,3*nc),IND)),shape=(3*nc,ne1+ne2+ne3)).tocsr() - - ## -------- - # no | node | e1 | e2 | e3 - # 011 | i ,j+1,k+1 | i ,j+1,k+1 | i ,j ,k+1 | i ,j+1,k+1 - ind1 = sub2ind(me1,c_[ii,jj+1,kk+1]) - ind2 = sub2ind(me2,c_[ii,jj,kk+1]) + ne1 - ind3 = sub2ind(me3,c_[ii,jj+1,kk]) + ne1 + ne2 - - IND = vstack((vstack((ind1,ind2)),ind3)) - IND = array(IND).flatten() - - P011 = sparse.coo_matrix((ones(3*nc),(linspace(0,3*nc-1,3*nc),IND)),shape=(3*nc,ne1+ne2+ne3)).tocsr() - - ## -------- - # no | node | e1 | e2 | e3 - # 111 | i+1,j+1,k+1 | i ,j+1,k+1 | i+1,j ,k+1 | i+1,j+1,k+1 - ind1 = sub2ind(me1,c_[ii,jj+1,kk+1]) - ind2 = sub2ind(me2,c_[ii+1,jj,kk+1]) + ne1 - ind3 = sub2ind(me3,c_[ii+1,jj+1,kk]) + ne1 + ne2 - - IND = vstack((vstack((ind1,ind2)),ind3)) - IND = array(IND).flatten() - - P111 = sparse.coo_matrix((ones(3*nc),(linspace(0,3*nc-1,3*nc),IND)),shape=(3*nc,ne1+ne2+ne3)).tocsr() - - + # 001 | i ,j ,k+1 | i ,j ,k+1 | i ,j ,k+1 | i ,j ,k + # 101 | i+1,j ,k+1 | i ,j ,k+1 | i+1,j ,k+1 | i+1,j ,k + # 011 | i ,j+1,k+1 | i ,j+1,k+1 | i ,j ,k+1 | i ,j+1,k + # 111 | i+1,j+1,k+1 | i ,j+1,k+1 | i+1,j ,k+1 | i+1,j+1,k + P000 = Pxxx([[0, 0, 0], [0, 0, 0], [0, 0, 0]]) + P100 = Pxxx([[0, 0, 0], [1, 0, 0], [1, 0, 0]]) + P010 = Pxxx([[0, 1, 0], [0, 0, 0], [0, 1, 0]]) + P110 = Pxxx([[0, 1, 0], [1, 0, 0], [1, 1, 0]]) + P001 = Pxxx([[0, 0, 1], [0, 0, 1], [0, 0, 0]]) + P101 = Pxxx([[0, 0, 1], [1, 0, 1], [1, 0, 0]]) + P011 = Pxxx([[0, 1, 1], [0, 0, 1], [0, 1, 0]]) + P111 = Pxxx([[0, 1, 1], [1, 0, 1], [1, 1, 0]]) # Cell volume row1 = sp.hstack((sdiag(sigma[:, 0]), sdiag(sigma[:, 3]), sdiag(sigma[:, 4]))) @@ -162,8 +63,8 @@ def getEdgeInnerProduct(mesh, sigma): row3 = sp.hstack((sdiag(sigma[:, 4]), sdiag(sigma[:, 5]), sdiag(sigma[:, 2]))) Sigma = sp.vstack((row1, row2, row3)) - v = sqrt(mesh.vol) - v3 = r_[v, v, v] + v = np.sqrt(mesh.vol) + v3 = np.r_[v, v, v] V = sdiag(v3)*Sigma*sdiag(v3) A = P000.T*V*P000 + P001.T*V*P001 + P010.T*V*P010 + P011.T*V*P011 + P100.T*V*P100 + P101.T*V*P101 + P110.T*V*P110 + P111.T*V*P111 @@ -175,7 +76,8 @@ def getEdgeInnerProduct(mesh, sigma): if __name__ == '__main__': - h = [array([1, 2, 3, 4]), array([1, 2, 1, 4, 2]), array([1, 1, 4, 1])] + from TensorMesh import * + h = [np.array([1, 2, 3, 4]), np.array([1, 2, 1, 4, 2]), np.array([1, 1, 4, 1])] mesh = TensorMesh(h) - sigma = ones((mesh.nC, 6)) + sigma = np.ones((mesh.nC, 6)) A = getEdgeInnerProduct(mesh, sigma) diff --git a/SimPEG/tests/test_massMatrices.py b/SimPEG/tests/test_massMatrices.py index 68ed8516..87310a47 100644 --- a/SimPEG/tests/test_massMatrices.py +++ b/SimPEG/tests/test_massMatrices.py @@ -32,7 +32,7 @@ class TestEdgeInnerProduct(OrderTest): Ey = call(ey, self.M.gridEy) Ez = call(ez, self.M.gridEz) - E = np.matrix(mkvc(np.r_[Ex, Ey, Ez], 2)) + E = np.matrix(np.r_[Ex, Ey, Ez]).T Gc = self.M.gridCC sigma = np.c_[call(sigma1, Gc), call(sigma2, Gc), call(sigma3, Gc), call(sigma4, Gc), call(sigma5, Gc), call(sigma6, Gc)] From c8633881fbdfeaaaa0cec92a60b24c155d1cf4a8 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Fri, 26 Jul 2013 14:19:44 -0700 Subject: [PATCH 08/51] Support for isotropic sigma --- SimPEG/getEdgeInnerProducts.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/SimPEG/getEdgeInnerProducts.py b/SimPEG/getEdgeInnerProducts.py index 858d5a72..7e123ab6 100644 --- a/SimPEG/getEdgeInnerProducts.py +++ b/SimPEG/getEdgeInnerProducts.py @@ -1,6 +1,6 @@ from scipy import sparse as sp from sputils import sdiag -from utils import sub2ind, ndgrid +from utils import sub2ind, ndgrid, mkvc import numpy as np @@ -21,7 +21,7 @@ def getEdgeInnerProduct(mesh, sigma): IND = np.r_[ind1, ind2, ind3].flatten() - return sp.coo_matrix((np.ones(3*nc), (np.linspace(0, 3*nc-1, 3*nc), IND)), shape=(3*nc, np.sum(mesh.nE))).tocsr() + return sp.coo_matrix((np.ones(3*nc), (range(3*nc), IND)), shape=(3*nc, np.sum(mesh.nE))).tocsr() # node(i,j,k+1) ------ edge2(i,j,k+1) ----- node(i,j+1,k+1) # / / @@ -57,15 +57,21 @@ def getEdgeInnerProduct(mesh, sigma): P011 = Pxxx([[0, 1, 1], [0, 0, 1], [0, 1, 0]]) P111 = Pxxx([[0, 1, 1], [1, 0, 1], [1, 1, 0]]) - # Cell volume - row1 = sp.hstack((sdiag(sigma[:, 0]), sdiag(sigma[:, 3]), sdiag(sigma[:, 4]))) - row2 = sp.hstack((sdiag(sigma[:, 3]), sdiag(sigma[:, 1]), sdiag(sigma[:, 5]))) - row3 = sp.hstack((sdiag(sigma[:, 4]), sdiag(sigma[:, 5]), sdiag(sigma[:, 2]))) - Sigma = sp.vstack((row1, row2, row3)) + if sigma.size == mesh.nC: # Isotropic! + sigma = mkvc(sigma) + Sigma = sdiag(np.r_[sigma, sigma, sigma]) + elif sigma.shape[1] == 3: # Diagonal tensor + Sigma = sdiag(np.r_[sigma[:, 0], sigma[:, 1], sigma[:, 2]]) + elif sigma.shape[1] == 6: # Fully anisotropic + row1 = sp.hstack((sdiag(sigma[:, 0]), sdiag(sigma[:, 3]), sdiag(sigma[:, 4]))) + row2 = sp.hstack((sdiag(sigma[:, 3]), sdiag(sigma[:, 1]), sdiag(sigma[:, 5]))) + row3 = sp.hstack((sdiag(sigma[:, 4]), sdiag(sigma[:, 5]), sdiag(sigma[:, 2]))) + Sigma = sp.vstack((row1, row2, row3)) + # Cell volume v = np.sqrt(mesh.vol) v3 = np.r_[v, v, v] - V = sdiag(v3)*Sigma*sdiag(v3) + V = sdiag(v3)*Sigma*sdiag(v3) # to keep symmetry A = P000.T*V*P000 + P001.T*V*P001 + P010.T*V*P010 + P011.T*V*P011 + P100.T*V*P100 + P101.T*V*P101 + P110.T*V*P110 + P111.T*V*P111 @@ -73,10 +79,8 @@ def getEdgeInnerProduct(mesh, sigma): return A - if __name__ == '__main__': - - from TensorMesh import * + from TensorMesh import TensorMesh h = [np.array([1, 2, 3, 4]), np.array([1, 2, 1, 4, 2]), np.array([1, 1, 4, 1])] mesh = TensorMesh(h) sigma = np.ones((mesh.nC, 6)) From 5c83095781f67e1c315bf2a7889ec09004964526 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Fri, 26 Jul 2013 14:30:04 -0700 Subject: [PATCH 09/51] Minor changes and comments. --- SimPEG/getEdgeInnerProducts.py | 2 +- SimPEG/tests/test_massMatrices.py | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/SimPEG/getEdgeInnerProducts.py b/SimPEG/getEdgeInnerProducts.py index 7e123ab6..68423a3f 100644 --- a/SimPEG/getEdgeInnerProducts.py +++ b/SimPEG/getEdgeInnerProducts.py @@ -58,7 +58,7 @@ def getEdgeInnerProduct(mesh, sigma): P111 = Pxxx([[0, 1, 1], [1, 0, 1], [1, 1, 0]]) if sigma.size == mesh.nC: # Isotropic! - sigma = mkvc(sigma) + sigma = mkvc(sigma) # ensure it is a vector. Sigma = sdiag(np.r_[sigma, sigma, sigma]) elif sigma.shape[1] == 3: # Diagonal tensor Sigma = sdiag(np.r_[sigma[:, 0], sigma[:, 1], sigma[:, 2]]) diff --git a/SimPEG/tests/test_massMatrices.py b/SimPEG/tests/test_massMatrices.py index 87310a47..0aae0615 100644 --- a/SimPEG/tests/test_massMatrices.py +++ b/SimPEG/tests/test_massMatrices.py @@ -1,18 +1,16 @@ import numpy as np import unittest +from OrderTest import OrderTest import sys sys.path.append('../') -from OrderTest import OrderTest from getEdgeInnerProducts import * class TestEdgeInnerProduct(OrderTest): - """Integrate a function over a unit cube domain.""" + """Integrate an edge function over a unit cube domain using edgeInnerProducts.""" name = "Edge Inner Product" - meshSizes = [4, 8, 16, 32] - def getError(self): call = lambda fun, xyz: fun(xyz[:, 0], xyz[:, 1], xyz[:, 2]) From fe547476c9ea0b7855df80b811b7d83fa304b03c Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Fri, 26 Jul 2013 15:56:21 -0700 Subject: [PATCH 10/51] IndexCube now in Utils --- SimPEG/tests/test_utils.py | 33 +++++++++++----- SimPEG/utils.py | 77 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 10 deletions(-) diff --git a/SimPEG/tests/test_utils.py b/SimPEG/tests/test_utils.py index 5d05b710..59b70147 100644 --- a/SimPEG/tests/test_utils.py +++ b/SimPEG/tests/test_utils.py @@ -2,7 +2,7 @@ import numpy as np import unittest import sys sys.path.append('../') -from utils import mkvc, ndgrid +from utils import mkvc, ndgrid, indexCube class TestSequenceFunctions(unittest.TestCase): @@ -30,10 +30,8 @@ class TestSequenceFunctions(unittest.TestCase): X1_test = np.array([1, 2, 3, 1, 2, 3]) X2_test = np.array([1, 1, 1, 2, 2, 2]) - xtest = np.all(XY[:, 0] == X1_test) - ytest = np.all(XY[:, 1] == X2_test) - - self.assertTrue(xtest and ytest) + self.assertTrue(np.all(XY[:, 0] == X1_test)) + self.assertTrue(np.all(XY[:, 1] == X2_test)) def test_ndgrid_3D(self): XYZ = ndgrid([self.a, self.b, self.c]) @@ -42,12 +40,27 @@ class TestSequenceFunctions(unittest.TestCase): X2_test = np.array([1, 1, 1, 2, 2, 2, 1, 1, 1, 2, 2, 2, 1, 1, 1, 2, 2, 2, 1, 1, 1, 2, 2, 2]) X3_test = np.array([1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4]) - xtest = np.all(XYZ[:, 0] == X1_test) - ytest = np.all(XYZ[:, 1] == X2_test) - ztest = np.all(XYZ[:, 2] == X3_test) + self.assertTrue(np.all(XYZ[:, 0] == X1_test)) + self.assertTrue(np.all(XYZ[:, 1] == X2_test)) + self.assertTrue(np.all(XYZ[:, 2] == X3_test)) - self.assertTrue(xtest and ytest and ztest) + def test_indexCube_2D(self): + nN = np.array([3, 3]) + self.assertTrue(np.all(indexCube('A', nN) == np.array([0, 1, 3, 4]))) + self.assertTrue(np.all(indexCube('B', nN) == np.array([3, 4, 6, 7]))) + self.assertTrue(np.all(indexCube('C', nN) == np.array([4, 5, 7, 8]))) + self.assertTrue(np.all(indexCube('D', nN) == np.array([1, 2, 4, 5]))) + + def test_indexCube_3D(self): + nN = np.array([3, 3, 3]) + self.assertTrue(np.all(indexCube('A', nN) == np.array([0, 1, 3, 4, 9, 10, 12, 13]))) + self.assertTrue(np.all(indexCube('B', nN) == np.array([3, 4, 6, 7, 12, 13, 15, 16]))) + self.assertTrue(np.all(indexCube('C', nN) == np.array([4, 5, 7, 8, 13, 14, 16, 17]))) + self.assertTrue(np.all(indexCube('D', nN) == np.array([1, 2, 4, 5, 10, 11, 13, 14]))) + self.assertTrue(np.all(indexCube('E', nN) == np.array([9, 10, 12, 13, 18, 19, 21, 22]))) + self.assertTrue(np.all(indexCube('F', nN) == np.array([12, 13, 15, 16, 21, 22, 24, 25]))) + self.assertTrue(np.all(indexCube('G', nN) == np.array([13, 14, 16, 17, 22, 23, 25, 26]))) + self.assertTrue(np.all(indexCube('H', nN) == np.array([10, 11, 13, 14, 19, 20, 22, 23]))) if __name__ == '__main__': unittest.main() - diff --git a/SimPEG/utils.py b/SimPEG/utils.py index 55acf9b0..8aa7f27b 100644 --- a/SimPEG/utils.py +++ b/SimPEG/utils.py @@ -124,6 +124,83 @@ def volTetra(xyz, A, B, C, D): return V/6 +def indexCube(nodes, nN): + """ + Returns the index of nodes on the mesh. + + + Input: + nodes - string of which nodes to return. e.g. 'ABCD' + nN - size of the nodal grid + + Output: + index - index in the order asked e.g. 'ABCD' --> (A,B,C,D) + + TWO DIMENSIONS: + + node(i,j) node(i,j+1) + A -------------- B + | | + | cell(i,j) | + | I | + | | + D -------------- C + node(i+1,j) node(i+1,j+1) + + + THREE DIMENSIONS: + + node(i,j,k+1) node(i,j+1,k+1) + E --------------- F + /| / | + / | / | + / | / | + node(i,j,k) node(i,j+1,k) + A -------------- B | + | H ----------|---- G + | /cell(i,j) | / + | / I | / + | / | / + D -------------- C + node(i+1,j,k) node(i+1,j+1,k) + + + @author Rowan Cockett + + Last modified on: 2013/03/07 + """ + + assert type(nodes) == str, "Nodes must be a str variable: e.g. 'ABCD'" + assert type(nN) == np.ndarray, "Number of nodes must be an ndarray" + nodes = nodes.upper() + # Make sure that we choose from the possible nodes. + possibleNodes = 'ABCD' if nN.size == 2 else 'ABCDEFGH' + for node in nodes: + assert node in possibleNodes, "Nodes must be chosen from: '%s'" % possibleNodes + dim = nN.size + nC = nN - 1 + + if dim == 2: + ij = ndgrid(np.arange(nC[0]), np.arange(nC[1])) + i, j = ij[:, 0], ij[:, 1] + elif dim == 3: + ijk = ndgrid(np.arange(nC[0]), np.arange(nC[1]), np.arange(nC[2])) + i, j, k = ijk[:, 0], ijk[:, 1], ijk[:, 2] + else: + raise Exception('Only 2 and 3 dimensions supported.') + + nodeMap = {'A': [0, 0, 0], 'B': [0, 1, 0], 'C': [1, 1, 0], 'D': [1, 0, 0], + 'E': [0, 0, 1], 'F': [0, 1, 1], 'G': [1, 1, 1], 'H': [1, 0, 1]} + out = () + for node in nodes: + shift = nodeMap[node] + if dim == 2: + out += (sub2ind(nN, np.c_[i+shift[0], j+shift[1]]).flatten(), ) + elif dim == 3: + out += (sub2ind(nN, np.c_[i+shift[0], j+shift[1], k+shift[2]]).flatten(), ) + + return out + def getSubArray(A, ind): """subArray""" return A[ind[0], :, :][:, ind[1], :][:, :, ind[2]] From 079acb1153fb129a37f090caaeb14f63b58d9f80 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Fri, 26 Jul 2013 16:38:12 -0700 Subject: [PATCH 11/51] volume in 2D by calculating faceInfo (in utils for now.) --- SimPEG/LogicallyOrthogonalMesh.py | 61 +++++++++++++++++++---- SimPEG/utils.py | 83 ++++++++++++++++++++++++++++++- 2 files changed, 131 insertions(+), 13 deletions(-) diff --git a/SimPEG/LogicallyOrthogonalMesh.py b/SimPEG/LogicallyOrthogonalMesh.py index 7369237c..5bb5482b 100644 --- a/SimPEG/LogicallyOrthogonalMesh.py +++ b/SimPEG/LogicallyOrthogonalMesh.py @@ -1,7 +1,7 @@ import numpy as np from BaseMesh import BaseMesh from DiffOperators import DiffOperators -from utils import mkvc, ndgrid +from utils import mkvc, ndgrid, volTetra, indexCube, faceInfo class LogicallyOrthogonalMesh(BaseMesh, DiffOperators): # , LOMGrid @@ -52,21 +52,60 @@ class LogicallyOrthogonalMesh(BaseMesh, DiffOperators): # , LOMGrid gridN = property(**gridN()) # --------------- Geometries --------------------- + # + # + # ------------------- 2D ------------------------- + # + # node(i,j) node(i,j+1) + # A -------------- B + # | | + # | cell(i,j) | + # | I | + # | | + # D -------------- C + # node(i+1,j) node(i+1,j+1) + # + # ------------------- 3D ------------------------- + # + # + # node(i,j,k+1) node(i,j+1,k+1) + # E --------------- F + # /| / | + # / | / | + # / | / | + # node(i,j,k) node(i,j+1,k) + # A -------------- B | + # | H ----------|---- G + # | /cell(i,j) | / + # | / I | / + # | / | / + # D -------------- C + # node(i+1,j,k) node(i+1,j+1,k) def vol(): doc = "Construct cell volumes of the 3D model as 1d array." def fget(self): if(self._vol is None): - vh = self.h - # Compute cell volumes - if(self.dim == 1): - self._vol = mkvc(vh[0]) - elif(self.dim == 2): - # Cell sizes in each direction - self._vol = mkvc(np.outer(vh[0], vh[1])) - elif(self.dim == 3): - # Cell sizes in each direction - self._vol = mkvc(np.outer(mkvc(np.outer(vh[0], vh[1])), vh[2])) + if self.dim == 2: + A, B, C, D = indexCube('ABCD', np.array([self.nNx, self.nNy])) + normal, area, length = faceInfo(np.c_[self.gridN, np.zeros((self.nC, 1))], A, B, C, D) + self._vol = area + elif self.dim == 3: + # Each polyhedron can be decomposed into 5 tetrahedrons + # T1 = [A B D E]; % cutted edge + # T2 = [B E F G]; % cutted edge + # T3 = [B D E G]; % mid + # T4 = [B C D G]; % cutted edge + # T5 = [D E G H]; % cutted edge + A, B, C, D, E, F, G, H = indexCube('ABCDEFGH', np.array([self.nNx, self.nNy, self.nNz])) + + v1 = volTetra(self.gridN, A, B, D, E) # cutted edge + v2 = volTetra(self.gridN, B, E, F, G) # cutted edge + v3 = volTetra(self.gridN, B, D, E, G) # mid + v4 = volTetra(self.gridN, B, C, D, G) # cutted edge + v5 = volTetra(self.gridN, D, E, G, H) # cutted edge + + self._vol = v1 + v2 + v3 + v4 + v5 return self._vol return locals() _vol = None diff --git a/SimPEG/utils.py b/SimPEG/utils.py index 8aa7f27b..37441e8e 100644 --- a/SimPEG/utils.py +++ b/SimPEG/utils.py @@ -126,7 +126,7 @@ def volTetra(xyz, A, B, C, D): def indexCube(nodes, nN): """ - Returns the index of nodes on the mesh. + Returns the index of nodes on the mesh. Input: @@ -167,7 +167,7 @@ def indexCube(nodes, nN): @author Rowan Cockett - Last modified on: 2013/03/07 + Last modified on: 2013/07/26 """ assert type(nodes) == str, "Nodes must be a str variable: e.g. 'ABCD'" @@ -201,6 +201,85 @@ def indexCube(nodes, nN): return out + +def faceInfo(xyz, A, B, C, D, average=True): + """ + function [N] = faceInfo(y,A,B,C,D) + + Returns the averaged normal, area, and edge lengths for a given set of faces. + + If average option is FALSE then N is a cell array {nA,nB,nC,nD} + + + Input: + xyz - X,Y,Z vertex vector + A,B,C,D - vert index of the face (counter clockwize) + + Options: + average - [true]/false, toggles returning all normals or the average + + Output: + N - average face normal or {nA,nB,nC,nD} if average = false + area - average face area + edgeLengths - exact edge Lengths, 4 column vector [AB, BC, CD, DA] + + see also testFaceNormal testFaceArea + + @author Rowan Cockett + + Last modified on: 2013/07/26 + + """ + + # compute normal that is pointing away from you. + # + # A -------A-B------- B + # | | + # | | + # D-A (X) B-C + # | | + # | | + # D -------C-D------- C + + AB = xyz[B, :] - xyz[A, :] + BC = xyz[C, :] - xyz[B, :] + CD = xyz[D, :] - xyz[C, :] + DA = xyz[A, :] - xyz[D, :] + + def cross(X, Y): + return np.c_[X[1, :]*Y[2, :] - X[2, :]*Y[1, :], + X[2, :]*Y[0, :] - X[0, :]*Y[2, :], + X[0, :]*Y[1, :] - X[1, :]*Y[0, :]] + + nA = cross(AB, DA) + nB = cross(BC, AB) + nC = cross(CD, BC) + nD = cross(DA, CD) + + length = lambda x: (x[:, 0]**2 + x[:, 1]**2 + x[:, 2]**2)**0.5 + normalize = lambda x: x/np.kron(np.ones((1, x.shape[1]), length(x), 1)) + if average: + # average the normals at each vertex. + N = (nA + nB + nC + nD)/4 # this is intrinsically weighted by area + # normalize + N = normalize(N) + else: + N = [normalize(nA), normalize(nB), normalize(nC), normalize(nD)] + + # Area calculation + # + # Approximate by 4 different triangles, and divide by 2. + # Each triangle is one half of the length of the cross product + # + # So also could be viewed as the average parallelogram. + area = (length(nA)+length(nB)+length(nC)+length(nD))/4 + + # simple edge length calculations + edgeLengths = [length(AB), length(BC), length(CD), length(DA)] + + return N, area, edgeLengths + + def getSubArray(A, ind): """subArray""" return A[ind[0], :, :][:, ind[1], :][:, :, ind[2]] From 05ddf93d647ad6f54949b4c716e5a791711dbf00 Mon Sep 17 00:00:00 2001 From: ehaber99 Date: Sat, 27 Jul 2013 16:11:24 -0700 Subject: [PATCH 12/51] Added getFaceInnerProduct and getInnerProduct that puts the edge and face mass matrices together --- SimPEG/getFaceInnerProducts.py | 87 +++++++++++++++++ SimPEG/getInnerProducts.py | 165 +++++++++++++++++++++++++++++++++ 2 files changed, 252 insertions(+) create mode 100644 SimPEG/getFaceInnerProducts.py create mode 100644 SimPEG/getInnerProducts.py diff --git a/SimPEG/getFaceInnerProducts.py b/SimPEG/getFaceInnerProducts.py new file mode 100644 index 00000000..961013c8 --- /dev/null +++ b/SimPEG/getFaceInnerProducts.py @@ -0,0 +1,87 @@ +from scipy import sparse as sp +from sputils import sdiag +from utils import sub2ind, ndgrid, mkvc +import numpy as np + + +def getFaceInnerProduct(mesh, mu): + + m = np.array([mesh.nCx, mesh.nCy, mesh.nCz]) + nc = mesh.nC + + i, j, k = np.int64(range(m[0])), np.int64(range(m[1])), np.int64(range(m[2])) + + iijjkk = ndgrid(i, j, k) + ii, jj, kk = iijjkk[:, 0], iijjkk[:, 1], iijjkk[:, 2] + + def Pxxx(pos): + ind1 = sub2ind(mesh.nFx, np.c_[ii + pos[0][0], jj + pos[0][1], kk + pos[0][2]]) + ind2 = sub2ind(mesh.nFy, np.c_[ii + pos[1][0], jj + pos[1][1], kk + pos[1][2]]) + mesh.nF[0] + ind3 = sub2ind(mesh.nFz, np.c_[ii + pos[2][0], jj + pos[2][1], kk + pos[2][2]]) + mesh.nF[0] + mesh.nF[1] + + IND = np.r_[ind1, ind2, ind3].flatten() + + return sp.coo_matrix((np.ones(3*nc), (range(3*nc), IND)), shape=(3*nc, np.sum(mesh.nF))).tocsr() + + # node(i,j,k+1) ------ edge2(i,j,k+1) ----- node(i,j+1,k+1) + # / / + # / / | + # edge3(i,j,k) face1(i,j,k) edge3(i,j+1,k) + # / / | + # / / | + # node(i,j,k) ------ edge2(i,j,k) ----- node(i,j+1,k) + # | | | + # | | node(i+1,j+1,k+1) + # | | / + # edge1(i,j,k) face3(i,j,k) edge1(i,j+1,k) + # | | / + # | | / + # | |/ + # node(i+1,j,k) ------ edge2(i+1,j,k) ----- node(i+1,j+1,k) + + # no | node | e1 | e2 | e3 + # 000 | i ,j ,k | i ,j ,k | i ,j ,k | i ,j ,k + # 100 | i+1,j ,k | i+1 ,j ,k | i,j ,k | i,j ,k + # 010 | i ,j+1,k | i ,j,k | i ,j+1 ,k | i ,j,k + # 110 | i+1,j+1,k | i+1 ,j,k | i,j+1 ,k | i,j,k + # 001 | i ,j ,k | i ,j ,k | i ,j ,k | i ,j ,k+1 + # 101 | i+1,j ,k | i+1 ,j ,k | i,j ,k | i,j ,k+1 + # 011 | i ,j+1,k | i ,j,k | i ,j+1 ,k | i ,j,k+1 + # 111 | i+1,j+1,k | i+1 ,j,k | i,j+1 ,k | i,j,k+1 + P000 = Pxxx([[0, 0, 0], [0, 0, 0], [0, 0, 0]]) + P100 = Pxxx([[1, 0, 0], [0, 0, 0], [0, 0, 0]]) + P010 = Pxxx([[0, 0, 0], [0, 1, 0], [0, 0, 0]]) + P110 = Pxxx([[1, 0, 0], [0, 1, 0], [0, 0, 0]]) + P001 = Pxxx([[0, 0, 0], [0, 0, 0], [0, 0, 1]]) + P101 = Pxxx([[1, 0, 0], [0, 0, 0], [0, 0, 1]]) + P011 = Pxxx([[0, 0, 0], [0, 1, 0], [0, 0, 1]]) + P111 = Pxxx([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + + if mu.size == mesh.nC: # Isotropic! + mu = mkvc(mu) # ensure it is a vector. + mu = sdiag(np.r_[mu, mu, mu]) + elif mu.shape[1] == 3: # Diagonal tensor + mu = sdiag(np.r_[mu[:, 0], mu[:, 1], mu[:, 2]]) + elif mu.shape[1] == 6: # Fully anisotropic + row1 = sp.hstack((sdiag(mu[:, 0]), sdiag(mu[:, 3]), sdiag(mu[:, 4]))) + row2 = sp.hstack((sdiag(mu[:, 3]), sdiag(mu[:, 1]), sdiag(mu[:, 5]))) + row3 = sp.hstack((sdiag(mu[:, 4]), sdiag(mu[:, 5]), sdiag(mu[:, 2]))) + mu = sp.vstack((row1, row2, row3)) + + # Cell volume + v = np.sqrt(mesh.vol) + v3 = np.r_[v, v, v] + V = sdiag(v3)*mu*sdiag(v3) # to keep symmetry + + A = P000.T*V*P000 + P001.T*V*P001 + P010.T*V*P010 + P011.T*V*P011 + P100.T*V*P100 + P101.T*V*P101 + P110.T*V*P110 + P111.T*V*P111 + + A = 0.125*A + + return A + +if __name__ == '__main__': + from TensorMesh import TensorMesh + h = [np.array([1, 2, 3, 4]), np.array([1, 2, 1, 4, 2]), np.array([1, 1, 4, 1])] + mesh = TensorMesh(h) + mu = np.ones((mesh.nC, 6)) + A = getFaceInnerProduct(mesh, mu) diff --git a/SimPEG/getInnerProducts.py b/SimPEG/getInnerProducts.py new file mode 100644 index 00000000..94c02826 --- /dev/null +++ b/SimPEG/getInnerProducts.py @@ -0,0 +1,165 @@ +from scipy import sparse as sp +from sputils import sdiag +from utils import sub2ind, ndgrid, mkvc +import numpy as np + + +def getFaceInnerProduct(mesh, mu): + + m = np.array([mesh.nCx, mesh.nCy, mesh.nCz]) + nc = mesh.nC + + i, j, k = np.int64(range(m[0])), np.int64(range(m[1])), np.int64(range(m[2])) + + iijjkk = ndgrid(i, j, k) + ii, jj, kk = iijjkk[:, 0], iijjkk[:, 1], iijjkk[:, 2] + + def Pxxx(pos): + ind1 = sub2ind(mesh.nFx, np.c_[ii + pos[0][0], jj + pos[0][1], kk + pos[0][2]]) + ind2 = sub2ind(mesh.nFy, np.c_[ii + pos[1][0], jj + pos[1][1], kk + pos[1][2]]) + mesh.nF[0] + ind3 = sub2ind(mesh.nFz, np.c_[ii + pos[2][0], jj + pos[2][1], kk + pos[2][2]]) + mesh.nF[0] + mesh.nF[1] + + IND = np.r_[ind1, ind2, ind3].flatten() + + return sp.coo_matrix((np.ones(3*nc), (range(3*nc), IND)), shape=(3*nc, np.sum(mesh.nF))).tocsr() + + # node(i,j,k+1) ------ edge2(i,j,k+1) ----- node(i,j+1,k+1) + # / / + # / / | + # edge3(i,j,k) face1(i,j,k) edge3(i,j+1,k) + # / / | + # / / | + # node(i,j,k) ------ edge2(i,j,k) ----- node(i,j+1,k) + # | | | + # | | node(i+1,j+1,k+1) + # | | / + # edge1(i,j,k) face3(i,j,k) edge1(i,j+1,k) + # | | / + # | | / + # | |/ + # node(i+1,j,k) ------ edge2(i+1,j,k) ----- node(i+1,j+1,k) + + # no | node | e1 | e2 | e3 + # 000 | i ,j ,k | i ,j ,k | i ,j ,k | i ,j ,k + # 100 | i+1,j ,k | i+1 ,j ,k | i,j ,k | i,j ,k + # 010 | i ,j+1,k | i ,j,k | i ,j+1 ,k | i ,j,k + # 110 | i+1,j+1,k | i+1 ,j,k | i,j+1 ,k | i,j,k + # 001 | i ,j ,k | i ,j ,k | i ,j ,k | i ,j ,k+1 + # 101 | i+1,j ,k | i+1 ,j ,k | i,j ,k | i,j ,k+1 + # 011 | i ,j+1,k | i ,j,k | i ,j+1 ,k | i ,j,k+1 + # 111 | i+1,j+1,k | i+1 ,j,k | i,j+1 ,k | i,j,k+1 + P000 = Pxxx([[0, 0, 0], [0, 0, 0], [0, 0, 0]]) + P100 = Pxxx([[1, 0, 0], [0, 0, 0], [0, 0, 0]]) + P010 = Pxxx([[0, 0, 0], [0, 1, 0], [0, 0, 0]]) + P110 = Pxxx([[1, 0, 0], [0, 1, 0], [0, 0, 0]]) + P001 = Pxxx([[0, 0, 0], [0, 0, 0], [0, 0, 1]]) + P101 = Pxxx([[1, 0, 0], [0, 0, 0], [0, 0, 1]]) + P011 = Pxxx([[0, 0, 0], [0, 1, 0], [0, 0, 1]]) + P111 = Pxxx([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + + if mu.size == mesh.nC: # Isotropic! + mu = mkvc(mu) # ensure it is a vector. + mu = sdiag(np.r_[mu, mu, mu]) + elif mu.shape[1] == 3: # Diagonal tensor + mu = sdiag(np.r_[mu[:, 0], mu[:, 1], mu[:, 2]]) + elif mu.shape[1] == 6: # Fully anisotropic + row1 = sp.hstack((sdiag(mu[:, 0]), sdiag(mu[:, 3]), sdiag(mu[:, 4]))) + row2 = sp.hstack((sdiag(mu[:, 3]), sdiag(mu[:, 1]), sdiag(mu[:, 5]))) + row3 = sp.hstack((sdiag(mu[:, 4]), sdiag(mu[:, 5]), sdiag(mu[:, 2]))) + mu = sp.vstack((row1, row2, row3)) + + # Cell volume + v = np.sqrt(mesh.vol) + v3 = np.r_[v, v, v] + V = sdiag(v3)*mu*sdiag(v3) # to keep symmetry + + A = P000.T*V*P000 + P001.T*V*P001 + P010.T*V*P010 + P011.T*V*P011 + P100.T*V*P100 + P101.T*V*P101 + P110.T*V*P110 + P111.T*V*P111 + + A = 0.125*A + + return A + + +def getEdgeInnerProduct(mesh, sigma): + + m = np.array([mesh.nCx, mesh.nCy, mesh.nCz]) + nc = mesh.nC + + i, j, k = np.int64(range(m[0])), np.int64(range(m[1])), np.int64(range(m[2])) + + iijjkk = ndgrid(i, j, k) + ii, jj, kk = iijjkk[:, 0], iijjkk[:, 1], iijjkk[:, 2] + + def Pxxx(pos): + ind1 = sub2ind(mesh.nEx, np.c_[ii + pos[0][0], jj + pos[0][1], kk + pos[0][2]]) + ind2 = sub2ind(mesh.nEy, np.c_[ii + pos[1][0], jj + pos[1][1], kk + pos[1][2]]) + mesh.nE[0] + ind3 = sub2ind(mesh.nEz, np.c_[ii + pos[2][0], jj + pos[2][1], kk + pos[2][2]]) + mesh.nE[0] + mesh.nE[1] + + IND = np.r_[ind1, ind2, ind3].flatten() + + return sp.coo_matrix((np.ones(3*nc), (range(3*nc), IND)), shape=(3*nc, np.sum(mesh.nE))).tocsr() + + # node(i,j,k+1) ------ edge2(i,j,k+1) ----- node(i,j+1,k+1) + # / / + # / / | + # edge3(i,j,k) face1(i,j,k) edge3(i,j+1,k) + # / / | + # / / | + # node(i,j,k) ------ edge2(i,j,k) ----- node(i,j+1,k) + # | | | + # | | node(i+1,j+1,k+1) + # | | / + # edge1(i,j,k) face3(i,j,k) edge1(i,j+1,k) + # | | / + # | | / + # | |/ + # node(i+1,j,k) ------ edge2(i+1,j,k) ----- node(i+1,j+1,k) + + # no | node | e1 | e2 | e3 + # 000 | i ,j ,k | i ,j ,k | i ,j ,k | i ,j ,k + # 100 | i+1,j ,k | i ,j ,k | i+1,j ,k | i+1,j ,k + # 010 | i ,j+1,k | i ,j+1,k | i ,j ,k | i ,j+1,k + # 110 | i+1,j+1,k | i ,j+1,k | i+1,j ,k | i+1,j+1,k + # 001 | i ,j ,k+1 | i ,j ,k+1 | i ,j ,k+1 | i ,j ,k + # 101 | i+1,j ,k+1 | i ,j ,k+1 | i+1,j ,k+1 | i+1,j ,k + # 011 | i ,j+1,k+1 | i ,j+1,k+1 | i ,j ,k+1 | i ,j+1,k + # 111 | i+1,j+1,k+1 | i ,j+1,k+1 | i+1,j ,k+1 | i+1,j+1,k + P000 = Pxxx([[0, 0, 0], [0, 0, 0], [0, 0, 0]]) + P100 = Pxxx([[0, 0, 0], [1, 0, 0], [1, 0, 0]]) + P010 = Pxxx([[0, 1, 0], [0, 0, 0], [0, 1, 0]]) + P110 = Pxxx([[0, 1, 0], [1, 0, 0], [1, 1, 0]]) + P001 = Pxxx([[0, 0, 1], [0, 0, 1], [0, 0, 0]]) + P101 = Pxxx([[0, 0, 1], [1, 0, 1], [1, 0, 0]]) + P011 = Pxxx([[0, 1, 1], [0, 0, 1], [0, 1, 0]]) + P111 = Pxxx([[0, 1, 1], [1, 0, 1], [1, 1, 0]]) + + if sigma.size == mesh.nC: # Isotropic! + sigma = mkvc(sigma) # ensure it is a vector. + Sigma = sdiag(np.r_[sigma, sigma, sigma]) + elif sigma.shape[1] == 3: # Diagonal tensor + Sigma = sdiag(np.r_[sigma[:, 0], sigma[:, 1], sigma[:, 2]]) + elif sigma.shape[1] == 6: # Fully anisotropic + row1 = sp.hstack((sdiag(sigma[:, 0]), sdiag(sigma[:, 3]), sdiag(sigma[:, 4]))) + row2 = sp.hstack((sdiag(sigma[:, 3]), sdiag(sigma[:, 1]), sdiag(sigma[:, 5]))) + row3 = sp.hstack((sdiag(sigma[:, 4]), sdiag(sigma[:, 5]), sdiag(sigma[:, 2]))) + Sigma = sp.vstack((row1, row2, row3)) + + # Cell volume + v = np.sqrt(mesh.vol) + v3 = np.r_[v, v, v] + V = sdiag(v3)*Sigma*sdiag(v3) # to keep symmetry + + A = P000.T*V*P000 + P001.T*V*P001 + P010.T*V*P010 + P011.T*V*P011 + P100.T*V*P100 + P101.T*V*P101 + P110.T*V*P110 + P111.T*V*P111 + + A = 0.125*A + + return A + + + +if __name__ == '__main__': + from TensorMesh import TensorMesh + h = [np.array([1, 2, 3, 4]), np.array([1, 2, 1, 4, 2]), np.array([1, 1, 4, 1])] + mesh = TensorMesh(h) + mu = np.ones((mesh.nC, 6)) + A = getFaceInnerProduct(mesh, mu) From f7713656ef47bfc8a0363358cfa6fee7e4a8ea15 Mon Sep 17 00:00:00 2001 From: ehaber99 Date: Sat, 27 Jul 2013 17:36:00 -0700 Subject: [PATCH 13/51] Added interpolation matrix. Not sure its working since my canopy is playing tricks --- SimPEG/interpmat.py | 86 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 SimPEG/interpmat.py diff --git a/SimPEG/interpmat.py b/SimPEG/interpmat.py new file mode 100644 index 00000000..35e998ff --- /dev/null +++ b/SimPEG/interpmat.py @@ -0,0 +1,86 @@ +from scipy import sparse as sp +from sputils import sdiag +from utils import sub2ind, ndgrid, mkvc +import numpy as np + +def interpmat(x,y,z,xr,yr,zr): +# +# This function does local linear interpolation +# computed for each receiver point in turn +# +# [Q] = linint(x,y,z,xr,yr,zr) +# Interpolation matrix +# + + nx = size(x) + ny = size(y) + nz = size(z) + + np = size(xr) + + #Q = spalloc(np,nx*ny*nz,8*np); + Q = sparse.coo_matrix((0.0,(0,0)),shape=(nx*ny*nz,8*np)) + + for i in range(0, np): + im = amin(abs(xr[i]-x)) + if xr[i] - x[im] >= 0: # Point on the left + ind_x[0] = im; ind_x[1] = im+1 + else: # Point on the right + ind_x[0] = im-1; ind_x[1] = im + + + dx[0] = xr[i] - x[ind_x[0]] + dx[1] = x[ind_x[1]] - xr[i] + + im = amin(abs(yr[i] - y)) + if yr[i] - y[im] >= 0: # Point on the left + ind_y[0] = im; ind_y[1] = im+1 + else: # Point on the right + ind_y[0] = im-1; ind_y[1] = im + + + dy[0] = yr[i] - y[ind_y[0]] + dy[1] = y[ind_y[1]] - yr[i]; + + im = amin(abs(zr[i] - z)); + if zr(i) -z(im) >= 0: # Point on the left + ind_z[0] = im; ind_z[1] = im+1 + else: # Point on the right + ind_z[0] = im-1; ind_z[1] = im; + + dz[0] = zr[i] - z[ind_z[0]]; dz[1] = z[ind_z[1]] - zr[i] + + Dx = x[ind_x[1]] - x[ind_x[0]] + Dy = y[ind_y[1]] - y[ind_y[0]] + Dz = z[ind_z[1]] - z[ind_z[0]] + dv = Dx*Dy*Dz + + # Get the row in the matrix + v = zeros([nx, ny,nz]); + + v[ ind_x[0], ind_y[0], ind_z[0]] = (1-dx[0]/Dx)*(1-dy[0]/Dy)*(1-dz[0]/Dz) + v[ ind_x[0], ind_y[1], ind_z[0]] = (1-dx[0]/Dx)*(1-dy[1]/Dy)*(1-dz[0]/Dz); + v[ ind_x[1], ind_y[0], ind_z[0]] = (1-dx[1]/Dx)*(1-dy[0]/Dy)*(1-dz[0]/Dz); + v[ ind_x[1], ind_y[1], ind_z[0]] = (1-dx[1]/Dx)*(1-dy[1]/Dy)*(1-dz[0]/Dz); + v[ ind_x[0], ind_y[0], ind_z[1]] = (1-dx[0]/Dx)*(1-dy[0]/Dy)*(1-dz[1]/Dz); + v[ ind_x[0], ind_y[1], ind_z[1]] = (1-dx[0]/Dx)*(1-dy[1]/Dy)*(1-dz[1]/Dz); + v[ ind_x[1], ind_y[0], ind_z[1]] = (1-dx[1]/Dx)*(1-dy[0]/Dy)*(1-dz[1]/Dz); + v[ ind_x[1], ind_y[1], ind_z[1]] = (1-dx[1]/Dx)*(1-dy[1]/Dy)*(1-dz[1]/Dz); + + + Q[i,:] = v.flatten('F') + + return Q + + +if __name__ == '__main__': + x = np.array([1, 2, 3, 4]) + y = np.array([1, 2, 3, 4, 5]) + z = np.array([0, 1, 4, 6]) + + xr = np.array([2.5,3.2]) + yr = np.array([2.4,3.6]) + zr = np.array([2.5,3.9]) + + A = interpmat(x,y,z,xr,yr,zr) + \ No newline at end of file From 1fca1b894e6e165e8e6309916bc9daabdad3c1a0 Mon Sep 17 00:00:00 2001 From: ehaber99 Date: Mon, 29 Jul 2013 12:47:41 -0700 Subject: [PATCH 14/51] fixed interpmat need more testing --- SimPEG/interpmat.py | 52 +++++++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/SimPEG/interpmat.py b/SimPEG/interpmat.py index 35e998ff..68b6c4c1 100644 --- a/SimPEG/interpmat.py +++ b/SimPEG/interpmat.py @@ -1,6 +1,4 @@ from scipy import sparse as sp -from sputils import sdiag -from utils import sub2ind, ndgrid, mkvc import numpy as np def interpmat(x,y,z,xr,yr,zr): @@ -12,17 +10,20 @@ def interpmat(x,y,z,xr,yr,zr): # Interpolation matrix # - nx = size(x) - ny = size(y) - nz = size(z) + nx = np.size(x) + ny = np.size(y) + nz = np.size(z) - np = size(xr) + nps = np.size(xr) #Q = spalloc(np,nx*ny*nz,8*np); - Q = sparse.coo_matrix((0.0,(0,0)),shape=(nx*ny*nz,8*np)) - - for i in range(0, np): - im = amin(abs(xr[i]-x)) + Q = sp.lil_matrix((nps,nx*ny*nz)) + ind_x = np.array([0,0]) + ind_y = np.array([0,0]) + ind_z = np.array([0,0]) + dx, dy, dz = np.zeros(2), np.zeros(2), np.zeros(2) + for i in range(0, nps): + im = np.amin(abs(xr[i]-x)) if xr[i] - x[im] >= 0: # Point on the left ind_x[0] = im; ind_x[1] = im+1 else: # Point on the right @@ -32,7 +33,7 @@ def interpmat(x,y,z,xr,yr,zr): dx[0] = xr[i] - x[ind_x[0]] dx[1] = x[ind_x[1]] - xr[i] - im = amin(abs(yr[i] - y)) + im = np.amin(abs(yr[i] - y)) if yr[i] - y[im] >= 0: # Point on the left ind_y[0] = im; ind_y[1] = im+1 else: # Point on the right @@ -42,8 +43,8 @@ def interpmat(x,y,z,xr,yr,zr): dy[0] = yr[i] - y[ind_y[0]] dy[1] = y[ind_y[1]] - yr[i]; - im = amin(abs(zr[i] - z)); - if zr(i) -z(im) >= 0: # Point on the left + im = np.amin(abs(zr[i] - z)); + if zr[i] -z[im] >= 0: # Point on the left ind_z[0] = im; ind_z[1] = im+1 else: # Point on the right ind_z[0] = im-1; ind_z[1] = im; @@ -53,27 +54,32 @@ def interpmat(x,y,z,xr,yr,zr): Dx = x[ind_x[1]] - x[ind_x[0]] Dy = y[ind_y[1]] - y[ind_y[0]] Dz = z[ind_z[1]] - z[ind_z[0]] - dv = Dx*Dy*Dz + #dv = Dx*Dy*Dz # Get the row in the matrix - v = zeros([nx, ny,nz]); + v = np.zeros([nx, ny,nz]) v[ ind_x[0], ind_y[0], ind_z[0]] = (1-dx[0]/Dx)*(1-dy[0]/Dy)*(1-dz[0]/Dz) - v[ ind_x[0], ind_y[1], ind_z[0]] = (1-dx[0]/Dx)*(1-dy[1]/Dy)*(1-dz[0]/Dz); - v[ ind_x[1], ind_y[0], ind_z[0]] = (1-dx[1]/Dx)*(1-dy[0]/Dy)*(1-dz[0]/Dz); - v[ ind_x[1], ind_y[1], ind_z[0]] = (1-dx[1]/Dx)*(1-dy[1]/Dy)*(1-dz[0]/Dz); - v[ ind_x[0], ind_y[0], ind_z[1]] = (1-dx[0]/Dx)*(1-dy[0]/Dy)*(1-dz[1]/Dz); - v[ ind_x[0], ind_y[1], ind_z[1]] = (1-dx[0]/Dx)*(1-dy[1]/Dy)*(1-dz[1]/Dz); - v[ ind_x[1], ind_y[0], ind_z[1]] = (1-dx[1]/Dx)*(1-dy[0]/Dy)*(1-dz[1]/Dz); - v[ ind_x[1], ind_y[1], ind_z[1]] = (1-dx[1]/Dx)*(1-dy[1]/Dy)*(1-dz[1]/Dz); + v[ ind_x[0], ind_y[1], ind_z[0]] = (1-dx[0]/Dx)*(1-dy[1]/Dy)*(1-dz[0]/Dz) + v[ ind_x[1], ind_y[0], ind_z[0]] = (1-dx[1]/Dx)*(1-dy[0]/Dy)*(1-dz[0]/Dz) + v[ ind_x[1], ind_y[1], ind_z[0]] = (1-dx[1]/Dx)*(1-dy[1]/Dy)*(1-dz[0]/Dz) + v[ ind_x[0], ind_y[0], ind_z[1]] = (1-dx[0]/Dx)*(1-dy[0]/Dy)*(1-dz[1]/Dz) + v[ ind_x[0], ind_y[1], ind_z[1]] = (1-dx[0]/Dx)*(1-dy[1]/Dy)*(1-dz[1]/Dz) + v[ ind_x[1], ind_y[0], ind_z[1]] = (1-dx[1]/Dx)*(1-dy[0]/Dy)*(1-dz[1]/Dz) + v[ ind_x[1], ind_y[1], ind_z[1]] = (1-dx[1]/Dx)*(1-dy[1]/Dy)*(1-dz[1]/Dz) + print(np.shape(v.flatten('F'))) + print(np.shape(Q)) + Q[i,:] = v.flatten('F') - return Q + + return Q.tocsr() if __name__ == '__main__': + x = np.array([1, 2, 3, 4]) y = np.array([1, 2, 3, 4, 5]) z = np.array([0, 1, 4, 6]) From ead836c3419697d2c01d49f82b0177333d33dbad Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Mon, 29 Jul 2013 23:02:25 -0700 Subject: [PATCH 15/51] Fixed 2D volume calculation. --- SimPEG/LogicallyOrthogonalMesh.py | 18 ++++++++++++------ SimPEG/utils.py | 8 ++++---- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/SimPEG/LogicallyOrthogonalMesh.py b/SimPEG/LogicallyOrthogonalMesh.py index 5bb5482b..15dcb09c 100644 --- a/SimPEG/LogicallyOrthogonalMesh.py +++ b/SimPEG/LogicallyOrthogonalMesh.py @@ -88,7 +88,7 @@ class LogicallyOrthogonalMesh(BaseMesh, DiffOperators): # , LOMGrid if(self._vol is None): if self.dim == 2: A, B, C, D = indexCube('ABCD', np.array([self.nNx, self.nNy])) - normal, area, length = faceInfo(np.c_[self.gridN, np.zeros((self.nC, 1))], A, B, C, D) + normal, area, length = faceInfo(np.c_[self.gridN, np.zeros((self.nN, 1))], A, B, C, D) self._vol = area elif self.dim == 3: # Each polyhedron can be decomposed into 5 tetrahedrons @@ -117,8 +117,14 @@ if __name__ == '__main__': h1 = np.cumsum(np.r_[0, np.ones(nc)/(nc)]) h2 = np.cumsum(np.r_[0, np.ones(nc)/(nc)]) h3 = np.cumsum(np.r_[0, np.ones(nc)/(nc)]) - h = [h1, h2, h3] - X, Y, Z = ndgrid(h1, h2, h3, vector=False) - M = LogicallyOrthogonalMesh([X, Y, Z]) - print M.r(M.gridCC, format='M') - print M.gridN[:, 0] + dee3 = False + if dee3: + X, Y, Z = ndgrid(h1, h2, h3, vector=False) + M = LogicallyOrthogonalMesh([X, Y, Z]) + else: + X, Y = ndgrid(h1, h2, vector=False) + M = LogicallyOrthogonalMesh([X, Y]) + + # print M.r(M.gridCC, format='M') + # print M.gridN[:, 0] + print np.sum(M.vol) diff --git a/SimPEG/utils.py b/SimPEG/utils.py index 37441e8e..eff11748 100644 --- a/SimPEG/utils.py +++ b/SimPEG/utils.py @@ -247,9 +247,9 @@ def faceInfo(xyz, A, B, C, D, average=True): DA = xyz[A, :] - xyz[D, :] def cross(X, Y): - return np.c_[X[1, :]*Y[2, :] - X[2, :]*Y[1, :], - X[2, :]*Y[0, :] - X[0, :]*Y[2, :], - X[0, :]*Y[1, :] - X[1, :]*Y[0, :]] + return np.c_[X[:, 1]*Y[:, 2] - X[:, 2]*Y[:, 1], + X[:, 2]*Y[:, 0] - X[:, 0]*Y[:, 2], + X[:, 0]*Y[:, 1] - X[:, 1]*Y[:, 0]] nA = cross(AB, DA) nB = cross(BC, AB) @@ -257,7 +257,7 @@ def faceInfo(xyz, A, B, C, D, average=True): nD = cross(DA, CD) length = lambda x: (x[:, 0]**2 + x[:, 1]**2 + x[:, 2]**2)**0.5 - normalize = lambda x: x/np.kron(np.ones((1, x.shape[1]), length(x), 1)) + normalize = lambda x: x/np.kron(np.ones((1, x.shape[1])), mkvc(length(N), 2)) if average: # average the normals at each vertex. N = (nA + nB + nC + nD)/4 # this is intrinsically weighted by area From d8f646c7d99216fe860daf861794c6ddb39afb15 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Tue, 30 Jul 2013 21:49:23 -0700 Subject: [PATCH 16/51] Updates to OrderTest so that it runs random meshes. --- SimPEG/tests/OrderTest.py | 42 +++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/SimPEG/tests/OrderTest.py b/SimPEG/tests/OrderTest.py index 7f9f97ca..59dce6d6 100644 --- a/SimPEG/tests/OrderTest.py +++ b/SimPEG/tests/OrderTest.py @@ -20,7 +20,7 @@ class OrderTest(unittest.TestCase): Note that you can provide any norm. - Test is passed when estimated rate order of convergence is at least 90% of the + Test is passed when estimated rate order of convergence is at least within the specified tolerance of the estimated rate supplied by the user. Minimal example for a curl operator: @@ -63,17 +63,32 @@ class OrderTest(unittest.TestCase): name = "Order Test" expectedOrder = 2 - meshSizes = [4, 8, 16, 32] + tolerance = 0.85 + meshSizes = [4, 8, 16, 32, 64] + meshType = 'uniformTensorMesh' + meshDimension = 3 def setupMesh(self, nc): """ For a given number of cells nc, generate a TensorMesh with uniform cells with edge length h=1/nc. """ - h1 = np.ones(nc)/nc - h2 = np.ones(nc)/nc - h3 = np.ones(nc)/nc - h = [h1, h2, h3] - self.M = TensorMesh(h) + if 'TensorMesh' in self.meshType: + if 'uniform' in self.meshType: + h1 = np.ones(nc)/nc + h2 = np.ones(nc)/nc + h3 = np.ones(nc)/nc + h = [h1, h2, h3] + elif 'random' in self.meshType: + h1 = np.random.rand(nc) + h2 = np.random.rand(nc) + h3 = np.random.rand(nc) + h = [hi/np.sum(hi) for hi in [h1, h2, h3]] # normalize + else: + raise Exception('Unexpected meshType') + + self.M = TensorMesh(h[:self.meshDimension]) + max_h = max([np.max(hi) for hi in self.M.h]) + return max_h def getError(self): """For given h, generate A[h], f and A(f) and return norm of error.""" @@ -89,9 +104,9 @@ class OrderTest(unittest.TestCase): """ order = [] err_old = 0. - nc_old = 0. + max_h_old = 0. for ii, nc in enumerate(self.meshSizes): - self.setupMesh(nc) + max_h = self.setupMesh(nc) err = self.getError() if ii == 0: print '' @@ -101,13 +116,14 @@ class OrderTest(unittest.TestCase): print '~~~~~~|~~~~~~~~~~~~~|~~~~~~~~~~~~~|~~~~~~~~~~' print '%4i | %8.2e |' % (nc, err) else: - order.append(np.log(err/err_old)/np.log(float(nc_old)/float(nc))) + order.append(np.log(err/err_old)/np.log(max_h/max_h_old)) print '%4i | %8.2e | %6.4f | %6.4f' % (nc, err, err_old/err, order[-1]) err_old = err - nc_old = nc + max_h_old = max_h print '---------------------------------------------' - self.assertTrue(len(np.where(np.array(order) > 0.9*self.expectedOrder)[0]) > np.floor(0.75*len(order))) - + passTest = np.mean(np.array(order)) > self.tolerance*self.expectedOrder + # passTest = len(np.where(np.array(order) > self.tolerance*self.expectedOrder)[0]) > np.floor(0.75*len(order)) + self.assertTrue(passTest) if __name__ == '__main__': unittest.main() From c259a6651a7760ff2cf93aca20642516f48673bd Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Tue, 30 Jul 2013 22:12:53 -0700 Subject: [PATCH 17/51] Integrated getEdge/FaceInnerProduct into the tensor mesh class. --- SimPEG/DiffOperators.py | 2 +- .../{getInnerProducts.py => InnerProducts.py} | 42 ++++++--- SimPEG/TensorMesh.py | 5 +- SimPEG/getEdgeInnerProducts.py | 87 ------------------- SimPEG/getFaceInnerProducts.py | 87 ------------------- SimPEG/tests/OrderTest.py | 2 +- SimPEG/tests/test_massMatrices.py | 5 +- 7 files changed, 39 insertions(+), 191 deletions(-) rename SimPEG/{getInnerProducts.py => InnerProducts.py} (82%) delete mode 100644 SimPEG/getEdgeInnerProducts.py delete mode 100644 SimPEG/getFaceInnerProducts.py diff --git a/SimPEG/DiffOperators.py b/SimPEG/DiffOperators.py index c5daa96d..6688f254 100644 --- a/SimPEG/DiffOperators.py +++ b/SimPEG/DiffOperators.py @@ -50,7 +50,7 @@ class DiffOperators(object): Class creates the differential operators that you need! """ def __init__(self): - raise Exception('DiffOperators is a base class providing differential operators on meshes and cannot run on its own. Inherit to your favorite Mesh class.') + raise Exception('DiffOperators is a base class providing differential operators on meshes and cannot run on its own. Inherit to your favorite Mesh class.') def faceDiv(): doc = "Construct divergence operator (face-stg to cell-centres)." diff --git a/SimPEG/getInnerProducts.py b/SimPEG/InnerProducts.py similarity index 82% rename from SimPEG/getInnerProducts.py rename to SimPEG/InnerProducts.py index 94c02826..bc482de4 100644 --- a/SimPEG/getInnerProducts.py +++ b/SimPEG/InnerProducts.py @@ -4,6 +4,28 @@ from utils import sub2ind, ndgrid, mkvc import numpy as np +class InnerProducts(object): + """ + Class creates the inner product matrices that you need! + """ + def __init__(self): + raise Exception('InnerProducts is a base class providing inner product matrices for meshes and cannot run on its own. Inherit to your favorite Mesh class.') + + def getFaceInnerProduct(self, mu): + if self._meshType == 'TENSOR': + pass + elif self._meshType == 'LOM': + pass # todo: we should be doing something slightly different here! + return getFaceInnerProduct(self, mu) + + def getEdgeInnerProduct(self, sigma): + if self._meshType == 'TENSOR': + pass + elif self._meshType == 'LOM': + pass # todo: we should be doing something slightly different here! + return getEdgeInnerProduct(self, sigma) + + def getFaceInnerProduct(mesh, mu): m = np.array([mesh.nCx, mesh.nCy, mesh.nCz]) @@ -39,15 +61,15 @@ def getFaceInnerProduct(mesh, mu): # | |/ # node(i+1,j,k) ------ edge2(i+1,j,k) ----- node(i+1,j+1,k) - # no | node | e1 | e2 | e3 - # 000 | i ,j ,k | i ,j ,k | i ,j ,k | i ,j ,k - # 100 | i+1,j ,k | i+1 ,j ,k | i,j ,k | i,j ,k - # 010 | i ,j+1,k | i ,j,k | i ,j+1 ,k | i ,j,k - # 110 | i+1,j+1,k | i+1 ,j,k | i,j+1 ,k | i,j,k - # 001 | i ,j ,k | i ,j ,k | i ,j ,k | i ,j ,k+1 - # 101 | i+1,j ,k | i+1 ,j ,k | i,j ,k | i,j ,k+1 - # 011 | i ,j+1,k | i ,j,k | i ,j+1 ,k | i ,j,k+1 - # 111 | i+1,j+1,k | i+1 ,j,k | i,j+1 ,k | i,j,k+1 + # no | node | f1 | f2 | f3 + # 000 | i ,j ,k | i , j, k | i, j , k | i, j, k + # 100 | i+1,j ,k | i+1, j, k | i, j , k | i, j, k + # 010 | i ,j+1,k | i , j, k | i, j+1, k | i, j, k + # 110 | i+1,j+1,k | i+1, j, k | i, j+1, k | i, j, k + # 001 | i ,j ,k | i , j, k | i, j , k | i, j, k+1 + # 101 | i+1,j ,k | i+1, j, k | i, j , k | i, j, k+1 + # 011 | i ,j+1,k | i , j, k | i, j+1, k | i, j, k+1 + # 111 | i+1,j+1,k | i+1, j, k | i, j+1, k | i, j, k+1 P000 = Pxxx([[0, 0, 0], [0, 0, 0], [0, 0, 0]]) P100 = Pxxx([[1, 0, 0], [0, 0, 0], [0, 0, 0]]) P010 = Pxxx([[0, 0, 0], [0, 1, 0], [0, 0, 0]]) @@ -162,4 +184,4 @@ if __name__ == '__main__': h = [np.array([1, 2, 3, 4]), np.array([1, 2, 1, 4, 2]), np.array([1, 1, 4, 1])] mesh = TensorMesh(h) mu = np.ones((mesh.nC, 6)) - A = getFaceInnerProduct(mesh, mu) + A = mesh.getFaceInnerProduct(mu) diff --git a/SimPEG/TensorMesh.py b/SimPEG/TensorMesh.py index b247138b..acfdfbac 100644 --- a/SimPEG/TensorMesh.py +++ b/SimPEG/TensorMesh.py @@ -2,10 +2,11 @@ import numpy as np from BaseMesh import BaseMesh from TensorView import TensorView from DiffOperators import DiffOperators +from InnerProducts import InnerProducts from utils import ndgrid, mkvc -class TensorMesh(BaseMesh, TensorView, DiffOperators): +class TensorMesh(BaseMesh, TensorView, DiffOperators, InnerProducts): """ TensorMesh is a mesh class that deals with tensor product meshes. @@ -21,6 +22,8 @@ class TensorMesh(BaseMesh, TensorView, DiffOperators): mesh = TensorMesh([hx, hy, hz]) """ + _meshType = 'TENSOR' + def __init__(self, h, x0=None): super(TensorMesh, self).__init__(np.array([x.size for x in h]), x0) diff --git a/SimPEG/getEdgeInnerProducts.py b/SimPEG/getEdgeInnerProducts.py deleted file mode 100644 index 68423a3f..00000000 --- a/SimPEG/getEdgeInnerProducts.py +++ /dev/null @@ -1,87 +0,0 @@ -from scipy import sparse as sp -from sputils import sdiag -from utils import sub2ind, ndgrid, mkvc -import numpy as np - - -def getEdgeInnerProduct(mesh, sigma): - - m = np.array([mesh.nCx, mesh.nCy, mesh.nCz]) - nc = mesh.nC - - i, j, k = np.int64(range(m[0])), np.int64(range(m[1])), np.int64(range(m[2])) - - iijjkk = ndgrid(i, j, k) - ii, jj, kk = iijjkk[:, 0], iijjkk[:, 1], iijjkk[:, 2] - - def Pxxx(pos): - ind1 = sub2ind(mesh.nEx, np.c_[ii + pos[0][0], jj + pos[0][1], kk + pos[0][2]]) - ind2 = sub2ind(mesh.nEy, np.c_[ii + pos[1][0], jj + pos[1][1], kk + pos[1][2]]) + mesh.nE[0] - ind3 = sub2ind(mesh.nEz, np.c_[ii + pos[2][0], jj + pos[2][1], kk + pos[2][2]]) + mesh.nE[0] + mesh.nE[1] - - IND = np.r_[ind1, ind2, ind3].flatten() - - return sp.coo_matrix((np.ones(3*nc), (range(3*nc), IND)), shape=(3*nc, np.sum(mesh.nE))).tocsr() - - # node(i,j,k+1) ------ edge2(i,j,k+1) ----- node(i,j+1,k+1) - # / / - # / / | - # edge3(i,j,k) face1(i,j,k) edge3(i,j+1,k) - # / / | - # / / | - # node(i,j,k) ------ edge2(i,j,k) ----- node(i,j+1,k) - # | | | - # | | node(i+1,j+1,k+1) - # | | / - # edge1(i,j,k) face3(i,j,k) edge1(i,j+1,k) - # | | / - # | | / - # | |/ - # node(i+1,j,k) ------ edge2(i+1,j,k) ----- node(i+1,j+1,k) - - # no | node | e1 | e2 | e3 - # 000 | i ,j ,k | i ,j ,k | i ,j ,k | i ,j ,k - # 100 | i+1,j ,k | i ,j ,k | i+1,j ,k | i+1,j ,k - # 010 | i ,j+1,k | i ,j+1,k | i ,j ,k | i ,j+1,k - # 110 | i+1,j+1,k | i ,j+1,k | i+1,j ,k | i+1,j+1,k - # 001 | i ,j ,k+1 | i ,j ,k+1 | i ,j ,k+1 | i ,j ,k - # 101 | i+1,j ,k+1 | i ,j ,k+1 | i+1,j ,k+1 | i+1,j ,k - # 011 | i ,j+1,k+1 | i ,j+1,k+1 | i ,j ,k+1 | i ,j+1,k - # 111 | i+1,j+1,k+1 | i ,j+1,k+1 | i+1,j ,k+1 | i+1,j+1,k - P000 = Pxxx([[0, 0, 0], [0, 0, 0], [0, 0, 0]]) - P100 = Pxxx([[0, 0, 0], [1, 0, 0], [1, 0, 0]]) - P010 = Pxxx([[0, 1, 0], [0, 0, 0], [0, 1, 0]]) - P110 = Pxxx([[0, 1, 0], [1, 0, 0], [1, 1, 0]]) - P001 = Pxxx([[0, 0, 1], [0, 0, 1], [0, 0, 0]]) - P101 = Pxxx([[0, 0, 1], [1, 0, 1], [1, 0, 0]]) - P011 = Pxxx([[0, 1, 1], [0, 0, 1], [0, 1, 0]]) - P111 = Pxxx([[0, 1, 1], [1, 0, 1], [1, 1, 0]]) - - if sigma.size == mesh.nC: # Isotropic! - sigma = mkvc(sigma) # ensure it is a vector. - Sigma = sdiag(np.r_[sigma, sigma, sigma]) - elif sigma.shape[1] == 3: # Diagonal tensor - Sigma = sdiag(np.r_[sigma[:, 0], sigma[:, 1], sigma[:, 2]]) - elif sigma.shape[1] == 6: # Fully anisotropic - row1 = sp.hstack((sdiag(sigma[:, 0]), sdiag(sigma[:, 3]), sdiag(sigma[:, 4]))) - row2 = sp.hstack((sdiag(sigma[:, 3]), sdiag(sigma[:, 1]), sdiag(sigma[:, 5]))) - row3 = sp.hstack((sdiag(sigma[:, 4]), sdiag(sigma[:, 5]), sdiag(sigma[:, 2]))) - Sigma = sp.vstack((row1, row2, row3)) - - # Cell volume - v = np.sqrt(mesh.vol) - v3 = np.r_[v, v, v] - V = sdiag(v3)*Sigma*sdiag(v3) # to keep symmetry - - A = P000.T*V*P000 + P001.T*V*P001 + P010.T*V*P010 + P011.T*V*P011 + P100.T*V*P100 + P101.T*V*P101 + P110.T*V*P110 + P111.T*V*P111 - - A = 0.125*A - - return A - -if __name__ == '__main__': - from TensorMesh import TensorMesh - h = [np.array([1, 2, 3, 4]), np.array([1, 2, 1, 4, 2]), np.array([1, 1, 4, 1])] - mesh = TensorMesh(h) - sigma = np.ones((mesh.nC, 6)) - A = getEdgeInnerProduct(mesh, sigma) diff --git a/SimPEG/getFaceInnerProducts.py b/SimPEG/getFaceInnerProducts.py deleted file mode 100644 index 961013c8..00000000 --- a/SimPEG/getFaceInnerProducts.py +++ /dev/null @@ -1,87 +0,0 @@ -from scipy import sparse as sp -from sputils import sdiag -from utils import sub2ind, ndgrid, mkvc -import numpy as np - - -def getFaceInnerProduct(mesh, mu): - - m = np.array([mesh.nCx, mesh.nCy, mesh.nCz]) - nc = mesh.nC - - i, j, k = np.int64(range(m[0])), np.int64(range(m[1])), np.int64(range(m[2])) - - iijjkk = ndgrid(i, j, k) - ii, jj, kk = iijjkk[:, 0], iijjkk[:, 1], iijjkk[:, 2] - - def Pxxx(pos): - ind1 = sub2ind(mesh.nFx, np.c_[ii + pos[0][0], jj + pos[0][1], kk + pos[0][2]]) - ind2 = sub2ind(mesh.nFy, np.c_[ii + pos[1][0], jj + pos[1][1], kk + pos[1][2]]) + mesh.nF[0] - ind3 = sub2ind(mesh.nFz, np.c_[ii + pos[2][0], jj + pos[2][1], kk + pos[2][2]]) + mesh.nF[0] + mesh.nF[1] - - IND = np.r_[ind1, ind2, ind3].flatten() - - return sp.coo_matrix((np.ones(3*nc), (range(3*nc), IND)), shape=(3*nc, np.sum(mesh.nF))).tocsr() - - # node(i,j,k+1) ------ edge2(i,j,k+1) ----- node(i,j+1,k+1) - # / / - # / / | - # edge3(i,j,k) face1(i,j,k) edge3(i,j+1,k) - # / / | - # / / | - # node(i,j,k) ------ edge2(i,j,k) ----- node(i,j+1,k) - # | | | - # | | node(i+1,j+1,k+1) - # | | / - # edge1(i,j,k) face3(i,j,k) edge1(i,j+1,k) - # | | / - # | | / - # | |/ - # node(i+1,j,k) ------ edge2(i+1,j,k) ----- node(i+1,j+1,k) - - # no | node | e1 | e2 | e3 - # 000 | i ,j ,k | i ,j ,k | i ,j ,k | i ,j ,k - # 100 | i+1,j ,k | i+1 ,j ,k | i,j ,k | i,j ,k - # 010 | i ,j+1,k | i ,j,k | i ,j+1 ,k | i ,j,k - # 110 | i+1,j+1,k | i+1 ,j,k | i,j+1 ,k | i,j,k - # 001 | i ,j ,k | i ,j ,k | i ,j ,k | i ,j ,k+1 - # 101 | i+1,j ,k | i+1 ,j ,k | i,j ,k | i,j ,k+1 - # 011 | i ,j+1,k | i ,j,k | i ,j+1 ,k | i ,j,k+1 - # 111 | i+1,j+1,k | i+1 ,j,k | i,j+1 ,k | i,j,k+1 - P000 = Pxxx([[0, 0, 0], [0, 0, 0], [0, 0, 0]]) - P100 = Pxxx([[1, 0, 0], [0, 0, 0], [0, 0, 0]]) - P010 = Pxxx([[0, 0, 0], [0, 1, 0], [0, 0, 0]]) - P110 = Pxxx([[1, 0, 0], [0, 1, 0], [0, 0, 0]]) - P001 = Pxxx([[0, 0, 0], [0, 0, 0], [0, 0, 1]]) - P101 = Pxxx([[1, 0, 0], [0, 0, 0], [0, 0, 1]]) - P011 = Pxxx([[0, 0, 0], [0, 1, 0], [0, 0, 1]]) - P111 = Pxxx([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) - - if mu.size == mesh.nC: # Isotropic! - mu = mkvc(mu) # ensure it is a vector. - mu = sdiag(np.r_[mu, mu, mu]) - elif mu.shape[1] == 3: # Diagonal tensor - mu = sdiag(np.r_[mu[:, 0], mu[:, 1], mu[:, 2]]) - elif mu.shape[1] == 6: # Fully anisotropic - row1 = sp.hstack((sdiag(mu[:, 0]), sdiag(mu[:, 3]), sdiag(mu[:, 4]))) - row2 = sp.hstack((sdiag(mu[:, 3]), sdiag(mu[:, 1]), sdiag(mu[:, 5]))) - row3 = sp.hstack((sdiag(mu[:, 4]), sdiag(mu[:, 5]), sdiag(mu[:, 2]))) - mu = sp.vstack((row1, row2, row3)) - - # Cell volume - v = np.sqrt(mesh.vol) - v3 = np.r_[v, v, v] - V = sdiag(v3)*mu*sdiag(v3) # to keep symmetry - - A = P000.T*V*P000 + P001.T*V*P001 + P010.T*V*P010 + P011.T*V*P011 + P100.T*V*P100 + P101.T*V*P101 + P110.T*V*P110 + P111.T*V*P111 - - A = 0.125*A - - return A - -if __name__ == '__main__': - from TensorMesh import TensorMesh - h = [np.array([1, 2, 3, 4]), np.array([1, 2, 1, 4, 2]), np.array([1, 1, 4, 1])] - mesh = TensorMesh(h) - mu = np.ones((mesh.nC, 6)) - A = getFaceInnerProduct(mesh, mu) diff --git a/SimPEG/tests/OrderTest.py b/SimPEG/tests/OrderTest.py index 59dce6d6..55ac7d34 100644 --- a/SimPEG/tests/OrderTest.py +++ b/SimPEG/tests/OrderTest.py @@ -64,7 +64,7 @@ class OrderTest(unittest.TestCase): name = "Order Test" expectedOrder = 2 tolerance = 0.85 - meshSizes = [4, 8, 16, 32, 64] + meshSizes = [4, 8, 16, 32] meshType = 'uniformTensorMesh' meshDimension = 3 diff --git a/SimPEG/tests/test_massMatrices.py b/SimPEG/tests/test_massMatrices.py index 0aae0615..4840abed 100644 --- a/SimPEG/tests/test_massMatrices.py +++ b/SimPEG/tests/test_massMatrices.py @@ -1,9 +1,6 @@ import numpy as np import unittest from OrderTest import OrderTest -import sys -sys.path.append('../') -from getEdgeInnerProducts import * class TestEdgeInnerProduct(OrderTest): @@ -35,7 +32,7 @@ class TestEdgeInnerProduct(OrderTest): sigma = np.c_[call(sigma1, Gc), call(sigma2, Gc), call(sigma3, Gc), call(sigma4, Gc), call(sigma5, Gc), call(sigma6, Gc)] - A = getEdgeInnerProduct(self.M, sigma) + A = self.M.getEdgeInnerProduct(sigma) numeric = E.T*A*E analytic = 69881./21600 # Found using matlab symbolic toolbox. err = np.abs(numeric - analytic) From 9573c6c230571b21f5cc8998dff4bb89efac9c29 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Tue, 30 Jul 2013 22:40:10 -0700 Subject: [PATCH 18/51] Tested face and edge inner products for anisotropic, isotropic and tensor sigma. --- SimPEG/tests/test_massMatrices.py | 94 ++++++++++++++++++++++++++----- 1 file changed, 81 insertions(+), 13 deletions(-) diff --git a/SimPEG/tests/test_massMatrices.py b/SimPEG/tests/test_massMatrices.py index 4840abed..f8fc99b0 100644 --- a/SimPEG/tests/test_massMatrices.py +++ b/SimPEG/tests/test_massMatrices.py @@ -3,8 +3,34 @@ import unittest from OrderTest import OrderTest -class TestEdgeInnerProduct(OrderTest): - """Integrate an edge function over a unit cube domain using edgeInnerProducts.""" +# MATLAB code: + +# syms x y z + +# ex = x.^2+y.*z; +# ey = (z.^2).*x+y.*z; +# ez = y.^2+x.*z; + +# e = [ex;ey;ez]; + +# sigma1 = x.*y+1; +# sigma2 = x.*z+2; +# sigma3 = 3+z.*y; +# sigma4 = 0.1.*x.*y.*z; +# sigma5 = 0.2.*x.*y; +# sigma6 = 0.1.*z; + +# S1 = [sigma1,0,0;0,sigma1,0;0,0,sigma1]; +# S2 = [sigma1,0,0;0,sigma2,0;0,0,sigma3]; +# S3 = [sigma1,sigma4,sigma5;sigma4,sigma2,sigma6;sigma5,sigma6,sigma3]; + +# i1 = int(int(int(e.'*S1*e,x,0,1),y,0,1),z,0,1); +# i2 = int(int(int(e.'*S2*e,x,0,1),y,0,1),z,0,1); +# i3 = int(int(int(e.'*S3*e,x,0,1),y,0,1),z,0,1); + + +class TestInnerProducts(OrderTest): + """Integrate an function over a unit cube domain using edgeInnerProducts and faceInnerProducts.""" name = "Edge Inner Product" @@ -23,22 +49,64 @@ class TestEdgeInnerProduct(OrderTest): sigma5 = lambda x, y, z: 0.2*x*y sigma6 = lambda x, y, z: 0.1*z - Ex = call(ex, self.M.gridEx) - Ey = call(ey, self.M.gridEy) - Ez = call(ez, self.M.gridEz) - - E = np.matrix(np.r_[Ex, Ey, Ez]).T Gc = self.M.gridCC - sigma = np.c_[call(sigma1, Gc), call(sigma2, Gc), call(sigma3, Gc), - call(sigma4, Gc), call(sigma5, Gc), call(sigma6, Gc)] + if self.sigmaTest == 1: + sigma = np.c_[call(sigma1, Gc)] + analytic = 647./360 # Found using matlab symbolic toolbox. + elif self.sigmaTest == 3: + sigma = np.c_[call(sigma1, Gc), call(sigma2, Gc), call(sigma3, Gc)] + analytic = 37./12 # Found using matlab symbolic toolbox. + elif self.sigmaTest == 6: + sigma = np.c_[call(sigma1, Gc), call(sigma2, Gc), call(sigma3, Gc), + call(sigma4, Gc), call(sigma5, Gc), call(sigma6, Gc)] + analytic = 69881./21600 # Found using matlab symbolic toolbox. + + if self.location == 'edges': + Ex = call(ex, self.M.gridEx) + Ey = call(ey, self.M.gridEy) + Ez = call(ez, self.M.gridEz) + E = np.matrix(np.r_[Ex, Ey, Ez]).T + A = self.M.getEdgeInnerProduct(sigma) + numeric = E.T*A*E + elif self.location == 'faces': + Fx = call(ex, self.M.gridFx) + Fy = call(ey, self.M.gridFy) + Fz = call(ez, self.M.gridFz) + F = np.matrix(np.r_[Fx, Fy, Fz]).T + A = self.M.getFaceInnerProduct(sigma) + numeric = F.T*A*F - A = self.M.getEdgeInnerProduct(sigma) - numeric = E.T*A*E - analytic = 69881./21600 # Found using matlab symbolic toolbox. err = np.abs(numeric - analytic) return err - def test_order(self): + def test_order1_edges(self): + self.location = 'edges' + self.sigmaTest = 1 + self.orderTest() + + def test_order3_edges(self): + self.location = 'edges' + self.sigmaTest = 3 + self.orderTest() + + def test_order6_edges(self): + self.location = 'edges' + self.sigmaTest = 6 + self.orderTest() + + def test_order1_faces(self): + self.location = 'faces' + self.sigmaTest = 1 + self.orderTest() + + def test_order3_faces(self): + self.location = 'faces' + self.sigmaTest = 3 + self.orderTest() + + def test_order6_faces(self): + self.location = 'faces' + self.sigmaTest = 6 self.orderTest() From 4660b177a1f0ceca9336c8263063fa21cdcea1fe Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Tue, 30 Jul 2013 22:48:27 -0700 Subject: [PATCH 19/51] Names in tests. --- SimPEG/tests/test_massMatrices.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/SimPEG/tests/test_massMatrices.py b/SimPEG/tests/test_massMatrices.py index f8fc99b0..c304dda5 100644 --- a/SimPEG/tests/test_massMatrices.py +++ b/SimPEG/tests/test_massMatrices.py @@ -32,8 +32,6 @@ from OrderTest import OrderTest class TestInnerProducts(OrderTest): """Integrate an function over a unit cube domain using edgeInnerProducts and faceInnerProducts.""" - name = "Edge Inner Product" - def getError(self): call = lambda fun, xyz: fun(xyz[:, 0], xyz[:, 1], xyz[:, 2]) @@ -80,31 +78,37 @@ class TestInnerProducts(OrderTest): return err def test_order1_edges(self): + self.name = "Edge Inner Product - Isotropic" self.location = 'edges' self.sigmaTest = 1 self.orderTest() def test_order3_edges(self): + self.name = "Edge Inner Product - Anisotropic" self.location = 'edges' self.sigmaTest = 3 self.orderTest() def test_order6_edges(self): + self.name = "Edge Inner Product - Full Tensor" self.location = 'edges' self.sigmaTest = 6 self.orderTest() def test_order1_faces(self): + self.name = "Face Inner Product - Isotropic" self.location = 'faces' self.sigmaTest = 1 self.orderTest() def test_order3_faces(self): + self.name = "Face Inner Product - Anisotropic" self.location = 'faces' self.sigmaTest = 3 self.orderTest() def test_order6_faces(self): + self.name = "Face Inner Product - Full Tensor" self.location = 'faces' self.sigmaTest = 6 self.orderTest() From 68e44cca5a253c9277ebd640c04ce294e772c565 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Tue, 30 Jul 2013 22:52:37 -0700 Subject: [PATCH 20/51] Deleted massMatrices.py This code is not working and is repeated in InnerProducts.py --- SimPEG/massMatrices.py | 94 ------------------------------------------ 1 file changed, 94 deletions(-) delete mode 100644 SimPEG/massMatrices.py diff --git a/SimPEG/massMatrices.py b/SimPEG/massMatrices.py deleted file mode 100644 index ead21e3a..00000000 --- a/SimPEG/massMatrices.py +++ /dev/null @@ -1,94 +0,0 @@ -import numpy as np -from scipy import sparse as sp -from sputils import sdiag, speye, kron3, spzeros -from utils import mkvc - - - -def getEdgeMassMatrix(sigma,mesh): - """Get anisotropic mass matrix""" - - n = array([size(mesh.h[0]),size(mesh.h[1]),size(mesh.h[2])]) - nx = prod(n + [1, 0, 0]) - ex = reshape(arange(0,nx),[n[0]+1,n[1],n[2]]) - ny = prod(n + [0, 1, 0]) - ey = reshape(arange(0,ny),[n[0],n[1]+1,n[2]]) - nz = prod(n + [0, 0, 1]); - ez = reshape(arange(0,nz),[n[0],n[1],n[2]+1]) - - - i = arange(0,n[0]-1); j = arange(0,n[1]-1); k = arange(0,n[2]-1) - - # corner i,j,k - Px1 = take(ex,[i,j,k]); Py1 = take(ey,[i,j,k]); Pz1 = take(ez,[i,j,k]) - # corner i+1,j,k - Px2 = take(ex,[i,j,k]); Py2 = take(ey,[i+1,j,k]); Pz2 = take(ez,[i+1,j,k]) - # corner i,j+1,k - Px3 = take(ex,[i,j+1,k]); Py3 = take(ey,[i,j,k]); Pz3 = take(ez,[i,j+1,k]) - # corner i+1,j+1,k - Px4 = take(ex,[i,j+1,k]); Py4 = take(ey,[i+1,j,k]); Pz4 = take(ez,[i+1,j+1,k]); - - # corner i,j,k+1 - Px5 = take(ex,[i,j,k+1]); Py5 = take(ey,[i,j,k+1]); Pz5 = take(ez,[i,j,k]) - # corner i+1,j,k+1 - Px6 = take(ex,[i,j,k+1]); Py6 = take(ey,[i+1,j,k+1]); Pz6 = take(ez,[i+1,j,k]) - # corner i,j+1,k+1 - Px7 = take(ex,[i,j+1,k+1]); Py7 = take(ey,[i,j,k+1]); Pz7 = take(ez,[i,j+1,k]) - # corner i+1,j+1,k+1 - Px8 = take(ex,[i,j+1,k+1]); Py8 = take(ey,[i+1,j,k+1]); Pz8 = take(ez,[i+1,j+1,k]) - - - nx1 = size(Px1); ny1 = size(Py1); nz1 = size(Pz1) - #sparse.coo_matrix((V,(I,J)),shape=(4,4)) - P1 = block_diag(( sparse.coo_matrix(arange(0,nx1),Px1(:), e(nx1), nx1,nx), - sparse.coo_matrix(arange(0,ny1),Py1(:),e(ny1), ny1,ny), - sparse.coo_matrix(arange(0,nz1),Pz1(:),e(nz1), nz1,nz))) - - nx2 = numel(Px2); ny2 = numel(Py2); nz2 = numel(Pz2); - P2 = blkdiag( sparse(1:nx2,Px2(:), e(nx2), nx2,nx) , ... - sparse(1:ny2,Py2(:),e(ny2), ny2,ny), ... - sparse(1:nz2,Pz2(:),e(nz2), nz2,nz)); - - nx3 = numel(Px3); ny3 = numel(Py3); nz3 = numel(Pz3); - P3 = blkdiag( sparse(1:nx3,Px3(:), e(nx3), nx3,nx) , ... - sparse(1:ny3,Py3(:),e(ny3), ny3,ny), ... - sparse(1:nz3,Pz3(:),e(nz3), nz3,nz)); - - nx4 = numel(Px4); ny4 = numel(Py4); nz4 = numel(Pz4); - P4 = blkdiag( sparse(1:nx4,Px4(:), e(nx4), nx4,nx) , ... - sparse(1:ny4,Py4(:), e(ny4), ny4,ny), ... - sparse(1:nz4,Pz4(:), e(nz4), nz4,nz)); - - nx5 = numel(Px5); ny5 = numel(Py5); nz5 = numel(Pz5); - P5 = blkdiag( sparse(1:nx5,Px5(:), e(nx5), nx5,nx) , ... - sparse(1:ny5,Py5(:), e(ny5), ny5,ny), ... - sparse(1:nz5,Pz5(:), e(nz5), nz5,nz)); - - nx6 = numel(Px6); ny6 = numel(Py6); nz6 = numel(Pz6); - P6 = blkdiag( sparse(1:nx6,Px6(:), e(nx6), nx6,nx) , ... - sparse(1:ny6,Py6(:), e(ny6), ny6,ny), ... - sparse(1:nz6,Pz6(:), e(nz6), nz6,nz)); - - nx7 = numel(Px7); ny7 = numel(Py7); nz7 = numel(Pz7); - P7 = blkdiag( sparse(1:nx7,Px7(:), e(nx7), nx7,nx) , ... - sparse(1:ny7,Py7(:), e(ny7), ny7,ny), ... - sparse(1:nz7,Pz7(:), e(nz7), nz7,nz)); - - nx8 = numel(Px8); ny8 = numel(Py8); nz8 = numel(Pz8); - P8 = blkdiag( sparse(1:nx8,Px8(:), e(nx8), nx8,nx) , ... - sparse(1:ny8,Py8(:), e(ny8), ny8,ny), ... - sparse(1:nz8,Pz8(:), e(nz8), nz8,nz)); - - V = sdiag(sqrt([v(:); v(:); v(:)])); - - # generate the conductivity - S = [sdiag(sig(:,1)) , sdiag(sig(:,4)) , sdiag(sig(:,5)); ... - sdiag(sig(:,4)) , sdiag(sig(:,2)) , sdiag(sig(:,6)); ... - sdiag(sig(:,5)) , sdiag(sig(:,6)) , sdiag(sig(:,3))]; - - # scale by the volume - S = V*S*V; - - M = 1/8*(P1'*S*P1 + P2'*S*P2 + P3'*S*P3 + P4'*S*P4 + ... - P5'*S*P5 + P6'*S*P6 + P7'*S*P7 + P8'*S*P8); - \ No newline at end of file From 7e169bbb7988e28c578a8c3b7eaeb6345cf8dc6b Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Tue, 30 Jul 2013 22:56:36 -0700 Subject: [PATCH 21/51] Cleaned utils code, merged in subArray. --- SimPEG/subArray.py | 8 -------- SimPEG/utils.py | 19 ++++++++++++------- 2 files changed, 12 insertions(+), 15 deletions(-) delete mode 100644 SimPEG/subArray.py diff --git a/SimPEG/subArray.py b/SimPEG/subArray.py deleted file mode 100644 index 2811f350..00000000 --- a/SimPEG/subArray.py +++ /dev/null @@ -1,8 +0,0 @@ -import numpy as np - -def getSubArray(A,ind): - """subArray""" - i = ind[0]; j = ind[1]; k = ind[2] - - return A[i,:,:][:,j,:][:,:,k] - \ No newline at end of file diff --git a/SimPEG/utils.py b/SimPEG/utils.py index b9e17847..ec89739b 100644 --- a/SimPEG/utils.py +++ b/SimPEG/utils.py @@ -1,5 +1,5 @@ import numpy as np -from numpy import * + def reshapeF(x, size): return np.reshape(x, size, order='F') @@ -106,13 +106,13 @@ def ind2sub(shape, ind): mult = [1] for i in range(0, len(revshp)-1): mult.extend([mult[i]*revshp[i]]) - mult = array(mult).reshape(len(mult)) + mult = np.array(mult).reshape(len(mult)) sub = [] for i in range(0, len(shape)): - sub.extend([math.floor(ind / mult[i])]) - ind = ind - (math.floor(ind/mult[i]) * mult[i]) + sub.extend([np.math.floor(ind / mult[i])]) + ind = ind - (np.math.floor(ind/mult[i]) * mult[i]) return sub @@ -122,7 +122,12 @@ def sub2ind(shape, subs): mult = [1] for i in range(0, len(revshp)-1): mult.extend([mult[i]*revshp[i]]) - mult = array(mult).reshape(len(mult), 1) + mult = np.array(mult).reshape(len(mult), 1) - idx = dot((subs), (mult)) - return idx \ No newline at end of file + idx = np.dot((subs), (mult)) + return idx + + +def getSubArray(A, ind): + """subArray""" + return A[ind[0], :, :][:, ind[1], :][:, :, ind[2]] From 47c0c7603df8be31526f82495cacd68ce89586b7 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Tue, 30 Jul 2013 23:43:13 -0700 Subject: [PATCH 22/51] Area and indexCube bug fix --- SimPEG/LogicallyOrthogonalMesh.py | 42 ++++++++++++++++++++++++++++--- SimPEG/utils.py | 25 ++++++++++-------- 2 files changed, 52 insertions(+), 15 deletions(-) diff --git a/SimPEG/LogicallyOrthogonalMesh.py b/SimPEG/LogicallyOrthogonalMesh.py index 15dcb09c..3ee037df 100644 --- a/SimPEG/LogicallyOrthogonalMesh.py +++ b/SimPEG/LogicallyOrthogonalMesh.py @@ -87,7 +87,7 @@ class LogicallyOrthogonalMesh(BaseMesh, DiffOperators): # , LOMGrid def fget(self): if(self._vol is None): if self.dim == 2: - A, B, C, D = indexCube('ABCD', np.array([self.nNx, self.nNy])) + A, B, C, D = indexCube('ABCD', self.n+1) normal, area, length = faceInfo(np.c_[self.gridN, np.zeros((self.nN, 1))], A, B, C, D) self._vol = area elif self.dim == 3: @@ -97,7 +97,7 @@ class LogicallyOrthogonalMesh(BaseMesh, DiffOperators): # , LOMGrid # T3 = [B D E G]; % mid # T4 = [B C D G]; % cutted edge # T5 = [D E G H]; % cutted edge - A, B, C, D, E, F, G, H = indexCube('ABCDEFGH', np.array([self.nNx, self.nNy, self.nNz])) + A, B, C, D, E, F, G, H = indexCube('ABCDEFGH', self.n+1) v1 = volTetra(self.gridN, A, B, D, E) # cutted edge v2 = volTetra(self.gridN, B, E, F, G) # cutted edge @@ -111,13 +111,46 @@ class LogicallyOrthogonalMesh(BaseMesh, DiffOperators): # , LOMGrid _vol = None vol = property(**vol()) + def area(): + doc = "Face areas." + + def fget(self): + if(self._area is None): + # Compute areas of cell faces + if(self.dim == 2): + xy = self.gridN + length = lambda x: (x[:, 0]**2 + x[:, 1]**2)**0.5 + + A, B = indexCube('AB', self.n+1, np.array([self.nNx, self.nCy])) + area1 = length(xy[B, :] - xy[A, :]) + A, D = indexCube('AD', self.n+1, np.array([self.nCx, self.nNy])) + area2 = length(xy[D, :] - xy[A, :]) + self._area = np.r_[mkvc(area1), mkvc(area2)] + elif(self.dim == 3): + + A, E, F, B = indexCube('AEFB', self.n+1, np.array([self.nNx, self.nCy, self.nCz])) + normal, area1, length = faceInfo(self.gridN, A, E, F, B) + + A, D, H, E = indexCube('ADHE', self.n+1, np.array([self.nCx, self.nNy, self.nCz])) + normal, area2, length = faceInfo(self.gridN, A, D, H, E) + + A, B, C, D = indexCube('ABCD', self.n+1, np.array([self.nCx, self.nCy, self.nNz])) + normal, area3, length = faceInfo(self.gridN, A, B, C, D) + + self._area = np.r_[mkvc(area1), mkvc(area2), mkvc(area3)] + return self._area + return locals() + _area = None + area = property(**area()) + if __name__ == '__main__': nc = 5 h1 = np.cumsum(np.r_[0, np.ones(nc)/(nc)]) + nc = 7 h2 = np.cumsum(np.r_[0, np.ones(nc)/(nc)]) h3 = np.cumsum(np.r_[0, np.ones(nc)/(nc)]) - dee3 = False + dee3 = True if dee3: X, Y, Z = ndgrid(h1, h2, h3, vector=False) M = LogicallyOrthogonalMesh([X, Y, Z]) @@ -127,4 +160,5 @@ if __name__ == '__main__': # print M.r(M.gridCC, format='M') # print M.gridN[:, 0] - print np.sum(M.vol) + print M.nE + print M.area diff --git a/SimPEG/utils.py b/SimPEG/utils.py index eff11748..9847751f 100644 --- a/SimPEG/utils.py +++ b/SimPEG/utils.py @@ -124,14 +124,16 @@ def volTetra(xyz, A, B, C, D): return V/6 -def indexCube(nodes, nN): +def indexCube(nodes, gridSize, n=None): """ Returns the index of nodes on the mesh. Input: - nodes - string of which nodes to return. e.g. 'ABCD' - nN - size of the nodal grid + nodes - string of which nodes to return. e.g. 'ABCD' + gridSize - size of the nodal grid + n - number of nodes each i,j,k direction: [ni,nj,nk] + Output: index - index in the order asked e.g. 'ABCD' --> (A,B,C,D) @@ -171,20 +173,21 @@ def indexCube(nodes, nN): """ assert type(nodes) == str, "Nodes must be a str variable: e.g. 'ABCD'" - assert type(nN) == np.ndarray, "Number of nodes must be an ndarray" + assert type(gridSize) == np.ndarray, "Number of nodes must be an ndarray" nodes = nodes.upper() # Make sure that we choose from the possible nodes. - possibleNodes = 'ABCD' if nN.size == 2 else 'ABCDEFGH' + possibleNodes = 'ABCD' if gridSize.size == 2 else 'ABCDEFGH' for node in nodes: assert node in possibleNodes, "Nodes must be chosen from: '%s'" % possibleNodes - dim = nN.size - nC = nN - 1 + dim = gridSize.size + if n is None: + n = gridSize - 1 if dim == 2: - ij = ndgrid(np.arange(nC[0]), np.arange(nC[1])) + ij = ndgrid(np.arange(n[0]), np.arange(n[1])) i, j = ij[:, 0], ij[:, 1] elif dim == 3: - ijk = ndgrid(np.arange(nC[0]), np.arange(nC[1]), np.arange(nC[2])) + ijk = ndgrid(np.arange(n[0]), np.arange(n[1]), np.arange(n[2])) i, j, k = ijk[:, 0], ijk[:, 1], ijk[:, 2] else: raise Exception('Only 2 and 3 dimensions supported.') @@ -195,9 +198,9 @@ def indexCube(nodes, nN): for node in nodes: shift = nodeMap[node] if dim == 2: - out += (sub2ind(nN, np.c_[i+shift[0], j+shift[1]]).flatten(), ) + out += (sub2ind(gridSize, np.c_[i+shift[0], j+shift[1]]).flatten(), ) elif dim == 3: - out += (sub2ind(nN, np.c_[i+shift[0], j+shift[1], k+shift[2]]).flatten(), ) + out += (sub2ind(gridSize, np.c_[i+shift[0], j+shift[1], k+shift[2]]).flatten(), ) return out From 451e4260cedb06b12b47dfdea819e3f7d5c8553e Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Wed, 31 Jul 2013 11:22:21 -0700 Subject: [PATCH 23/51] Removed x0 from input for LOM --- SimPEG/LogicallyOrthogonalMesh.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/SimPEG/LogicallyOrthogonalMesh.py b/SimPEG/LogicallyOrthogonalMesh.py index 3ee037df..5594b5b9 100644 --- a/SimPEG/LogicallyOrthogonalMesh.py +++ b/SimPEG/LogicallyOrthogonalMesh.py @@ -9,7 +9,7 @@ class LogicallyOrthogonalMesh(BaseMesh, DiffOperators): # , LOMGrid LogicallyOrthogonalMesh is a mesh class that deals with logically orthogonal meshes. """ - def __init__(self, nodes, x0=None): + def __init__(self, nodes): assert type(nodes) == list, "'nodes' variable must be a list of np.ndarray" for i, nodes_i in enumerate(nodes): @@ -19,9 +19,7 @@ class LogicallyOrthogonalMesh(BaseMesh, DiffOperators): # , LOMGrid assert len(nodes[0].shape) == len(nodes), "Dimension mismatch" assert len(nodes[0].shape) > 1, "Not worth using LOM for a 1D mesh." - super(LogicallyOrthogonalMesh, self).__init__(np.array(nodes[0].shape)-1, x0) - - assert len(nodes[0].shape) == len(self.x0), "Dimension mismatch. x0 != len(h)" + super(LogicallyOrthogonalMesh, self).__init__(np.array(nodes[0].shape)-1, None) # Save nodes to private variable _gridN as vectors self._gridN = np.ones((nodes[0].size, self.dim)) From 34e380ccb903117a2f7a831a79755b53e0664706 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Wed, 31 Jul 2013 15:07:14 -0700 Subject: [PATCH 24/51] edge and tangent calculating, along with testing functions. --- SimPEG/LogicallyOrthogonalMesh.py | 57 +++++++++++++++++--- SimPEG/tests/test_LogicallyOrthogonalMesh.py | 52 ++++++++++++++++++ SimPEG/utils.py | 2 +- 3 files changed, 104 insertions(+), 7 deletions(-) create mode 100644 SimPEG/tests/test_LogicallyOrthogonalMesh.py diff --git a/SimPEG/LogicallyOrthogonalMesh.py b/SimPEG/LogicallyOrthogonalMesh.py index 5594b5b9..ae11647a 100644 --- a/SimPEG/LogicallyOrthogonalMesh.py +++ b/SimPEG/LogicallyOrthogonalMesh.py @@ -3,12 +3,20 @@ from BaseMesh import BaseMesh from DiffOperators import DiffOperators from utils import mkvc, ndgrid, volTetra, indexCube, faceInfo +# Some helper functions. +length2D = lambda x: (x[:, 0]**2 + x[:, 1]**2)**0.5 +length3D = lambda x: (x[:, 0]**2 + x[:, 1]**2 + x[:, 2]**2)**0.5 +normalize2D = lambda x: x/np.kron(np.ones((1, 2)), mkvc(length2D(x), 2)) +normalize3D = lambda x: x/np.kron(np.ones((1, 3)), mkvc(length3D(x), 2)) + class LogicallyOrthogonalMesh(BaseMesh, DiffOperators): # , LOMGrid """ LogicallyOrthogonalMesh is a mesh class that deals with logically orthogonal meshes. """ + _meshType = 'LOM' + def __init__(self, nodes): assert type(nodes) == list, "'nodes' variable must be a list of np.ndarray" @@ -117,12 +125,10 @@ class LogicallyOrthogonalMesh(BaseMesh, DiffOperators): # , LOMGrid # Compute areas of cell faces if(self.dim == 2): xy = self.gridN - length = lambda x: (x[:, 0]**2 + x[:, 1]**2)**0.5 - A, B = indexCube('AB', self.n+1, np.array([self.nNx, self.nCy])) - area1 = length(xy[B, :] - xy[A, :]) + area1 = length2D(xy[B, :] - xy[A, :]) A, D = indexCube('AD', self.n+1, np.array([self.nCx, self.nNy])) - area2 = length(xy[D, :] - xy[A, :]) + area2 = length2D(xy[D, :] - xy[A, :]) self._area = np.r_[mkvc(area1), mkvc(area2)] elif(self.dim == 3): @@ -141,6 +147,45 @@ class LogicallyOrthogonalMesh(BaseMesh, DiffOperators): # , LOMGrid _area = None area = property(**area()) + def edge(): + doc = "Edge legnths." + + def fget(self): + if(self._edge is None or self._tangents is None): + if(self.dim == 2): + xy = self.gridN + A, D = indexCube('AD', self.n+1, np.array([self.nCx, self.nNy])) + edge1 = xy[D, :] - xy[A, :] + A, B = indexCube('AB', self.n+1, np.array([self.nNx, self.nCy])) + edge2 = xy[B, :] - xy[A, :] + self._edge = np.r_[mkvc(length2D(edge1)), mkvc(length2D(edge2))] + self._tangents = np.r_[edge1, edge2]/np.c_[self._edge, self._edge] + elif(self.dim == 3): + xyz = self.gridN + A, D = indexCube('AD', self.n+1, np.array([self.nCx, self.nNy, self.nNz])) + edge1 = xyz[D, :] - xyz[A, :] + A, B = indexCube('AB', self.n+1, np.array([self.nNx, self.nCy, self.nNz])) + edge2 = xyz[B, :] - xyz[A, :] + A, E = indexCube('AE', self.n+1, np.array([self.nNx, self.nNy, self.nCz])) + edge3 = xyz[E, :] - xyz[A, :] + self._edge = np.r_[mkvc(length3D(edge1)), mkvc(length3D(edge2)), mkvc(length3D(edge3))] + self._tangents = np.r_[edge1, edge2, edge3]/np.c_[self._edge, self._edge, self._edge] + return self._edge + return locals() + _edge = None + edge = property(**edge()) + + def tangents(): + doc = "Edge tangents." + + def fget(self): + if(self._tangents is None): + self.edge # calling .edge will create the tangents + return self._tangents + return locals() + _tangents = None + tangents = property(**tangents()) + if __name__ == '__main__': nc = 5 @@ -148,7 +193,7 @@ if __name__ == '__main__': nc = 7 h2 = np.cumsum(np.r_[0, np.ones(nc)/(nc)]) h3 = np.cumsum(np.r_[0, np.ones(nc)/(nc)]) - dee3 = True + dee3 = False if dee3: X, Y, Z = ndgrid(h1, h2, h3, vector=False) M = LogicallyOrthogonalMesh([X, Y, Z]) @@ -159,4 +204,4 @@ if __name__ == '__main__': # print M.r(M.gridCC, format='M') # print M.gridN[:, 0] print M.nE - print M.area + print M.r(M.tangents, 'E', 'Ex', 'M') diff --git a/SimPEG/tests/test_LogicallyOrthogonalMesh.py b/SimPEG/tests/test_LogicallyOrthogonalMesh.py new file mode 100644 index 00000000..6c6eade9 --- /dev/null +++ b/SimPEG/tests/test_LogicallyOrthogonalMesh.py @@ -0,0 +1,52 @@ +import numpy as np +import unittest +import sys +sys.path.append('../') +from TensorMesh import TensorMesh +from LogicallyOrthogonalMesh import LogicallyOrthogonalMesh +from OrderTest import OrderTest +from scipy.sparse.linalg import dsolve +from utils import ndgrid + + +class BasicLOMTests(unittest.TestCase): + + def setUp(self): + a = np.array([1, 1, 1]) + b = np.array([1, 2]) + c = np.array([1, 4]) + gridIt = lambda h: [np.cumsum(np.r_[0, x]) for x in h] + X, Y = ndgrid(gridIt([a, b]), vector=False) + self.TM2 = TensorMesh([a, b]) + self.LOM2 = LogicallyOrthogonalMesh([X, Y]) + X, Y, Z = ndgrid(gridIt([a, b, c]), vector=False) + self.TM3 = TensorMesh([a, b, c]) + self.LOM3 = LogicallyOrthogonalMesh([X, Y, Z]) + + def test_area_3D(self): + test_area = np.array([1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 1, 1, 1, 2, 2, 2, 1, 1, 1, 2, 2, 2, 1, 1, 1, 2, 2, 2]) + self.assertTrue(np.all(self.LOM3.area == test_area)) + + def test_vol_3D(self): + test_vol = np.array([1, 1, 1, 2, 2, 2, 4, 4, 4, 8, 8, 8]) + np.testing.assert_almost_equal(self.LOM3.vol, test_vol) + self.assertTrue(True) # Pass if you get past the assertion. + + def test_vol_2D(self): + test_vol = np.array([1, 1, 1, 2, 2, 2]) + t1 = np.all(self.LOM2.vol == test_vol) + self.assertTrue(t1) + + def test_edge_3D(self): + test_edge = np.array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4]) + t1 = np.all(self.LOM3.edge == test_edge) + self.assertTrue(t1) + + def test_edge_2D(self): + test_edge = np.array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2]) + t1 = np.all(self.LOM2.edge == test_edge) + self.assertTrue(t1) + + +if __name__ == '__main__': + unittest.main() diff --git a/SimPEG/utils.py b/SimPEG/utils.py index c4280457..55771729 100644 --- a/SimPEG/utils.py +++ b/SimPEG/utils.py @@ -259,7 +259,7 @@ def faceInfo(xyz, A, B, C, D, average=True): nD = cross(DA, CD) length = lambda x: (x[:, 0]**2 + x[:, 1]**2 + x[:, 2]**2)**0.5 - normalize = lambda x: x/np.kron(np.ones((1, x.shape[1])), mkvc(length(N), 2)) + normalize = lambda x: x/np.kron(np.ones((1, x.shape[1])), mkvc(length(x), 2)) if average: # average the normals at each vertex. N = (nA + nB + nC + nD)/4 # this is intrinsically weighted by area From ea223d073c3fbe9bed6febbc5ed6ad8472a31bd6 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Wed, 31 Jul 2013 18:07:52 -0700 Subject: [PATCH 25/51] Face Normals. Note that there are some serious differences in how these are stored based in 3D vs 2D, but that is the nature of the beast. :) --- SimPEG/LogicallyOrthogonalMesh.py | 56 ++++++++++++++++++++++++------- SimPEG/utils.py | 15 +++++---- 2 files changed, 52 insertions(+), 19 deletions(-) diff --git a/SimPEG/LogicallyOrthogonalMesh.py b/SimPEG/LogicallyOrthogonalMesh.py index ae11647a..af141ac7 100644 --- a/SimPEG/LogicallyOrthogonalMesh.py +++ b/SimPEG/LogicallyOrthogonalMesh.py @@ -94,7 +94,7 @@ class LogicallyOrthogonalMesh(BaseMesh, DiffOperators): # , LOMGrid if(self._vol is None): if self.dim == 2: A, B, C, D = indexCube('ABCD', self.n+1) - normal, area, length = faceInfo(np.c_[self.gridN, np.zeros((self.nN, 1))], A, B, C, D) + normal, area = faceInfo(np.c_[self.gridN, np.zeros((self.nN, 1))], A, B, C, D) self._vol = area elif self.dim == 3: # Each polyhedron can be decomposed into 5 tetrahedrons @@ -121,32 +121,67 @@ class LogicallyOrthogonalMesh(BaseMesh, DiffOperators): # , LOMGrid doc = "Face areas." def fget(self): - if(self._area is None): + if(self._area is None or self._normals is None): # Compute areas of cell faces if(self.dim == 2): xy = self.gridN A, B = indexCube('AB', self.n+1, np.array([self.nNx, self.nCy])) - area1 = length2D(xy[B, :] - xy[A, :]) + edge1 = xy[B, :] - xy[A, :] + normal1 = np.c_[edge1[:, 1], -edge1[:, 0]] + area1 = length2D(edge1) A, D = indexCube('AD', self.n+1, np.array([self.nCx, self.nNy])) - area2 = length2D(xy[D, :] - xy[A, :]) + # Note that we are doing A-D to make sure the normal points the right way. + # Think about it. Look at the picture. Normal points towards C iff you do this. + edge2 = xy[A, :] - xy[D, :] + normal2 = np.c_[edge2[:, 1], -edge2[:, 0]] + area2 = length2D(edge2) self._area = np.r_[mkvc(area1), mkvc(area2)] + self._normals = [normalize2D(normal1), normalize2D(normal2)] elif(self.dim == 3): A, E, F, B = indexCube('AEFB', self.n+1, np.array([self.nNx, self.nCy, self.nCz])) - normal, area1, length = faceInfo(self.gridN, A, E, F, B) + normal1, area1 = faceInfo(self.gridN, A, E, F, B, average=False, normalizeNormals=False) A, D, H, E = indexCube('ADHE', self.n+1, np.array([self.nCx, self.nNy, self.nCz])) - normal, area2, length = faceInfo(self.gridN, A, D, H, E) + normal2, area2 = faceInfo(self.gridN, A, D, H, E, average=False, normalizeNormals=False) A, B, C, D = indexCube('ABCD', self.n+1, np.array([self.nCx, self.nCy, self.nNz])) - normal, area3, length = faceInfo(self.gridN, A, B, C, D) + normal3, area3 = faceInfo(self.gridN, A, B, C, D, average=False, normalizeNormals=False) self._area = np.r_[mkvc(area1), mkvc(area2), mkvc(area3)] + self._normals = [normal1, normal2, normal3] return self._area return locals() _area = None area = property(**area()) + def normals(): + doc = """ +Face normals: calling this will average +the computed normals so that there is one +per face. This is especially relevant in +3D, as there are up to 4 different normals +for each face that will be different. + +To reshape the normals into a matrix and get the y component: + +NyX, NyY, NyZ = M.r(M.normals, 'F', 'Fy', 'M') +""" + + def fget(self): + if(self._tangents is None): + self.area # calling .area will create the face normals + if self.dim == 2: + return normalize2D(np.r_[self._normals[0], self._normals[1]]) + elif self.dim == 3: + normal1 = (self._normals[0][0] + self._normals[0][1] + self._normals[0][2] + self._normals[0][3])/4 + normal2 = (self._normals[1][0] + self._normals[1][1] + self._normals[1][2] + self._normals[1][3])/4 + normal3 = (self._normals[2][0] + self._normals[2][1] + self._normals[2][2] + self._normals[2][3])/4 + return normalize3D(np.r_[normal1, normal2, normal3]) + return locals() + _normals = None + normals = property(**normals()) + def edge(): doc = "Edge legnths." @@ -193,7 +228,7 @@ if __name__ == '__main__': nc = 7 h2 = np.cumsum(np.r_[0, np.ones(nc)/(nc)]) h3 = np.cumsum(np.r_[0, np.ones(nc)/(nc)]) - dee3 = False + dee3 = True if dee3: X, Y, Z = ndgrid(h1, h2, h3, vector=False) M = LogicallyOrthogonalMesh([X, Y, Z]) @@ -201,7 +236,4 @@ if __name__ == '__main__': X, Y = ndgrid(h1, h2, vector=False) M = LogicallyOrthogonalMesh([X, Y]) - # print M.r(M.gridCC, format='M') - # print M.gridN[:, 0] - print M.nE - print M.r(M.tangents, 'E', 'Ex', 'M') + print M.r(M.normals, 'F', 'Fx', 'V') diff --git a/SimPEG/utils.py b/SimPEG/utils.py index 55771729..e182cc5e 100644 --- a/SimPEG/utils.py +++ b/SimPEG/utils.py @@ -204,7 +204,7 @@ def indexCube(nodes, gridSize, n=None): return out -def faceInfo(xyz, A, B, C, D, average=True): +def faceInfo(xyz, A, B, C, D, average=True, normalizeNormals=True): """ function [N] = faceInfo(y,A,B,C,D) @@ -232,7 +232,8 @@ def faceInfo(xyz, A, B, C, D, average=True): Last modified on: 2013/07/26 """ - + assert type(average) is bool, 'average must be a boolean' + assert type(normalizeNormals) is bool, 'normalizeNormals must be a boolean' # compute normal that is pointing away from you. # # A -------A-B------- B @@ -266,7 +267,10 @@ def faceInfo(xyz, A, B, C, D, average=True): # normalize N = normalize(N) else: - N = [normalize(nA), normalize(nB), normalize(nC), normalize(nD)] + if normalizeNormals: + N = [normalize(nA), normalize(nB), normalize(nC), normalize(nD)] + else: + N = [nA, nB, nC, nD] # Area calculation # @@ -276,10 +280,7 @@ def faceInfo(xyz, A, B, C, D, average=True): # So also could be viewed as the average parallelogram. area = (length(nA)+length(nB)+length(nC)+length(nD))/4 - # simple edge length calculations - edgeLengths = [length(AB), length(BC), length(CD), length(DA)] - - return N, area, edgeLengths + return N, area def ind2sub(shape, ind): From 5af4a5f5c27b833cc975423c28d09884e306731b Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Wed, 31 Jul 2013 18:34:03 -0700 Subject: [PATCH 26/51] 3D volume tweak now takes the average of two different ways to divide into tetrahedras --- SimPEG/LogicallyOrthogonalMesh.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/SimPEG/LogicallyOrthogonalMesh.py b/SimPEG/LogicallyOrthogonalMesh.py index af141ac7..dd424e6e 100644 --- a/SimPEG/LogicallyOrthogonalMesh.py +++ b/SimPEG/LogicallyOrthogonalMesh.py @@ -98,20 +98,22 @@ class LogicallyOrthogonalMesh(BaseMesh, DiffOperators): # , LOMGrid self._vol = area elif self.dim == 3: # Each polyhedron can be decomposed into 5 tetrahedrons - # T1 = [A B D E]; % cutted edge - # T2 = [B E F G]; % cutted edge - # T3 = [B D E G]; % mid - # T4 = [B C D G]; % cutted edge - # T5 = [D E G H]; % cutted edge + # However, this presents a choice so we may as well divide in two ways and average. A, B, C, D, E, F, G, H = indexCube('ABCDEFGH', self.n+1) - v1 = volTetra(self.gridN, A, B, D, E) # cutted edge - v2 = volTetra(self.gridN, B, E, F, G) # cutted edge - v3 = volTetra(self.gridN, B, D, E, G) # mid - v4 = volTetra(self.gridN, B, C, D, G) # cutted edge - v5 = volTetra(self.gridN, D, E, G, H) # cutted edge + vol1 = (volTetra(self.gridN, A, B, D, E) + # cutted edge top + volTetra(self.gridN, B, E, F, G) + # cutted edge top + volTetra(self.gridN, B, D, E, G) + # middle + volTetra(self.gridN, B, C, D, G) + # cutted edge bottom + volTetra(self.gridN, D, E, G, H)) # cutted edge bottom - self._vol = v1 + v2 + v3 + v4 + v5 + vol2 = (volTetra(self.gridN, A, F, B, C) + # cutted edge top + volTetra(self.gridN, A, E, F, H) + # cutted edge top + volTetra(self.gridN, A, H, F, C) + # middle + volTetra(self.gridN, C, H, D, A) + # cutted edge bottom + volTetra(self.gridN, C, G, H, F)) # cutted edge bottom + + self._vol = (vol1 + vol2)/2 return self._vol return locals() _vol = None @@ -169,7 +171,7 @@ NyX, NyY, NyZ = M.r(M.normals, 'F', 'Fy', 'M') """ def fget(self): - if(self._tangents is None): + if(self._normals is None): self.area # calling .area will create the face normals if self.dim == 2: return normalize2D(np.r_[self._normals[0], self._normals[1]]) From 4887f4b13086df8eab94670d9de4f1f8bcd6bc48 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Thu, 1 Aug 2013 08:23:23 -0700 Subject: [PATCH 27/51] Minor changes to utile so that it works with matrices. --- SimPEG/utils.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/SimPEG/utils.py b/SimPEG/utils.py index e182cc5e..e92f81cb 100644 --- a/SimPEG/utils.py +++ b/SimPEG/utils.py @@ -18,6 +18,9 @@ def mkvc(x, numDims=1): > (3, 1, 1) """ + if type(x) == np.matrix: + x = np.array(x) + assert type(x) == np.ndarray, "Vector must be a numpy array" if numDims == 1: @@ -259,7 +262,7 @@ def faceInfo(xyz, A, B, C, D, average=True, normalizeNormals=True): nC = cross(CD, BC) nD = cross(DA, CD) - length = lambda x: (x[:, 0]**2 + x[:, 1]**2 + x[:, 2]**2)**0.5 + length = lambda x: np.sqrt(x[:, 0]**2 + x[:, 1]**2 + x[:, 2]**2) normalize = lambda x: x/np.kron(np.ones((1, x.shape[1])), mkvc(length(x), 2)) if average: # average the normals at each vertex. @@ -278,6 +281,8 @@ def faceInfo(xyz, A, B, C, D, average=True, normalizeNormals=True): # Each triangle is one half of the length of the cross product # # So also could be viewed as the average parallelogram. + print nA, nB, nC, nD + print length(nA), length(nB), length(nC), length(nD) area = (length(nA)+length(nB)+length(nC)+length(nD))/4 return N, area From 444feca101bff3805fb74900e487fd5dca6dc0af Mon Sep 17 00:00:00 2001 From: ehaber99 Date: Thu, 1 Aug 2013 15:05:12 -0700 Subject: [PATCH 28/51] initial forward EM --- SimPEG/EMforward.py | 49 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 SimPEG/EMforward.py diff --git a/SimPEG/EMforward.py b/SimPEG/EMforward.py new file mode 100644 index 00000000..fc143841 --- /dev/null +++ b/SimPEG/EMforward.py @@ -0,0 +1,49 @@ +import numpy as np +from utils import mkvc +import scipy.sparse.linalg.dsolve as dsl + +def getMisfit(m,mesh,forward): + + mu0 = 4*np.pi*1e-7 + omega = forward['omega'] #[param['indomega']] + rhs = forward['rhs'] #[:,param['indrhs']] + misfit = 0 + + # Maxwell's system for E + for i in range(len(omega)): + for j in range(rhs.shape[1]): + Curl = mesh.edgeCurl + #Grad = mesh.nodalGrad + sigma = np.exp(m) + Me,PP = mesh.getEdgeMass(sigma) + Mf = 1/mu0 * mesh.getFaceMass() # assume mu = mu0 + + A = Curl.T * Mf * Curl - 1j * omega[i] * Me + b = mkvc(np.array(rhs[:,j])) + e = dsl.spsolve(A,b) + e = mkvc(e,2) + #print np.linalg.norm(A*e-b)/np.linalg.norm(b) + P = forward['projection'] + d = P*e + r = mkvc(d - param.dobs[i,j,:],2) + + mis = mis + 0.5*(r.T*r) + # get derivatives + lam = dsl.spsolve(A.T,P.T*r) + Gij = PP.T*diag((PP*e)*mesh.vol) + dmis = dmis - Gij.T*lam + + + + +if __name__ == '__main__': + from TensorMesh import TensorMesh + h = [np.ones(7),np.ones(8),np.ones(9)] + mesh = TensorMesh(h) + ne = np.sum(mesh.nE) + Q = np.matrix(np.random.randn(ne,5)) + P = np.matrix(Q.T) + forward = {'omega':[1,2,3], 'rhs':Q,'projection':P} + + m = np.ones(mesh.nC) + getMisfit(m,mesh,forward) From dcae030d811b994b1d2786f5ab849f05d9bf7c7e Mon Sep 17 00:00:00 2001 From: ehaber99 Date: Fri, 2 Aug 2013 00:18:46 -0700 Subject: [PATCH 29/51] 1. Added output for the edgeInnerProduct so we can use it in the derivative phase 2. The getMisfit may be working but needs a little bit more work We need to go through our code to make sure that all vectors that can be multiplied by a matrix are set to vec = mkvc(vec,2) --- SimPEG/EMforward.py | 47 ++++++++++++++++++++++++++++++++--------- SimPEG/InnerProducts.py | 12 ++++++----- 2 files changed, 44 insertions(+), 15 deletions(-) diff --git a/SimPEG/EMforward.py b/SimPEG/EMforward.py index fc143841..0f65cb21 100644 --- a/SimPEG/EMforward.py +++ b/SimPEG/EMforward.py @@ -1,13 +1,15 @@ import numpy as np from utils import mkvc import scipy.sparse.linalg.dsolve as dsl +from InnerProducts import getFaceInnerProduct, getEdgeInnerProduct -def getMisfit(m,mesh,forward): +def getMisfit(m,mesh,forward,param): mu0 = 4*np.pi*1e-7 omega = forward['omega'] #[param['indomega']] rhs = forward['rhs'] #[:,param['indrhs']] - misfit = 0 + mis = 0 + dmis = m*0 # Maxwell's system for E for i in range(len(omega)): @@ -15,8 +17,8 @@ def getMisfit(m,mesh,forward): Curl = mesh.edgeCurl #Grad = mesh.nodalGrad sigma = np.exp(m) - Me,PP = mesh.getEdgeMass(sigma) - Mf = 1/mu0 * mesh.getFaceMass() # assume mu = mu0 + Me,PP = getEdgeInnerProduct(mesh,sigma) + Mf = 1/mu0 * getFaceInnerProduct(mesh) # assume mu = mu0 A = Curl.T * Mf * Curl - 1j * omega[i] * Me b = mkvc(np.array(rhs[:,j])) @@ -30,20 +32,45 @@ def getMisfit(m,mesh,forward): mis = mis + 0.5*(r.T*r) # get derivatives lam = dsl.spsolve(A.T,P.T*r) - Gij = PP.T*diag((PP*e)*mesh.vol) + lam = mkvc(lam,2) + Gij = - 1j * omega[i] * PP.T*sp.diag((PP*e)*mesh.vol) dmis = dmis - Gij.T*lam + return mis, dmis, d + if __name__ == '__main__': from TensorMesh import TensorMesh + from interpmat import interpmat + from scipy import sparse as sp + h = [np.ones(7),np.ones(8),np.ones(9)] mesh = TensorMesh(h) - ne = np.sum(mesh.nE) - Q = np.matrix(np.random.randn(ne,5)) - P = np.matrix(Q.T) - forward = {'omega':[1,2,3], 'rhs':Q,'projection':P} + xs = np.array([3.1,4.3,5.4,6.5]) + ys = np.array([3.2,4.1,5.4,6.2]) + zs = np.array([4.3,4.2,4.1,4.1]); + + xyz = mesh.gridEx + x = xyz[:,0]; y = xyz[:,1]; z = xyz[:,2] + Px = interpmat(x,y,z,xs,ys,zs) + xyz = mesh.gridEy + x = xyz[:,0]; y = xyz[:,1]; z = xyz[:,2] + Py = interpmat(x,y,z,xs,ys,zs) + xyz = mesh.gridEz + x = xyz[:,0]; y = xyz[:,1]; z = xyz[:,2] + Pz = interpmat(x,y,z,xs,ys,zs) + P = sp.hstack((Px,Py,Pz)) + + ne = np.sum(mesh.nE) + Q = np.matrix(np.random.randn(ne,5)) + omega = [1,2,3] + forward = {'omega':omega, 'rhs':Q,'projection':P} + dobs = np.ones(np.size(xs),np.shape(Q,2),np.size(omega)) + param = {'dobs':dobs} + + m = np.ones(mesh.nC) - getMisfit(m,mesh,forward) + getMisfit(m,mesh,forward,param) diff --git a/SimPEG/InnerProducts.py b/SimPEG/InnerProducts.py index bc482de4..d4b2e9ad 100644 --- a/SimPEG/InnerProducts.py +++ b/SimPEG/InnerProducts.py @@ -169,13 +169,15 @@ def getEdgeInnerProduct(mesh, sigma): # Cell volume v = np.sqrt(mesh.vol) v3 = np.r_[v, v, v] - V = sdiag(v3)*Sigma*sdiag(v3) # to keep symmetry + #V = sdiag(v3)*Sigma*sdiag(v3) # to keep symmetry - A = P000.T*V*P000 + P001.T*V*P001 + P010.T*V*P010 + P011.T*V*P011 + P100.T*V*P100 + P101.T*V*P101 + P110.T*V*P110 + P111.T*V*P111 + #A = P000.T*V*P000 + P001.T*V*P001 + P010.T*V*P010 + P011.T*V*P011 + P100.T*V*P100 + P101.T*V*P101 + P110.T*V*P110 + P111.T*V*P111 - A = 0.125*A - - return A + #A = 0.125*A + P = sp.vstack((sdiag(v3)*P000,sdiag(v3)*P001,sdiag(v3)*P010,sdiag(v3)*P011, + sdiag(v3)*P100,sdiag(v3)*P101,sdiag(v3)*P110,sdiag(v3)*P111)) + A = 0.125* (P.T*Sigma*P) + return A, P From ec4358d872fd9c9088c31f7cfe532d5159c445d5 Mon Sep 17 00:00:00 2001 From: ehaber99 Date: Fri, 2 Aug 2013 01:51:10 -0700 Subject: [PATCH 30/51] corrected bugs --- SimPEG/InnerProducts.py | 12 +++++++----- SimPEG/interpmat.py | 14 ++++++++------ 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/SimPEG/InnerProducts.py b/SimPEG/InnerProducts.py index d4b2e9ad..1ff5cea4 100644 --- a/SimPEG/InnerProducts.py +++ b/SimPEG/InnerProducts.py @@ -169,14 +169,14 @@ def getEdgeInnerProduct(mesh, sigma): # Cell volume v = np.sqrt(mesh.vol) v3 = np.r_[v, v, v] - #V = sdiag(v3)*Sigma*sdiag(v3) # to keep symmetry + V = sdiag(v3)*Sigma*sdiag(v3) # to keep symmetry - #A = P000.T*V*P000 + P001.T*V*P001 + P010.T*V*P010 + P011.T*V*P011 + P100.T*V*P100 + P101.T*V*P101 + P110.T*V*P110 + P111.T*V*P111 + A = P000.T*V*P000 + P001.T*V*P001 + P010.T*V*P010 + P011.T*V*P011 + P100.T*V*P100 + P101.T*V*P101 + P110.T*V*P110 + P111.T*V*P111 - #A = 0.125*A + A = 0.125*A P = sp.vstack((sdiag(v3)*P000,sdiag(v3)*P001,sdiag(v3)*P010,sdiag(v3)*P011, sdiag(v3)*P100,sdiag(v3)*P101,sdiag(v3)*P110,sdiag(v3)*P111)) - A = 0.125* (P.T*Sigma*P) + return A, P @@ -186,4 +186,6 @@ if __name__ == '__main__': h = [np.array([1, 2, 3, 4]), np.array([1, 2, 1, 4, 2]), np.array([1, 1, 4, 1])] mesh = TensorMesh(h) mu = np.ones((mesh.nC, 6)) - A = mesh.getFaceInnerProduct(mu) + A = getFaceInnerProduct(mesh,mu) + B = getEdgeInnerProduct(mesh,mu) + diff --git a/SimPEG/interpmat.py b/SimPEG/interpmat.py index 68b6c4c1..bfb80291 100644 --- a/SimPEG/interpmat.py +++ b/SimPEG/interpmat.py @@ -1,6 +1,7 @@ from scipy import sparse as sp import numpy as np + def interpmat(x,y,z,xr,yr,zr): # # This function does local linear interpolation @@ -23,7 +24,8 @@ def interpmat(x,y,z,xr,yr,zr): ind_z = np.array([0,0]) dx, dy, dz = np.zeros(2), np.zeros(2), np.zeros(2) for i in range(0, nps): - im = np.amin(abs(xr[i]-x)) + im = np.argmin(abs(xr[i]-x)) + print i,im if xr[i] - x[im] >= 0: # Point on the left ind_x[0] = im; ind_x[1] = im+1 else: # Point on the right @@ -33,7 +35,7 @@ def interpmat(x,y,z,xr,yr,zr): dx[0] = xr[i] - x[ind_x[0]] dx[1] = x[ind_x[1]] - xr[i] - im = np.amin(abs(yr[i] - y)) + im = np.argmin(abs(yr[i] - y)) if yr[i] - y[im] >= 0: # Point on the left ind_y[0] = im; ind_y[1] = im+1 else: # Point on the right @@ -43,7 +45,7 @@ def interpmat(x,y,z,xr,yr,zr): dy[0] = yr[i] - y[ind_y[0]] dy[1] = y[ind_y[1]] - yr[i]; - im = np.amin(abs(zr[i] - z)); + im = np.argmin(abs(zr[i] - z)); if zr[i] -z[im] >= 0: # Point on the left ind_z[0] = im; ind_z[1] = im+1 else: # Point on the right @@ -80,9 +82,9 @@ def interpmat(x,y,z,xr,yr,zr): if __name__ == '__main__': - x = np.array([1, 2, 3, 4]) - y = np.array([1, 2, 3, 4, 5]) - z = np.array([0, 1, 4, 6]) + x = np.array([1.1, 2.1, 3.6, 4.9]) + y = np.array([1.2, 2.2, 3.3, 4.9, 5.6]) + z = np.array([0.8, 1.7, 4.9, 6.5]) xr = np.array([2.5,3.2]) yr = np.array([2.4,3.6]) From f41463b241de0945dfeb149e72afefd3191403a0 Mon Sep 17 00:00:00 2001 From: ehaber99 Date: Fri, 2 Aug 2013 01:51:46 -0700 Subject: [PATCH 31/51] need more work but its closer --- SimPEG/EMforward.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/SimPEG/EMforward.py b/SimPEG/EMforward.py index 0f65cb21..9c332c9b 100644 --- a/SimPEG/EMforward.py +++ b/SimPEG/EMforward.py @@ -54,12 +54,15 @@ if __name__ == '__main__': xyz = mesh.gridEx x = xyz[:,0]; y = xyz[:,1]; z = xyz[:,2] + x = list(set(x)); y = list(set(y)); z = list(set(z)) Px = interpmat(x,y,z,xs,ys,zs) xyz = mesh.gridEy x = xyz[:,0]; y = xyz[:,1]; z = xyz[:,2] + x = list(set(x)); y = list(set(y)); z = list(set(z)) Py = interpmat(x,y,z,xs,ys,zs) xyz = mesh.gridEz x = xyz[:,0]; y = xyz[:,1]; z = xyz[:,2] + x = list(set(x)); y = list(set(y)); z = list(set(z)) Pz = interpmat(x,y,z,xs,ys,zs) P = sp.hstack((Px,Py,Pz)) @@ -67,7 +70,7 @@ if __name__ == '__main__': Q = np.matrix(np.random.randn(ne,5)) omega = [1,2,3] forward = {'omega':omega, 'rhs':Q,'projection':P} - dobs = np.ones(np.size(xs),np.shape(Q,2),np.size(omega)) + dobs = np.ones([np.size(xs),5,np.size(omega)]) param = {'dobs':dobs} From 24773c4f60b008e0c88b4b18783125ad35655ad4 Mon Sep 17 00:00:00 2001 From: ehaber99 Date: Sat, 3 Aug 2013 02:07:15 -0700 Subject: [PATCH 32/51] my first attempt at a wrapper to overcome some of numpy issues --- SimPEG/ops.py | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 SimPEG/ops.py diff --git a/SimPEG/ops.py b/SimPEG/ops.py new file mode 100644 index 00000000..4997cde9 --- /dev/null +++ b/SimPEG/ops.py @@ -0,0 +1,62 @@ +import numpy as np +from utils import mkvc +import scipy.sparse.linalg as spla +import scipy.sparse as sp + + +def matmul(A,B): + + # first check shape + if np.shape(A)[1] != np.shape(B)[0]: + print 'error in sizes' + return + + # Check types + sA = sp.issparse(A) + sB = sp.issparse(B) + + if ((sA == False) & (sB == True)): # doesno't work unless we trick it + return (B.T.dot(A.T)).T + else: + return A.dot(B) + + +def dot(A,B): + A = mkvc(A,1) + B = mkvc(B,1) + return np.dot(A,B) + +def inner(A,B): + A = mkvc(A,1) + B = mkvc(B,1) + return np.dot(A,B) + + +if __name__ == '__main__': + import numpy as np + from utils import mkvc + import scipy.sparse as sp + + # generate sparse and dense matrices + A = sp.rand(100, 200, density=0.05, format='csr', dtype=None) + B = sp.rand(200, 150, density=0.05, format='csr', dtype=None) + C = np.random.rand(200,150) + D = np.random.rand(150,100) + b = mkvc(np.arange(200),1) + c = np.reshape(b,(1,200)) + matmul(A,B) + matmul(A,C) + matmul(C,D) + matmul(D,A) + matmul(A,b) + dot(c,b) + dot(C,C) + print np.shape(c), np.shape(b)[0] + print matmul(c,b),dot(c,b) + + + + + + + From caaa7dcb41c19d6be451ced0625fe0f37fcaa9c7 Mon Sep 17 00:00:00 2001 From: ehaber99 Date: Sat, 3 Aug 2013 10:23:35 -0700 Subject: [PATCH 33/51] made the inner products easy to use to get the derivatives with respect to conductivity/mu --- SimPEG/InnerProducts.py | 45 ++++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/SimPEG/InnerProducts.py b/SimPEG/InnerProducts.py index 1ff5cea4..5e53961f 100644 --- a/SimPEG/InnerProducts.py +++ b/SimPEG/InnerProducts.py @@ -81,23 +81,34 @@ def getFaceInnerProduct(mesh, mu): if mu.size == mesh.nC: # Isotropic! mu = mkvc(mu) # ensure it is a vector. - mu = sdiag(np.r_[mu, mu, mu]) + Mu = sdiag(np.r_[mu, mu, mu]) elif mu.shape[1] == 3: # Diagonal tensor - mu = sdiag(np.r_[mu[:, 0], mu[:, 1], mu[:, 2]]) + Mu = sdiag(np.r_[mu[:, 0], mu[:, 1], mu[:, 2]]) elif mu.shape[1] == 6: # Fully anisotropic row1 = sp.hstack((sdiag(mu[:, 0]), sdiag(mu[:, 3]), sdiag(mu[:, 4]))) row2 = sp.hstack((sdiag(mu[:, 3]), sdiag(mu[:, 1]), sdiag(mu[:, 5]))) row3 = sp.hstack((sdiag(mu[:, 4]), sdiag(mu[:, 5]), sdiag(mu[:, 2]))) - mu = sp.vstack((row1, row2, row3)) + Mu = sp.vstack((row1, row2, row3)) # Cell volume v = np.sqrt(mesh.vol) - v3 = np.r_[v, v, v] - V = sdiag(v3)*mu*sdiag(v3) # to keep symmetry + v3 = (0.125)**(0.5)*np.r_[v, v, v] + #V = sdiag(v3)*mu*sdiag(v3) # to keep symmetry + #A = P000.T*V*P000 + P001.T*V*P001 + P010.T*V*P010 + P011.T*V*P011 + P100.T*V*P100 + P101.T*V*P101 + P110.T*V*P110 + P111.T*V*P111 + #A = 0.125*A + P000 = sdiag(v3)*P000; P001 = sdiag(v3)*P001; P010 = sdiag(v3)*P010; P011 = sdiag(v3)*P011 + P100 = sdiag(v3)*P100; P101 = sdiag(v3)*P101; P110 = sdiag(v3)*P110; P111 = sdiag(v3)*P111 + + A = P000.T*Mu*P000 + P001.T*Mu*P001 + P010.T*Mu*P010 + P011.T*Mu*P011 + P100.T*Mu*P100 + P101.T*Mu*P101 + P110.T*Mu*P110 + P111.T*Mu*P111 + + #P = sp.vstack((sdiag(v3)*P000,sdiag(v3)*P001,sdiag(v3)*P010,sdiag(v3)*P011, + # sdiag(v3)*P100,sdiag(v3)*P101,sdiag(v3)*P110,sdiag(v3)*P111)) + + #A = 0.125*(P.T * sp.kron(sp.eye(8),Sigma) * P) + P = [P000,P001,P010,P011,P100,P101,P110,P111] + return A, P - A = P000.T*V*P000 + P001.T*V*P001 + P010.T*V*P010 + P011.T*V*P011 + P100.T*V*P100 + P101.T*V*P101 + P110.T*V*P110 + P111.T*V*P111 - A = 0.125*A return A @@ -168,15 +179,21 @@ def getEdgeInnerProduct(mesh, sigma): # Cell volume v = np.sqrt(mesh.vol) - v3 = np.r_[v, v, v] - V = sdiag(v3)*Sigma*sdiag(v3) # to keep symmetry + v3 = (0.125)**(0.5)*np.r_[v, v, v] + + P000 = sdiag(v3)*P000; P001 = sdiag(v3)*P001; P010 = sdiag(v3)*P010; P011 = sdiag(v3)*P011 + P100 = sdiag(v3)*P100; P101 = sdiag(v3)*P101; P110 = sdiag(v3)*P110; P111 = sdiag(v3)*P111 - A = P000.T*V*P000 + P001.T*V*P001 + P010.T*V*P010 + P011.T*V*P011 + P100.T*V*P100 + P101.T*V*P101 + P110.T*V*P110 + P111.T*V*P111 - A = 0.125*A - P = sp.vstack((sdiag(v3)*P000,sdiag(v3)*P001,sdiag(v3)*P010,sdiag(v3)*P011, - sdiag(v3)*P100,sdiag(v3)*P101,sdiag(v3)*P110,sdiag(v3)*P111)) + #V = sdiag(v3)*Sigma*sdiag(v3) # to keep symmetry + A = P000.T*Sigma*P000 + P001.T*Sigma*P001 + P010.T*Sigma*P010 + P011.T*Sigma*P011 + P100.T*Sigma*P100 + P101.T*Sigma*P101 + P110.T*Sigma*P110 + P111.T*Sigma*P111 + + #P = sp.vstack((sdiag(v3)*P000,sdiag(v3)*P001,sdiag(v3)*P010,sdiag(v3)*P011, + # sdiag(v3)*P100,sdiag(v3)*P101,sdiag(v3)*P110,sdiag(v3)*P111)) + + #A = 0.125*(P.T * sp.kron(sp.eye(8),Sigma) * P) + P = [P000,P001,P010,P011,P100,P101,P110,P111] return A, P @@ -187,5 +204,5 @@ if __name__ == '__main__': mesh = TensorMesh(h) mu = np.ones((mesh.nC, 6)) A = getFaceInnerProduct(mesh,mu) - B = getEdgeInnerProduct(mesh,mu) + B, P = getEdgeInnerProduct(mesh,mu) From 306e31ef047920db0747521a017420dc5461172a Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Sat, 3 Aug 2013 13:53:08 -0700 Subject: [PATCH 34/51] Added warning to the faceInfo utils ---> this does not compute concave areas correctly. This is an issue, and should be fixed in future versions. --- SimPEG/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SimPEG/utils.py b/SimPEG/utils.py index e92f81cb..0741b7bb 100644 --- a/SimPEG/utils.py +++ b/SimPEG/utils.py @@ -281,8 +281,8 @@ def faceInfo(xyz, A, B, C, D, average=True, normalizeNormals=True): # Each triangle is one half of the length of the cross product # # So also could be viewed as the average parallelogram. - print nA, nB, nC, nD - print length(nA), length(nB), length(nC), length(nD) + # + # WARNING: This does not compute correctly for concave quadrilaterals area = (length(nA)+length(nB)+length(nC)+length(nD))/4 return N, area From 9ca9c20731ff4395f9a749c5b5d0905d752004ac Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Sat, 3 Aug 2013 13:57:00 -0700 Subject: [PATCH 35/51] Modified innerProducts so they have defaults, and you have to explicitly ask for the projection matrices. --- SimPEG/InnerProducts.py | 163 ++++++++++++++------------------ SimPEG/tests/test_tensorMesh.py | 1 + 2 files changed, 70 insertions(+), 94 deletions(-) diff --git a/SimPEG/InnerProducts.py b/SimPEG/InnerProducts.py index 5e53961f..b8819e6a 100644 --- a/SimPEG/InnerProducts.py +++ b/SimPEG/InnerProducts.py @@ -11,22 +11,45 @@ class InnerProducts(object): def __init__(self): raise Exception('InnerProducts is a base class providing inner product matrices for meshes and cannot run on its own. Inherit to your favorite Mesh class.') - def getFaceInnerProduct(self, mu): + def getFaceInnerProduct(self, mu=None, returnP=False): if self._meshType == 'TENSOR': pass elif self._meshType == 'LOM': pass # todo: we should be doing something slightly different here! - return getFaceInnerProduct(self, mu) + return getFaceInnerProduct(self, mu, returnP) - def getEdgeInnerProduct(self, sigma): + def getEdgeInnerProduct(self, sigma=None, returnP=False): if self._meshType == 'TENSOR': pass elif self._meshType == 'LOM': pass # todo: we should be doing something slightly different here! - return getEdgeInnerProduct(self, sigma) + return getEdgeInnerProduct(self, sigma, returnP) -def getFaceInnerProduct(mesh, mu): +# ------------------------ Geometries ------------------------------ +# +# +# node(i,j,k+1) ------ edge2(i,j,k+1) ----- node(i,j+1,k+1) +# / / +# / / | +# edge3(i,j,k) face1(i,j,k) edge3(i,j+1,k) +# / / | +# / / | +# node(i,j,k) ------ edge2(i,j,k) ----- node(i,j+1,k) +# | | | +# | | node(i+1,j+1,k+1) +# | | / +# edge1(i,j,k) face3(i,j,k) edge1(i,j+1,k) +# | | / +# | | / +# | |/ +# node(i+1,j,k) ------ edge2(i+1,j,k) ----- node(i+1,j+1,k) + + +def getFaceInnerProduct(mesh, mu=None, returnP=False): + + if mu is None: # default is ones + mu = np.ones((mesh.nC, 1)) m = np.array([mesh.nCx, mesh.nCy, mesh.nCz]) nc = mesh.nC @@ -45,22 +68,6 @@ def getFaceInnerProduct(mesh, mu): return sp.coo_matrix((np.ones(3*nc), (range(3*nc), IND)), shape=(3*nc, np.sum(mesh.nF))).tocsr() - # node(i,j,k+1) ------ edge2(i,j,k+1) ----- node(i,j+1,k+1) - # / / - # / / | - # edge3(i,j,k) face1(i,j,k) edge3(i,j+1,k) - # / / | - # / / | - # node(i,j,k) ------ edge2(i,j,k) ----- node(i,j+1,k) - # | | | - # | | node(i+1,j+1,k+1) - # | | / - # edge1(i,j,k) face3(i,j,k) edge1(i,j+1,k) - # | | / - # | | / - # | |/ - # node(i+1,j,k) ------ edge2(i+1,j,k) ----- node(i+1,j+1,k) - # no | node | f1 | f2 | f3 # 000 | i ,j ,k | i , j, k | i, j , k | i, j, k # 100 | i+1,j ,k | i+1, j, k | i, j , k | i, j, k @@ -70,14 +77,19 @@ def getFaceInnerProduct(mesh, mu): # 101 | i+1,j ,k | i+1, j, k | i, j , k | i, j, k+1 # 011 | i ,j+1,k | i , j, k | i, j+1, k | i, j, k+1 # 111 | i+1,j+1,k | i+1, j, k | i, j+1, k | i, j, k+1 - P000 = Pxxx([[0, 0, 0], [0, 0, 0], [0, 0, 0]]) - P100 = Pxxx([[1, 0, 0], [0, 0, 0], [0, 0, 0]]) - P010 = Pxxx([[0, 0, 0], [0, 1, 0], [0, 0, 0]]) - P110 = Pxxx([[1, 0, 0], [0, 1, 0], [0, 0, 0]]) - P001 = Pxxx([[0, 0, 0], [0, 0, 0], [0, 0, 1]]) - P101 = Pxxx([[1, 0, 0], [0, 0, 0], [0, 0, 1]]) - P011 = Pxxx([[0, 0, 0], [0, 1, 0], [0, 0, 1]]) - P111 = Pxxx([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + + # Square root of cell volume multiplied by 1/8 + v = np.sqrt(0.125*mesh.vol) + V3 = sdiag(np.r_[v, v, v]) # We will multiply on each side to keep symmetry + + P000 = V3*Pxxx([[0, 0, 0], [0, 0, 0], [0, 0, 0]]) + P100 = V3*Pxxx([[1, 0, 0], [0, 0, 0], [0, 0, 0]]) + P010 = V3*Pxxx([[0, 0, 0], [0, 1, 0], [0, 0, 0]]) + P110 = V3*Pxxx([[1, 0, 0], [0, 1, 0], [0, 0, 0]]) + P001 = V3*Pxxx([[0, 0, 0], [0, 0, 0], [0, 0, 1]]) + P101 = V3*Pxxx([[1, 0, 0], [0, 0, 0], [0, 0, 1]]) + P011 = V3*Pxxx([[0, 0, 0], [0, 1, 0], [0, 0, 1]]) + P111 = V3*Pxxx([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) if mu.size == mesh.nC: # Isotropic! mu = mkvc(mu) # ensure it is a vector. @@ -90,30 +102,18 @@ def getFaceInnerProduct(mesh, mu): row3 = sp.hstack((sdiag(mu[:, 4]), sdiag(mu[:, 5]), sdiag(mu[:, 2]))) Mu = sp.vstack((row1, row2, row3)) - # Cell volume - v = np.sqrt(mesh.vol) - v3 = (0.125)**(0.5)*np.r_[v, v, v] - #V = sdiag(v3)*mu*sdiag(v3) # to keep symmetry - #A = P000.T*V*P000 + P001.T*V*P001 + P010.T*V*P010 + P011.T*V*P011 + P100.T*V*P100 + P101.T*V*P101 + P110.T*V*P110 + P111.T*V*P111 - #A = 0.125*A - P000 = sdiag(v3)*P000; P001 = sdiag(v3)*P001; P010 = sdiag(v3)*P010; P011 = sdiag(v3)*P011 - P100 = sdiag(v3)*P100; P101 = sdiag(v3)*P101; P110 = sdiag(v3)*P110; P111 = sdiag(v3)*P111 - A = P000.T*Mu*P000 + P001.T*Mu*P001 + P010.T*Mu*P010 + P011.T*Mu*P011 + P100.T*Mu*P100 + P101.T*Mu*P101 + P110.T*Mu*P110 + P111.T*Mu*P111 - - #P = sp.vstack((sdiag(v3)*P000,sdiag(v3)*P001,sdiag(v3)*P010,sdiag(v3)*P011, - # sdiag(v3)*P100,sdiag(v3)*P101,sdiag(v3)*P110,sdiag(v3)*P111)) - - #A = 0.125*(P.T * sp.kron(sp.eye(8),Sigma) * P) - P = [P000,P001,P010,P011,P100,P101,P110,P111] - return A, P + P = [P000, P001, P010, P011, P100, P101, P110, P111] + if returnP: + return A, P + else: + return A +def getEdgeInnerProduct(mesh, sigma=None, returnP=False): - return A - - -def getEdgeInnerProduct(mesh, sigma): + if sigma is None: # default is ones + sigma = np.ones((mesh.nC, 1)) m = np.array([mesh.nCx, mesh.nCy, mesh.nCz]) nc = mesh.nC @@ -132,22 +132,6 @@ def getEdgeInnerProduct(mesh, sigma): return sp.coo_matrix((np.ones(3*nc), (range(3*nc), IND)), shape=(3*nc, np.sum(mesh.nE))).tocsr() - # node(i,j,k+1) ------ edge2(i,j,k+1) ----- node(i,j+1,k+1) - # / / - # / / | - # edge3(i,j,k) face1(i,j,k) edge3(i,j+1,k) - # / / | - # / / | - # node(i,j,k) ------ edge2(i,j,k) ----- node(i,j+1,k) - # | | | - # | | node(i+1,j+1,k+1) - # | | / - # edge1(i,j,k) face3(i,j,k) edge1(i,j+1,k) - # | | / - # | | / - # | |/ - # node(i+1,j,k) ------ edge2(i+1,j,k) ----- node(i+1,j+1,k) - # no | node | e1 | e2 | e3 # 000 | i ,j ,k | i ,j ,k | i ,j ,k | i ,j ,k # 100 | i+1,j ,k | i ,j ,k | i+1,j ,k | i+1,j ,k @@ -157,14 +141,19 @@ def getEdgeInnerProduct(mesh, sigma): # 101 | i+1,j ,k+1 | i ,j ,k+1 | i+1,j ,k+1 | i+1,j ,k # 011 | i ,j+1,k+1 | i ,j+1,k+1 | i ,j ,k+1 | i ,j+1,k # 111 | i+1,j+1,k+1 | i ,j+1,k+1 | i+1,j ,k+1 | i+1,j+1,k - P000 = Pxxx([[0, 0, 0], [0, 0, 0], [0, 0, 0]]) - P100 = Pxxx([[0, 0, 0], [1, 0, 0], [1, 0, 0]]) - P010 = Pxxx([[0, 1, 0], [0, 0, 0], [0, 1, 0]]) - P110 = Pxxx([[0, 1, 0], [1, 0, 0], [1, 1, 0]]) - P001 = Pxxx([[0, 0, 1], [0, 0, 1], [0, 0, 0]]) - P101 = Pxxx([[0, 0, 1], [1, 0, 1], [1, 0, 0]]) - P011 = Pxxx([[0, 1, 1], [0, 0, 1], [0, 1, 0]]) - P111 = Pxxx([[0, 1, 1], [1, 0, 1], [1, 1, 0]]) + + # Square root of cell volume multiplied by 1/8 + v = np.sqrt(0.125*mesh.vol) + V3 = sdiag(np.r_[v, v, v]) # We will multiply on each side to keep symmetry + + P000 = V3*Pxxx([[0, 0, 0], [0, 0, 0], [0, 0, 0]]) + P100 = V3*Pxxx([[0, 0, 0], [1, 0, 0], [1, 0, 0]]) + P010 = V3*Pxxx([[0, 1, 0], [0, 0, 0], [0, 1, 0]]) + P110 = V3*Pxxx([[0, 1, 0], [1, 0, 0], [1, 1, 0]]) + P001 = V3*Pxxx([[0, 0, 1], [0, 0, 1], [0, 0, 0]]) + P101 = V3*Pxxx([[0, 0, 1], [1, 0, 1], [1, 0, 0]]) + P011 = V3*Pxxx([[0, 1, 1], [0, 0, 1], [0, 1, 0]]) + P111 = V3*Pxxx([[0, 1, 1], [1, 0, 1], [1, 1, 0]]) if sigma.size == mesh.nC: # Isotropic! sigma = mkvc(sigma) # ensure it is a vector. @@ -177,25 +166,12 @@ def getEdgeInnerProduct(mesh, sigma): row3 = sp.hstack((sdiag(sigma[:, 4]), sdiag(sigma[:, 5]), sdiag(sigma[:, 2]))) Sigma = sp.vstack((row1, row2, row3)) - # Cell volume - v = np.sqrt(mesh.vol) - v3 = (0.125)**(0.5)*np.r_[v, v, v] - - P000 = sdiag(v3)*P000; P001 = sdiag(v3)*P001; P010 = sdiag(v3)*P010; P011 = sdiag(v3)*P011 - P100 = sdiag(v3)*P100; P101 = sdiag(v3)*P101; P110 = sdiag(v3)*P110; P111 = sdiag(v3)*P111 - - - #V = sdiag(v3)*Sigma*sdiag(v3) # to keep symmetry - A = P000.T*Sigma*P000 + P001.T*Sigma*P001 + P010.T*Sigma*P010 + P011.T*Sigma*P011 + P100.T*Sigma*P100 + P101.T*Sigma*P101 + P110.T*Sigma*P110 + P111.T*Sigma*P111 - - #P = sp.vstack((sdiag(v3)*P000,sdiag(v3)*P001,sdiag(v3)*P010,sdiag(v3)*P011, - # sdiag(v3)*P100,sdiag(v3)*P101,sdiag(v3)*P110,sdiag(v3)*P111)) - - #A = 0.125*(P.T * sp.kron(sp.eye(8),Sigma) * P) - P = [P000,P001,P010,P011,P100,P101,P110,P111] - return A, P - + P = [P000, P001, P010, P011, P100, P101, P110, P111] + if returnP: + return A, P + else: + return A if __name__ == '__main__': @@ -203,6 +179,5 @@ if __name__ == '__main__': h = [np.array([1, 2, 3, 4]), np.array([1, 2, 1, 4, 2]), np.array([1, 1, 4, 1])] mesh = TensorMesh(h) mu = np.ones((mesh.nC, 6)) - A = getFaceInnerProduct(mesh,mu) - B, P = getEdgeInnerProduct(mesh,mu) - + A, P = mesh.getFaceInnerProduct(mu, returnP=True) + B, P = mesh.getEdgeInnerProduct(mu, returnP=True) diff --git a/SimPEG/tests/test_tensorMesh.py b/SimPEG/tests/test_tensorMesh.py index c0b194db..bc034e0b 100644 --- a/SimPEG/tests/test_tensorMesh.py +++ b/SimPEG/tests/test_tensorMesh.py @@ -57,6 +57,7 @@ class BasicTensorMeshTests(unittest.TestCase): t1 = np.all(self.mesh2.edge == test_edge) self.assertTrue(t1) + class TestCurl(OrderTest): name = "Curl" From 6d5190b2f2a39debadf57b9ce99119e262566970 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Sat, 3 Aug 2013 16:07:27 -0700 Subject: [PATCH 36/51] GridFunctions! --- SimPEG/LogicallyOrthogonalMesh.py | 97 ++++++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/SimPEG/LogicallyOrthogonalMesh.py b/SimPEG/LogicallyOrthogonalMesh.py index dd424e6e..1018305b 100644 --- a/SimPEG/LogicallyOrthogonalMesh.py +++ b/SimPEG/LogicallyOrthogonalMesh.py @@ -1,6 +1,7 @@ import numpy as np from BaseMesh import BaseMesh from DiffOperators import DiffOperators +from InnerProducts import InnerProducts from utils import mkvc, ndgrid, volTetra, indexCube, faceInfo # Some helper functions. @@ -10,7 +11,7 @@ normalize2D = lambda x: x/np.kron(np.ones((1, 2)), mkvc(length2D(x), 2)) normalize3D = lambda x: x/np.kron(np.ones((1, 3)), mkvc(length3D(x), 2)) -class LogicallyOrthogonalMesh(BaseMesh, DiffOperators): # , LOMGrid +class LogicallyOrthogonalMesh(BaseMesh, DiffOperators, InnerProducts): """ LogicallyOrthogonalMesh is a mesh class that deals with logically orthogonal meshes. @@ -57,6 +58,100 @@ class LogicallyOrthogonalMesh(BaseMesh, DiffOperators): # , LOMGrid _gridN = None # Store grid by default gridN = property(**gridN()) + def gridFx(): + doc = "Face staggered grid in the x direction." + + def fget(self): + if self._gridFx is None: + N = self.r(self.gridN, 'N', 'N', 'M') + if self.dim == 2: + XY = [mkvc(0.5 * (n[:, :-1] + n[:, 1:])) for n in N] + self._gridFx = np.c_[XY[0], XY[1]] + elif self.dim == 3: + XYZ = [mkvc(0.25 * (n[:, :-1, :-1] + n[:, :-1, 1:] + n[:, 1:, :-1] + n[:, 1:, 1:])) for n in N] + self._gridFx = np.c_[XYZ[0], XYZ[1], XYZ[2]] + return self._gridFx + return locals() + _gridFx = None # Store grid by default + gridFx = property(**gridFx()) + + def gridFy(): + doc = "Face staggered grid in the y direction." + + def fget(self): + if self._gridFy is None: + N = self.r(self.gridN, 'N', 'N', 'M') + if self.dim == 2: + XY = [mkvc(0.5 * (n[:-1, :] + n[1:, :])) for n in N] + self._gridFy = np.c_[XY[0], XY[1]] + elif self.dim == 3: + XYZ = [mkvc(0.25 * (n[:-1, :, :-1] + n[:-1, :, 1:] + n[1:, :, :-1] + n[1:, :, 1:])) for n in N] + self._gridFy = np.c_[XYZ[0], XYZ[1], XYZ[2]] + return self._gridFy + return locals() + _gridFy = None # Store grid by default + gridFy = property(**gridFy()) + + def gridFz(): + doc = "Face staggered grid in the z direction." + + def fget(self): + if self._gridFz is None and self.dim == 3: + N = self.r(self.gridN, 'N', 'N', 'M') + XYZ = [mkvc(0.25 * (n[:-1, :-1, :] + n[:-1, 1:, :] + n[1:, :-1, :] + n[1:, 1:, :])) for n in N] + self._gridFz = np.c_[XYZ[0], XYZ[1], XYZ[2]] + return self._gridFz + return locals() + _gridFz = None # Store grid by default + gridFz = property(**gridFz()) + + def gridEx(): + doc = "Edge staggered grid in the x direction." + + def fget(self): + if self._gridEx is None: + N = self.r(self.gridN, 'N', 'N', 'M') + if self.dim == 2: + XY = [mkvc(0.5 * (n[:-1, :] + n[1:, :])) for n in N] + self._gridEx = np.c_[XY[0], XY[1]] + elif self.dim == 3: + XYZ = [mkvc(0.5 * (n[:-1, :, :] + n[1:, :, :])) for n in N] + self._gridEx = np.c_[XYZ[0], XYZ[1], XYZ[2]] + return self._gridEx + return locals() + _gridEx = None # Store grid by default + gridEx = property(**gridEx()) + + def gridEy(): + doc = "Edge staggered grid in the y direction." + + def fget(self): + if self._gridEy is None: + N = self.r(self.gridN, 'N', 'N', 'M') + if self.dim == 2: + XY = [mkvc(0.5 * (n[:, :-1] + n[:, 1:])) for n in N] + self._gridEy = np.c_[XY[0], XY[1]] + elif self.dim == 3: + XYZ = [mkvc(0.5 * (n[:, :-1, :] + n[:, 1:, :])) for n in N] + self._gridEy = np.c_[XYZ[0], XYZ[1], XYZ[2]] + return self._gridEy + return locals() + _gridEy = None # Store grid by default + gridEy = property(**gridEy()) + + def gridEz(): + doc = "Edge staggered grid in the z direction." + + def fget(self): + if self._gridEz is None and self.dim == 3: + N = self.r(self.gridN, 'N', 'N', 'M') + XYZ = [mkvc(0.5 * (n[:, :, :-1] + n[:, :, 1:])) for n in N] + self._gridEz = np.c_[XYZ[0], XYZ[1], XYZ[2]] + return self._gridEz + return locals() + _gridEz = None # Store grid by default + gridEz = property(**gridEz()) + # --------------- Geometries --------------------- # # From 1acb1a4b848dbcde990fee6e7a2ebe48d9844071 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Sat, 3 Aug 2013 16:08:04 -0700 Subject: [PATCH 37/51] Ensure you return None if dimension of TensorMesh is less than the requested grid. --- SimPEG/TensorMesh.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SimPEG/TensorMesh.py b/SimPEG/TensorMesh.py index acfdfbac..5218aec1 100644 --- a/SimPEG/TensorMesh.py +++ b/SimPEG/TensorMesh.py @@ -133,7 +133,7 @@ class TensorMesh(BaseMesh, TensorView, DiffOperators, InnerProducts): doc = "Face staggered grid in the y direction." def fget(self): - if self._gridFy is None: + if self._gridFy is None and self.dim > 1: self._gridFy = ndgrid([x for x in [self.vectorCCx, self.vectorNy, self.vectorCCz] if not x is None]) return self._gridFy return locals() @@ -144,7 +144,7 @@ class TensorMesh(BaseMesh, TensorView, DiffOperators, InnerProducts): doc = "Face staggered grid in the z direction." def fget(self): - if self._gridFz is None: + if self._gridFz is None and self.dim > 2: self._gridFz = ndgrid([x for x in [self.vectorCCx, self.vectorCCy, self.vectorNz] if not x is None]) return self._gridFz return locals() @@ -166,7 +166,7 @@ class TensorMesh(BaseMesh, TensorView, DiffOperators, InnerProducts): doc = "Edge staggered grid in the y direction." def fget(self): - if self._gridEy is None: + if self._gridEy is None and self.dim > 1: self._gridEy = ndgrid([x for x in [self.vectorNx, self.vectorCCy, self.vectorNz] if not x is None]) return self._gridEy return locals() @@ -177,7 +177,7 @@ class TensorMesh(BaseMesh, TensorView, DiffOperators, InnerProducts): doc = "Edge staggered grid in the z direction." def fget(self): - if self._gridEz is None: + if self._gridEz is None and self.dim > 2: self._gridEz = ndgrid([x for x in [self.vectorNx, self.vectorNy, self.vectorCCz] if not x is None]) return self._gridEz return locals() From cc66eaf9fdcfaa431b8b2a11a07e48925478967a Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Sat, 3 Aug 2013 16:08:54 -0700 Subject: [PATCH 38/51] Testing of LOM (comparing against Tensor Mesh) --- SimPEG/__init__.py | 1 + SimPEG/tests/OrderTest.py | 11 +++- SimPEG/tests/test_LogicallyOrthogonalMesh.py | 55 +++++++++++++++++++- 3 files changed, 64 insertions(+), 3 deletions(-) diff --git a/SimPEG/__init__.py b/SimPEG/__init__.py index bcbba681..ca9b9f7b 100644 --- a/SimPEG/__init__.py +++ b/SimPEG/__init__.py @@ -1,2 +1,3 @@ from TensorMesh import TensorMesh +from LogicallyOrthogonalMesh import LogicallyOrthogonalMesh import utils diff --git a/SimPEG/tests/OrderTest.py b/SimPEG/tests/OrderTest.py index 55ac7d34..1b7ba7de 100644 --- a/SimPEG/tests/OrderTest.py +++ b/SimPEG/tests/OrderTest.py @@ -1,6 +1,6 @@ import sys sys.path.append('../../') -from SimPEG import TensorMesh +from SimPEG import TensorMesh, utils, LogicallyOrthogonalMesh import numpy as np import unittest @@ -90,6 +90,15 @@ class OrderTest(unittest.TestCase): max_h = max([np.max(hi) for hi in self.M.h]) return max_h + elif 'LOM' in self.meshType: + if 'uniform' in self.meshType: + xx = np.ones(nc)/nc + X, Y, Z = utils.ndgrid(xx, xx, xx, vector=False) + else: + raise Exception('Unexpected meshType') + self.M = LogicallyOrthogonalMesh([X, Y, Z]) + return 1./nc + def getError(self): """For given h, generate A[h], f and A(f) and return norm of error.""" return 1. diff --git a/SimPEG/tests/test_LogicallyOrthogonalMesh.py b/SimPEG/tests/test_LogicallyOrthogonalMesh.py index 6c6eade9..523b18d2 100644 --- a/SimPEG/tests/test_LogicallyOrthogonalMesh.py +++ b/SimPEG/tests/test_LogicallyOrthogonalMesh.py @@ -4,8 +4,6 @@ import sys sys.path.append('../') from TensorMesh import TensorMesh from LogicallyOrthogonalMesh import LogicallyOrthogonalMesh -from OrderTest import OrderTest -from scipy.sparse.linalg import dsolve from utils import ndgrid @@ -47,6 +45,59 @@ class BasicLOMTests(unittest.TestCase): t1 = np.all(self.LOM2.edge == test_edge) self.assertTrue(t1) + def test_tangents(self): + T = self.LOM2.tangents + self.assertTrue(np.all(self.LOM2.r(T, 'E', 'Ex', 'V')[0] == np.ones(self.LOM2.nE[0]))) + self.assertTrue(np.all(self.LOM2.r(T, 'E', 'Ex', 'V')[1] == np.zeros(self.LOM2.nE[0]))) + self.assertTrue(np.all(self.LOM2.r(T, 'E', 'Ey', 'V')[0] == np.zeros(self.LOM2.nE[1]))) + self.assertTrue(np.all(self.LOM2.r(T, 'E', 'Ey', 'V')[1] == np.ones(self.LOM2.nE[1]))) + + T = self.LOM3.tangents + self.assertTrue(np.all(self.LOM3.r(T, 'E', 'Ex', 'V')[0] == np.ones(self.LOM3.nE[0]))) + self.assertTrue(np.all(self.LOM3.r(T, 'E', 'Ex', 'V')[1] == np.zeros(self.LOM3.nE[0]))) + self.assertTrue(np.all(self.LOM3.r(T, 'E', 'Ex', 'V')[2] == np.zeros(self.LOM3.nE[0]))) + + self.assertTrue(np.all(self.LOM3.r(T, 'E', 'Ey', 'V')[0] == np.zeros(self.LOM3.nE[1]))) + self.assertTrue(np.all(self.LOM3.r(T, 'E', 'Ey', 'V')[1] == np.ones(self.LOM3.nE[1]))) + self.assertTrue(np.all(self.LOM3.r(T, 'E', 'Ey', 'V')[2] == np.zeros(self.LOM3.nE[1]))) + + self.assertTrue(np.all(self.LOM3.r(T, 'E', 'Ez', 'V')[0] == np.zeros(self.LOM3.nE[2]))) + self.assertTrue(np.all(self.LOM3.r(T, 'E', 'Ez', 'V')[1] == np.zeros(self.LOM3.nE[2]))) + self.assertTrue(np.all(self.LOM3.r(T, 'E', 'Ez', 'V')[2] == np.ones(self.LOM3.nE[2]))) + + def test_normals(self): + N = self.LOM2.normals + self.assertTrue(np.all(self.LOM2.r(N, 'F', 'Fx', 'V')[0] == np.ones(self.LOM2.nF[0]))) + self.assertTrue(np.all(self.LOM2.r(N, 'F', 'Fx', 'V')[1] == np.zeros(self.LOM2.nF[0]))) + self.assertTrue(np.all(self.LOM2.r(N, 'F', 'Fy', 'V')[0] == np.zeros(self.LOM2.nF[1]))) + self.assertTrue(np.all(self.LOM2.r(N, 'F', 'Fy', 'V')[1] == np.ones(self.LOM2.nF[1]))) + + N = self.LOM3.normals + self.assertTrue(np.all(self.LOM3.r(N, 'F', 'Fx', 'V')[0] == np.ones(self.LOM3.nF[0]))) + self.assertTrue(np.all(self.LOM3.r(N, 'F', 'Fx', 'V')[1] == np.zeros(self.LOM3.nF[0]))) + self.assertTrue(np.all(self.LOM3.r(N, 'F', 'Fx', 'V')[2] == np.zeros(self.LOM3.nF[0]))) + + self.assertTrue(np.all(self.LOM3.r(N, 'F', 'Fy', 'V')[0] == np.zeros(self.LOM3.nF[1]))) + self.assertTrue(np.all(self.LOM3.r(N, 'F', 'Fy', 'V')[1] == np.ones(self.LOM3.nF[1]))) + self.assertTrue(np.all(self.LOM3.r(N, 'F', 'Fy', 'V')[2] == np.zeros(self.LOM3.nF[1]))) + + self.assertTrue(np.all(self.LOM3.r(N, 'F', 'Fz', 'V')[0] == np.zeros(self.LOM3.nF[2]))) + self.assertTrue(np.all(self.LOM3.r(N, 'F', 'Fz', 'V')[1] == np.zeros(self.LOM3.nF[2]))) + self.assertTrue(np.all(self.LOM3.r(N, 'F', 'Fz', 'V')[2] == np.ones(self.LOM3.nF[2]))) + + def test_grid(self): + self.assertTrue(np.all(self.LOM2.gridFx == self.TM2.gridFx)) + self.assertTrue(np.all(self.LOM2.gridFy == self.TM2.gridFy)) + self.assertTrue(np.all(self.LOM2.gridEx == self.TM2.gridEx)) + self.assertTrue(np.all(self.LOM2.gridEy == self.TM2.gridEy)) + + self.assertTrue(np.all(self.LOM3.gridFx == self.TM3.gridFx)) + self.assertTrue(np.all(self.LOM3.gridFy == self.TM3.gridFy)) + self.assertTrue(np.all(self.LOM3.gridFz == self.TM3.gridFz)) + self.assertTrue(np.all(self.LOM3.gridEx == self.TM3.gridEx)) + self.assertTrue(np.all(self.LOM3.gridEy == self.TM3.gridEy)) + self.assertTrue(np.all(self.LOM3.gridEz == self.TM3.gridEz)) + if __name__ == '__main__': unittest.main() From 26b334585fb30fbfd6b7141968ad0ea5340828f7 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Sat, 3 Aug 2013 16:39:34 -0700 Subject: [PATCH 39/51] EdgeInnerProducts now working for LOM Fixed DiffOps bug in CURL Fixed bug in OrderTest --> must remember to do cumSum, and not just supply dx to ndgrid test_massMatrices now tests uniformLOM --- SimPEG/DiffOperators.py | 6 +++--- SimPEG/InnerProducts.py | 19 ++++++++++++++--- SimPEG/sputils.py | 34 +++++++++++++++++++++++++++++++ SimPEG/tests/OrderTest.py | 4 ++-- SimPEG/tests/test_massMatrices.py | 2 ++ 5 files changed, 57 insertions(+), 8 deletions(-) diff --git a/SimPEG/DiffOperators.py b/SimPEG/DiffOperators.py index df321e5f..d02b26bf 100644 --- a/SimPEG/DiffOperators.py +++ b/SimPEG/DiffOperators.py @@ -168,9 +168,9 @@ class DiffOperators(object): def fget(self): if(self._edgeCurl is None): # The number of cell centers in each direction - n1 = np.size(self.hx) - n2 = np.size(self.hy) - n3 = np.size(self.hz) + n1 = self.nCx + n2 = self.nCy + n3 = self.nCy # Compute lengths of cell edges L = self.edge diff --git a/SimPEG/InnerProducts.py b/SimPEG/InnerProducts.py index b8819e6a..d1d0d623 100644 --- a/SimPEG/InnerProducts.py +++ b/SimPEG/InnerProducts.py @@ -1,6 +1,6 @@ from scipy import sparse as sp -from sputils import sdiag -from utils import sub2ind, ndgrid, mkvc +from sputils import sdiag, inv3X3BlockDiagonal +from utils import sub2ind, ndgrid, mkvc, getSubArray import numpy as np @@ -123,6 +123,11 @@ def getEdgeInnerProduct(mesh, sigma=None, returnP=False): iijjkk = ndgrid(i, j, k) ii, jj, kk = iijjkk[:, 0], iijjkk[:, 1], iijjkk[:, 2] + if mesh._meshType == 'LOM': + eT1 = mesh.r(mesh.tangents, 'E', 'Ex', 'M') + eT2 = mesh.r(mesh.tangents, 'E', 'Ey', 'M') + eT3 = mesh.r(mesh.tangents, 'E', 'Ez', 'M') + def Pxxx(pos): ind1 = sub2ind(mesh.nEx, np.c_[ii + pos[0][0], jj + pos[0][1], kk + pos[0][2]]) ind2 = sub2ind(mesh.nEy, np.c_[ii + pos[1][0], jj + pos[1][1], kk + pos[1][2]]) + mesh.nE[0] @@ -130,7 +135,15 @@ def getEdgeInnerProduct(mesh, sigma=None, returnP=False): IND = np.r_[ind1, ind2, ind3].flatten() - return sp.coo_matrix((np.ones(3*nc), (range(3*nc), IND)), shape=(3*nc, np.sum(mesh.nE))).tocsr() + PXXX = sp.coo_matrix((np.ones(3*nc), (range(3*nc), IND)), shape=(3*nc, np.sum(mesh.nE))).tocsr() + + if mesh._meshType == 'LOM': + I3x3 = inv3X3BlockDiagonal(getSubArray(eT1[0], [i + pos[0][0], j + pos[0][1], k + pos[0][2]]), getSubArray(eT1[1], [i + pos[0][0], j + pos[0][1], k + pos[0][2]]), getSubArray(eT1[2], [i + pos[0][0], j + pos[0][1], k + pos[0][2]]), + getSubArray(eT2[0], [i + pos[1][0], j + pos[1][1], k + pos[1][2]]), getSubArray(eT2[1], [i + pos[1][0], j + pos[1][1], k + pos[1][2]]), getSubArray(eT2[2], [i + pos[1][0], j + pos[1][1], k + pos[1][2]]), + getSubArray(eT3[0], [i + pos[2][0], j + pos[2][1], k + pos[2][2]]), getSubArray(eT3[1], [i + pos[2][0], j + pos[2][1], k + pos[2][2]]), getSubArray(eT3[2], [i + pos[2][0], j + pos[2][1], k + pos[2][2]])) + PXXX = I3x3 * PXXX + + return PXXX # no | node | e1 | e2 | e3 # 000 | i ,j ,k | i ,j ,k | i ,j ,k | i ,j ,k diff --git a/SimPEG/sputils.py b/SimPEG/sputils.py index d9aab184..ae41a210 100644 --- a/SimPEG/sputils.py +++ b/SimPEG/sputils.py @@ -1,4 +1,5 @@ from scipy import sparse as sp +from utils import mkvc def sdiag(h): @@ -19,3 +20,36 @@ def kron3(A, B, C): def spzeros(n1, n2): """spzeros""" return sp.coo_matrix((n1, n2)).tocsr() + + +def inv3X3BlockDiagonal(a11, a12, a13, a21, a22, a23, a31, a32, a33): + + a11 = mkvc(a11) + a12 = mkvc(a12) + a13 = mkvc(a13) + a21 = mkvc(a21) + a22 = mkvc(a22) + a23 = mkvc(a23) + a31 = mkvc(a31) + a32 = mkvc(a32) + a33 = mkvc(a33) + + detA = a31*a12*a23 - a31*a13*a22 - a21*a12*a33 + a21*a13*a32 + a11*a22*a33 - a11*a23*a32 + + b11 = +(a22*a33 - a23*a32)/detA + b12 = -(a12*a33 - a13*a32)/detA + b13 = +(a12*a23 - a13*a22)/detA + + b21 = +(a31*a23 - a21*a33)/detA + b22 = -(a31*a13 - a11*a33)/detA + b23 = +(a21*a13 - a11*a23)/detA + + b31 = -(a31*a22 - a21*a32)/detA + b32 = +(a31*a12 - a11*a32)/detA + b33 = -(a21*a12 - a11*a22)/detA + + B = sp.vstack((sp.hstack((sdiag(b11), sdiag(b12), sdiag(b13))), + sp.hstack((sdiag(b21), sdiag(b22), sdiag(b23))), + sp.hstack((sdiag(b31), sdiag(b32), sdiag(b33))))) + + return B diff --git a/SimPEG/tests/OrderTest.py b/SimPEG/tests/OrderTest.py index 1b7ba7de..5bddf118 100644 --- a/SimPEG/tests/OrderTest.py +++ b/SimPEG/tests/OrderTest.py @@ -92,8 +92,8 @@ class OrderTest(unittest.TestCase): elif 'LOM' in self.meshType: if 'uniform' in self.meshType: - xx = np.ones(nc)/nc - X, Y, Z = utils.ndgrid(xx, xx, xx, vector=False) + xx = np.cumsum(np.r_[0, np.ones(nc)/nc]) + X, Y, Z = utils.ndgrid([xx, xx, xx], vector=False) else: raise Exception('Unexpected meshType') self.M = LogicallyOrthogonalMesh([X, Y, Z]) diff --git a/SimPEG/tests/test_massMatrices.py b/SimPEG/tests/test_massMatrices.py index c304dda5..da9f0ca6 100644 --- a/SimPEG/tests/test_massMatrices.py +++ b/SimPEG/tests/test_massMatrices.py @@ -32,6 +32,8 @@ from OrderTest import OrderTest class TestInnerProducts(OrderTest): """Integrate an function over a unit cube domain using edgeInnerProducts and faceInnerProducts.""" + meshType = 'uniformLOM' + def getError(self): call = lambda fun, xyz: fun(xyz[:, 0], xyz[:, 1], xyz[:, 2]) From 849a91473815c141080f3572dfb7d1457cea5e2a Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Sun, 4 Aug 2013 07:54:33 -0700 Subject: [PATCH 40/51] LOM grid ding and visualization code. --- SimPEG/LomView.py | 61 +++++++++++++++++++++++++++++++++++++++++++ SimPEG/exampleGrid.py | 25 ++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 SimPEG/LomView.py create mode 100644 SimPEG/exampleGrid.py diff --git a/SimPEG/LomView.py b/SimPEG/LomView.py new file mode 100644 index 00000000..1596dfd9 --- /dev/null +++ b/SimPEG/LomView.py @@ -0,0 +1,61 @@ +import numpy as np +import matplotlib.pyplot as plt +import matplotlib +from mpl_toolkits.mplot3d import Axes3D +from utils import mkvc + + +class LomView(object): + """ + Provides viewing functions for TensorMesh + + This class is inherited by TensorMesh + """ + def __init__(self): + pass + + def plotGrid(self): + """Plot the nodal, cell-centered and staggered grids for 1,2 and 3 dimensions.""" + NN = self.r(self.gridN, 'N', 'N', 'M') + if self.dim == 2: + fig = plt.figure(2) + fig.clf() + ax = plt.subplot(111) + X1 = np.c_[mkvc(NN[0][:-1, :]), mkvc(NN[0][1:, :]), mkvc(NN[0][:-1, :])*np.nan].flatten() + Y1 = np.c_[mkvc(NN[1][:-1, :]), mkvc(NN[1][1:, :]), mkvc(NN[1][:-1, :])*np.nan].flatten() + + X2 = np.c_[mkvc(NN[0][:, :-1]), mkvc(NN[0][:, 1:]), mkvc(NN[0][:, :-1])*np.nan].flatten() + Y2 = np.c_[mkvc(NN[1][:, :-1]), mkvc(NN[1][:, 1:]), mkvc(NN[1][:, :-1])*np.nan].flatten() + + X = np.r_[X1, X2] + Y = np.r_[Y1, Y2] + + plt.plot(X, Y) + elif self.dim == 3: + fig = plt.figure(3) + fig.clf() + ax = fig.add_subplot(111, projection='3d') + X1 = np.c_[mkvc(NN[0][:-1, :, :]), mkvc(NN[0][1:, :, :]), mkvc(NN[0][:-1, :, :])*np.nan].flatten() + Y1 = np.c_[mkvc(NN[1][:-1, :, :]), mkvc(NN[1][1:, :, :]), mkvc(NN[1][:-1, :, :])*np.nan].flatten() + Z1 = np.c_[mkvc(NN[2][:-1, :, :]), mkvc(NN[2][1:, :, :]), mkvc(NN[2][:-1, :, :])*np.nan].flatten() + + X2 = np.c_[mkvc(NN[0][:, :-1, :]), mkvc(NN[0][:, 1:, :]), mkvc(NN[0][:, :-1, :])*np.nan].flatten() + Y2 = np.c_[mkvc(NN[1][:, :-1, :]), mkvc(NN[1][:, 1:, :]), mkvc(NN[1][:, :-1, :])*np.nan].flatten() + Z2 = np.c_[mkvc(NN[2][:, :-1, :]), mkvc(NN[2][:, 1:, :]), mkvc(NN[2][:, :-1, :])*np.nan].flatten() + + X3 = np.c_[mkvc(NN[0][:, :, :-1]), mkvc(NN[0][:, :, 1:]), mkvc(NN[0][:, :, :-1])*np.nan].flatten() + Y3 = np.c_[mkvc(NN[1][:, :, :-1]), mkvc(NN[1][:, :, 1:]), mkvc(NN[1][:, :, :-1])*np.nan].flatten() + Z3 = np.c_[mkvc(NN[2][:, :, :-1]), mkvc(NN[2][:, :, 1:]), mkvc(NN[2][:, :, :-1])*np.nan].flatten() + + X = np.r_[X1, X2, X3] + Y = np.r_[Y1, Y2, Y3] + Z = np.r_[Z1, Z2, Z3] + + plt.plot(X, Y, 'b', zs=Z) + ax.set_zlabel('x3') + + ax.grid(True) + ax.hold(False) + ax.set_xlabel('x1') + ax.set_ylabel('x2') + fig.show() diff --git a/SimPEG/exampleGrid.py b/SimPEG/exampleGrid.py new file mode 100644 index 00000000..a49326df --- /dev/null +++ b/SimPEG/exampleGrid.py @@ -0,0 +1,25 @@ +import numpy as np +from utils import mkvc, ndgrid + + +def exampleLomGird(nC, exType): + assert type(nC) == list, "nC must be a list containing the number of nodes" + assert len(nC) == 2 or len(nC) == 3, "nC must either two or three dimensions" + exType = exType.lower() + + possibleTypes = ['rect', 'rotate'] + assert exType in possibleTypes, "Not a possible example type." + + if exType == 'rect': + return ndgrid([np.cumsum(np.r_[0, np.ones(nx)/nx]) for nx in nC], vector=False) + elif exType == 'rotate': + if len(nC) == 2: + X, Y = ndgrid([np.cumsum(np.r_[0, np.ones(nx)/nx]) for nx in nC], vector=False) + amt = 0.5-np.sqrt((X - 0.5)**2 + (Y - 0.5)**2) + amt[amt < 0] = 0 + return X + (-(Y - 0.5))*amt, Y + (+(X - 0.5))*amt + elif len(nC) == 3: + X, Y, Z = ndgrid([np.cumsum(np.r_[0, np.ones(nx)/nx]) for nx in nC], vector=False) + amt = 0.5-np.sqrt((X - 0.5)**2 + (Y - 0.5)**2 + (Z - 0.5)**2) + amt[amt < 0] = 0 + return X + (-(Y - 0.5))*amt, Y + (-(Z - 0.5))*amt, Z + (-(X - 0.5))*amt From e2e38074fc5533bb4f87a40bdf0aa9ff5631a28b Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Sun, 4 Aug 2013 07:56:25 -0700 Subject: [PATCH 41/51] Order Test must incorporate different meshes. Current test fails, must support 2D mass matrices. --- SimPEG/LogicallyOrthogonalMesh.py | 3 ++- SimPEG/__init__.py | 1 + SimPEG/tests/OrderTest.py | 16 +++++++++++----- SimPEG/tests/test_massMatrices.py | 4 +++- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/SimPEG/LogicallyOrthogonalMesh.py b/SimPEG/LogicallyOrthogonalMesh.py index 1018305b..1c29c947 100644 --- a/SimPEG/LogicallyOrthogonalMesh.py +++ b/SimPEG/LogicallyOrthogonalMesh.py @@ -2,6 +2,7 @@ import numpy as np from BaseMesh import BaseMesh from DiffOperators import DiffOperators from InnerProducts import InnerProducts +from LomView import LomView from utils import mkvc, ndgrid, volTetra, indexCube, faceInfo # Some helper functions. @@ -11,7 +12,7 @@ normalize2D = lambda x: x/np.kron(np.ones((1, 2)), mkvc(length2D(x), 2)) normalize3D = lambda x: x/np.kron(np.ones((1, 3)), mkvc(length3D(x), 2)) -class LogicallyOrthogonalMesh(BaseMesh, DiffOperators, InnerProducts): +class LogicallyOrthogonalMesh(BaseMesh, DiffOperators, InnerProducts, LomView): """ LogicallyOrthogonalMesh is a mesh class that deals with logically orthogonal meshes. diff --git a/SimPEG/__init__.py b/SimPEG/__init__.py index ca9b9f7b..7cb2b76d 100644 --- a/SimPEG/__init__.py +++ b/SimPEG/__init__.py @@ -1,3 +1,4 @@ from TensorMesh import TensorMesh from LogicallyOrthogonalMesh import LogicallyOrthogonalMesh import utils +from exampleGrid import exampleLomGird diff --git a/SimPEG/tests/OrderTest.py b/SimPEG/tests/OrderTest.py index 5bddf118..64d37a32 100644 --- a/SimPEG/tests/OrderTest.py +++ b/SimPEG/tests/OrderTest.py @@ -1,6 +1,6 @@ import sys sys.path.append('../../') -from SimPEG import TensorMesh, utils, LogicallyOrthogonalMesh +from SimPEG import TensorMesh, utils, LogicallyOrthogonalMesh, exampleLomGird import numpy as np import unittest @@ -92,11 +92,17 @@ class OrderTest(unittest.TestCase): elif 'LOM' in self.meshType: if 'uniform' in self.meshType: - xx = np.cumsum(np.r_[0, np.ones(nc)/nc]) - X, Y, Z = utils.ndgrid([xx, xx, xx], vector=False) + kwrd = 'rect' + elif 'rotate' in self.meshType: + kwrd = 'rotate' else: raise Exception('Unexpected meshType') - self.M = LogicallyOrthogonalMesh([X, Y, Z]) + if self.meshDimension == 2: + X, Y = exampleLomGird([nc, nc], kwrd) + self.M = LogicallyOrthogonalMesh([X, Y]) + if self.meshDimension == 3: + X, Y, Z = exampleLomGird([nc, nc, nc], kwrd) + self.M = LogicallyOrthogonalMesh([X, Y, Z]) return 1./nc def getError(self): @@ -119,7 +125,7 @@ class OrderTest(unittest.TestCase): err = self.getError() if ii == 0: print '' - print 'Testing order of: ' + self.name + print 'Testing convergence on ' + self.M._meshType + ' of: ' + self.name print '_____________________________________________' print ' h | error | e(i-1)/e(i) | order' print '~~~~~~|~~~~~~~~~~~~~|~~~~~~~~~~~~~|~~~~~~~~~~' diff --git a/SimPEG/tests/test_massMatrices.py b/SimPEG/tests/test_massMatrices.py index da9f0ca6..021a307b 100644 --- a/SimPEG/tests/test_massMatrices.py +++ b/SimPEG/tests/test_massMatrices.py @@ -32,7 +32,9 @@ from OrderTest import OrderTest class TestInnerProducts(OrderTest): """Integrate an function over a unit cube domain using edgeInnerProducts and faceInnerProducts.""" - meshType = 'uniformLOM' + meshType = 'rotateLOM' + meshDimension = 2 + meshSizes = [16, 32, 64] def getError(self): From e073eaeb8b35f5fdb205b13edc92aae191dc8d8f Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Mon, 5 Aug 2013 11:51:03 -0700 Subject: [PATCH 42/51] Innerproduct work. Testing, visualization, and 2D - NOTE there are bugs in anything but a uniform LOM Not very helpful yet! --- SimPEG/InnerProducts.py | 94 ++++++++++++++++++- SimPEG/LomView.py | 32 ++++++- SimPEG/sputils.py | 45 +++++++++- SimPEG/tests/OrderTest.py | 61 +++++++------ SimPEG/tests/test_massMatrices.py | 90 ++++++++++++++++++- SimPEG/tests/test_operators.py | 144 ++++++++++++++++++++++++++++++ SimPEG/tests/test_tensorMesh.py | 80 +---------------- SimPEG/tests/test_utils.py | 25 ++++++ SimPEG/utils.py | 10 ++- 9 files changed, 465 insertions(+), 116 deletions(-) create mode 100644 SimPEG/tests/test_operators.py diff --git a/SimPEG/InnerProducts.py b/SimPEG/InnerProducts.py index d1d0d623..5cc391c4 100644 --- a/SimPEG/InnerProducts.py +++ b/SimPEG/InnerProducts.py @@ -1,5 +1,5 @@ from scipy import sparse as sp -from sputils import sdiag, inv3X3BlockDiagonal +from sputils import sdiag, inv3X3BlockDiagonal, inv2X2BlockDiagonal from utils import sub2ind, ndgrid, mkvc, getSubArray import numpy as np @@ -16,7 +16,10 @@ class InnerProducts(object): pass elif self._meshType == 'LOM': pass # todo: we should be doing something slightly different here! - return getFaceInnerProduct(self, mu, returnP) + if self.dim == 2: + return getFaceInnerProduct2D(self, mu, returnP) + elif self.dim == 3: + return getFaceInnerProduct(self, mu, returnP) def getEdgeInnerProduct(self, sigma=None, returnP=False): if self._meshType == 'TENSOR': @@ -59,6 +62,11 @@ def getFaceInnerProduct(mesh, mu=None, returnP=False): iijjkk = ndgrid(i, j, k) ii, jj, kk = iijjkk[:, 0], iijjkk[:, 1], iijjkk[:, 2] + if mesh._meshType == 'LOM': + fN1 = mesh.r(mesh.normals, 'F', 'Fx', 'M') + fN2 = mesh.r(mesh.normals, 'F', 'Fy', 'M') + fN3 = mesh.r(mesh.normals, 'F', 'Fz', 'M') + def Pxxx(pos): ind1 = sub2ind(mesh.nFx, np.c_[ii + pos[0][0], jj + pos[0][1], kk + pos[0][2]]) ind2 = sub2ind(mesh.nFy, np.c_[ii + pos[1][0], jj + pos[1][1], kk + pos[1][2]]) + mesh.nF[0] @@ -66,7 +74,15 @@ def getFaceInnerProduct(mesh, mu=None, returnP=False): IND = np.r_[ind1, ind2, ind3].flatten() - return sp.coo_matrix((np.ones(3*nc), (range(3*nc), IND)), shape=(3*nc, np.sum(mesh.nF))).tocsr() + PXXX = sp.coo_matrix((np.ones(3*nc), (range(3*nc), IND)), shape=(3*nc, np.sum(mesh.nF))).tocsr() + + if mesh._meshType == 'LOM': + I3x3 = inv3X3BlockDiagonal(getSubArray(fN1[0], [i + pos[0][0], j + pos[0][1], k + pos[0][2]]), getSubArray(fN1[1], [i + pos[0][0], j + pos[0][1], k + pos[0][2]]), getSubArray(fN1[2], [i + pos[0][0], j + pos[0][1], k + pos[0][2]]), + getSubArray(fN2[0], [i + pos[1][0], j + pos[1][1], k + pos[1][2]]), getSubArray(fN2[1], [i + pos[1][0], j + pos[1][1], k + pos[1][2]]), getSubArray(fN2[2], [i + pos[1][0], j + pos[1][1], k + pos[1][2]]), + getSubArray(fN3[0], [i + pos[2][0], j + pos[2][1], k + pos[2][2]]), getSubArray(fN3[1], [i + pos[2][0], j + pos[2][1], k + pos[2][2]]), getSubArray(fN3[2], [i + pos[2][0], j + pos[2][1], k + pos[2][2]])) + PXXX = I3x3 * PXXX + + return PXXX # no | node | f1 | f2 | f3 # 000 | i ,j ,k | i , j, k | i, j , k | i, j, k @@ -110,6 +126,78 @@ def getFaceInnerProduct(mesh, mu=None, returnP=False): return A +def getFaceInnerProduct2D(mesh, mu=None, returnP=False): + + if mu is None: # default is ones + mu = np.ones((mesh.nC, 1)) + + m = np.array([mesh.nCx, mesh.nCy]) + nc = mesh.nC + + i, j = np.int64(range(m[0])), np.int64(range(m[1])) + + iijj = ndgrid(i, j) + ii, jj = iijj[:, 0], iijj[:, 1] + + if mesh._meshType == 'LOM': + fN1 = mesh.r(mesh.normals, 'F', 'Fx', 'M') + fN2 = mesh.r(mesh.normals, 'F', 'Fy', 'M') + + def Pxx(pos): + ind1 = sub2ind(mesh.nFx, np.c_[ii + pos[0][0], jj + pos[0][1]]) + ind2 = sub2ind(mesh.nFy, np.c_[ii + pos[1][0], jj + pos[1][1]]) + mesh.nF[0] + + IND = np.r_[ind1, ind2].flatten() + + PXX = sp.coo_matrix((np.ones(2*nc), (range(2*nc), IND)), shape=(2*nc, np.sum(mesh.nF))).tocsr() + + if mesh._meshType == 'LOM': + # print fN1[0].shape + # print fN2[0].shape + # print np.c_[i+pos[0][0],j+pos[0][1],i+pos[1][0],j+pos[1][1]] + # print fN1[1].shape + I2x2 = inv2X2BlockDiagonal(getSubArray(fN1[0], [i + pos[0][0], j + pos[0][1]]), getSubArray(fN1[1], [i + pos[0][0], j + pos[0][1]]), + getSubArray(fN2[0], [i + pos[1][0], j + pos[1][1]]), getSubArray(fN2[1], [i + pos[1][0], j + pos[1][1]])) + PXX = I2x2 * PXX + + # import matplotlib.pyplot as plt + # plt.spy(PXX) + # plt.show() + return PXX + + # no | node | f1 | f2 + # 00 | i ,j | i , j | i, j + # 10 | i+1,j | i+1, j | i, j + # 01 | i ,j+1 | i , j | i, j+1 + # 11 | i+1,j+1 | i+1, j | i, j+1 + + # Square root of cell volume multiplied by 1/4 + v = np.sqrt(0.25*mesh.vol) + V2 = sdiag(np.r_[v, v]) # We will multiply on each side to keep symmetry + + P00 = V2*Pxx([[0, 0], [0, 0]]) + P10 = V2*Pxx([[1, 0], [0, 0]]) + P01 = V2*Pxx([[0, 0], [0, 1]]) + P11 = V2*Pxx([[1, 0], [0, 1]]) + + if mu.size == mesh.nC: # Isotropic! + mu = mkvc(mu) # ensure it is a vector. + Mu = sdiag(np.r_[mu, mu]) + elif mu.shape[1] == 2: # Diagonal tensor + Mu = sdiag(np.r_[mu[:, 0], mu[:, 1]]) + elif mu.shape[1] == 3: # Fully anisotropic + row1 = sp.hstack((sdiag(mu[:, 0]), sdiag(mu[:, 2]))) + row2 = sp.hstack((sdiag(mu[:, 2]), sdiag(mu[:, 1]))) + Mu = sp.vstack((row1, row2)) + + A = P00.T*Mu*P00 + P10.T*Mu*P10 + P01.T*Mu*P01 + P11.T*Mu*P11 + P = [P00, P10, P01, P11] + if returnP: + return A, P + else: + return A + + def getEdgeInnerProduct(mesh, sigma=None, returnP=False): if sigma is None: # default is ones diff --git a/SimPEG/LomView.py b/SimPEG/LomView.py index 1596dfd9..4c9b1dab 100644 --- a/SimPEG/LomView.py +++ b/SimPEG/LomView.py @@ -14,7 +14,7 @@ class LomView(object): def __init__(self): pass - def plotGrid(self): + def plotGrid(self, length=0.05): """Plot the nodal, cell-centered and staggered grids for 1,2 and 3 dimensions.""" NN = self.r(self.gridN, 'N', 'N', 'M') if self.dim == 2: @@ -31,6 +31,36 @@ class LomView(object): Y = np.r_[Y1, Y2] plt.plot(X, Y) + + plt.hold(True) + Nx = self.r(self.normals, 'F', 'Fx', 'V') + Ny = self.r(self.normals, 'F', 'Fy', 'V') + Tx = self.r(self.tangents, 'E', 'Ex', 'V') + Ty = self.r(self.tangents, 'E', 'Ey', 'V') + + plt.plot(self.gridN[:, 0], self.gridN[:, 1], 'bo') + + nX = np.c_[self.gridFx[:, 0], self.gridFx[:, 0] + Nx[0]*length, self.gridFx[:, 0]*np.nan].flatten() + nY = np.c_[self.gridFx[:, 1], self.gridFx[:, 1] + Nx[1]*length, self.gridFx[:, 1]*np.nan].flatten() + plt.plot(self.gridFx[:, 0], self.gridFx[:, 1], 'rs') + plt.plot(nX, nY, 'r-') + + nX = np.c_[self.gridFy[:, 0], self.gridFy[:, 0] + Ny[0]*length, self.gridFy[:, 0]*np.nan].flatten() + nY = np.c_[self.gridFy[:, 1], self.gridFy[:, 1] + Ny[1]*length, self.gridFy[:, 1]*np.nan].flatten() + #plt.plot(self.gridFy[:, 0], self.gridFy[:, 1], 'gs') + plt.plot(nX, nY, 'g-') + + tX = np.c_[self.gridEx[:, 0], self.gridEx[:, 0] + Tx[0]*length, self.gridEx[:, 0]*np.nan].flatten() + tY = np.c_[self.gridEx[:, 1], self.gridEx[:, 1] + Tx[1]*length, self.gridEx[:, 1]*np.nan].flatten() + plt.plot(self.gridEx[:, 0], self.gridEx[:, 1], 'r^') + plt.plot(tX, tY, 'r-') + + nX = np.c_[self.gridEy[:, 0], self.gridEy[:, 0] + Ty[0]*length, self.gridEy[:, 0]*np.nan].flatten() + nY = np.c_[self.gridEy[:, 1], self.gridEy[:, 1] + Ty[1]*length, self.gridEy[:, 1]*np.nan].flatten() + #plt.plot(self.gridEy[:, 0], self.gridEy[:, 1], 'g^') + plt.plot(nX, nY, 'g-') + plt.axis('equal') + elif self.dim == 3: fig = plt.figure(3) fig.clf() diff --git a/SimPEG/sputils.py b/SimPEG/sputils.py index ae41a210..79a1abf9 100644 --- a/SimPEG/sputils.py +++ b/SimPEG/sputils.py @@ -4,7 +4,7 @@ from utils import mkvc def sdiag(h): """Sparse diagonal matrix""" - return sp.spdiags(h, 0, h.size, h.size, format="csr") + return sp.spdiags(mkvc(h), 0, h.size, h.size, format="csr") def speye(n): @@ -23,6 +23,16 @@ def spzeros(n1, n2): def inv3X3BlockDiagonal(a11, a12, a13, a21, a22, a23, a31, a32, a33): + """ B = inv3X3BlockDiagonal(a11, a12, a13, a21, a22, a23, a31, a32, a33) + + inverts a stack of 3x3 matrices + + Input: + A - a11, a12, a13, a21, a22, a23, a31, a32, a33 + + Output: + B - inverse + """ a11 = mkvc(a11) a12 = mkvc(a12) @@ -53,3 +63,36 @@ def inv3X3BlockDiagonal(a11, a12, a13, a21, a22, a23, a31, a32, a33): sp.hstack((sdiag(b31), sdiag(b32), sdiag(b33))))) return B + + +def inv2X2BlockDiagonal(a11, a12, a21, a22): + """ B = inv2X2BlockDiagonal(a11, a12, a21, a22) + + Inverts a stack of 2x2 matrices by using the inversion formula + + inv(A) = (1/det(A)) * cof(A)^T + + Input: + A - a11, a12, a13, a21, a22, a23, a31, a32, a33 + + Output: + B - inverse + """ + + a11 = mkvc(a11) + a12 = mkvc(a12) + a21 = mkvc(a21) + a22 = mkvc(a22) + + # compute inverse of the determinant. + detAinv = 1./(a11*a22 - a21*a12) + + b11 = +detAinv*a22 + b12 = -detAinv*a12 + b21 = -detAinv*a21 + b22 = +detAinv*a11 + + B = sp.vstack((sp.hstack((sdiag(b11), sdiag(b12))), + sp.hstack((sdiag(b21), sdiag(b22))))) + + return B diff --git a/SimPEG/tests/OrderTest.py b/SimPEG/tests/OrderTest.py index 64d37a32..107a8d9f 100644 --- a/SimPEG/tests/OrderTest.py +++ b/SimPEG/tests/OrderTest.py @@ -65,20 +65,21 @@ class OrderTest(unittest.TestCase): expectedOrder = 2 tolerance = 0.85 meshSizes = [4, 8, 16, 32] - meshType = 'uniformTensorMesh' + meshTypes = ['uniformTensorMesh'] + _meshType = meshTypes[0] meshDimension = 3 def setupMesh(self, nc): """ For a given number of cells nc, generate a TensorMesh with uniform cells with edge length h=1/nc. """ - if 'TensorMesh' in self.meshType: - if 'uniform' in self.meshType: + if 'TensorMesh' in self._meshType: + if 'uniform' in self._meshType: h1 = np.ones(nc)/nc h2 = np.ones(nc)/nc h3 = np.ones(nc)/nc h = [h1, h2, h3] - elif 'random' in self.meshType: + elif 'random' in self._meshType: h1 = np.random.rand(nc) h2 = np.random.rand(nc) h3 = np.random.rand(nc) @@ -90,10 +91,10 @@ class OrderTest(unittest.TestCase): max_h = max([np.max(hi) for hi in self.M.h]) return max_h - elif 'LOM' in self.meshType: - if 'uniform' in self.meshType: + elif 'LOM' in self._meshType: + if 'uniform' in self._meshType: kwrd = 'rect' - elif 'rotate' in self.meshType: + elif 'rotate' in self._meshType: kwrd = 'rotate' else: raise Exception('Unexpected meshType') @@ -117,28 +118,30 @@ class OrderTest(unittest.TestCase): """ - order = [] - err_old = 0. - max_h_old = 0. - for ii, nc in enumerate(self.meshSizes): - max_h = self.setupMesh(nc) - err = self.getError() - if ii == 0: - print '' - print 'Testing convergence on ' + self.M._meshType + ' of: ' + self.name - print '_____________________________________________' - print ' h | error | e(i-1)/e(i) | order' - print '~~~~~~|~~~~~~~~~~~~~|~~~~~~~~~~~~~|~~~~~~~~~~' - print '%4i | %8.2e |' % (nc, err) - else: - order.append(np.log(err/err_old)/np.log(max_h/max_h_old)) - print '%4i | %8.2e | %6.4f | %6.4f' % (nc, err, err_old/err, order[-1]) - err_old = err - max_h_old = max_h - print '---------------------------------------------' - passTest = np.mean(np.array(order)) > self.tolerance*self.expectedOrder - # passTest = len(np.where(np.array(order) > self.tolerance*self.expectedOrder)[0]) > np.floor(0.75*len(order)) - self.assertTrue(passTest) + for meshType in self.meshTypes: + self._meshType = meshType + order = [] + err_old = 0. + max_h_old = 0. + for ii, nc in enumerate(self.meshSizes): + max_h = self.setupMesh(nc) + err = self.getError() + if ii == 0: + print '' + print 'Testing convergence on ' + self.M._meshType + ' of: ' + self.name + print '_____________________________________________' + print ' h | error | e(i-1)/e(i) | order' + print '~~~~~~|~~~~~~~~~~~~~|~~~~~~~~~~~~~|~~~~~~~~~~' + print '%4i | %8.2e |' % (nc, err) + else: + order.append(np.log(err/err_old)/np.log(max_h/max_h_old)) + print '%4i | %8.2e | %6.4f | %6.4f' % (nc, err, err_old/err, order[-1]) + err_old = err + max_h_old = max_h + print '---------------------------------------------' + passTest = np.mean(np.array(order)) > self.tolerance*self.expectedOrder + # passTest = len(np.where(np.array(order) > self.tolerance*self.expectedOrder)[0]) > np.floor(0.75*len(order)) + self.assertTrue(passTest) if __name__ == '__main__': unittest.main() diff --git a/SimPEG/tests/test_massMatrices.py b/SimPEG/tests/test_massMatrices.py index 021a307b..83d8bf3e 100644 --- a/SimPEG/tests/test_massMatrices.py +++ b/SimPEG/tests/test_massMatrices.py @@ -32,9 +32,9 @@ from OrderTest import OrderTest class TestInnerProducts(OrderTest): """Integrate an function over a unit cube domain using edgeInnerProducts and faceInnerProducts.""" - meshType = 'rotateLOM' - meshDimension = 2 - meshSizes = [16, 32, 64] + meshTypes = ['uniformTensorMesh', 'uniformLOM'] + meshDimension = 3 + meshSizes = [16, 32] def getError(self): @@ -118,5 +118,89 @@ class TestInnerProducts(OrderTest): self.orderTest() +class TestInnerProducts2D(OrderTest): + """Integrate an function over a unit cube domain using edgeInnerProducts and faceInnerProducts.""" + + meshTypes = ['uniformTensorMesh', 'uniformLOM', 'rotateLOM'] + meshDimension = 2 + meshSizes = [4, 8, 16, 32, 64, 128] + + def getError(self): + + z = 5 # Because 5 is just such a great number. + + call = lambda fun, xy: fun(xy[:, 0], xy[:, 1]) + + ex = lambda x, y: x**2+y*z + ey = lambda x, y: (z**2)*x+y*z + + sigma1 = lambda x, y: x*y+1 + sigma2 = lambda x, y: x*z+2 + sigma3 = lambda x, y: 3+z*y + + Gc = self.M.gridCC + if self.sigmaTest == 1: + sigma = np.c_[call(sigma1, Gc)] + analytic = 144877./360 # Found using matlab symbolic toolbox. z=5 + elif self.sigmaTest == 2: + sigma = np.c_[call(sigma1, Gc), call(sigma2, Gc)] + analytic = 189959./120 # Found using matlab symbolic toolbox. z=5 + elif self.sigmaTest == 3: + sigma = np.c_[call(sigma1, Gc), call(sigma2, Gc), call(sigma3, Gc)] + analytic = 781427./360 # Found using matlab symbolic toolbox. z=5 + + if self.location == 'edges': + Ex = call(ex, self.M.gridEx) + Ey = call(ey, self.M.gridEy) + E = np.matrix(np.r_[Ex, Ey]).T + A = self.M.getEdgeInnerProduct(sigma) + numeric = E.T*A*E + elif self.location == 'faces': + Fx = call(ex, self.M.gridFx) + Fy = call(ey, self.M.gridFy) + F = np.matrix(np.r_[Fx, Fy]).T + A = self.M.getFaceInnerProduct(sigma) + numeric = F.T*A*F + + err = np.abs(numeric - analytic) + return err + + # def test_order1_edges(self): + # self.name = "2D Edge Inner Product - Isotropic" + # self.location = 'edges' + # self.sigmaTest = 1 + # self.orderTest() + + # def test_order3_edges(self): + # self.name = "2D Edge Inner Product - Anisotropic" + # self.location = 'edges' + # self.sigmaTest = 2 + # self.orderTest() + + # def test_order6_edges(self): + # self.name = "2D Edge Inner Product - Full Tensor" + # self.location = 'edges' + # self.sigmaTest = 3 + # self.orderTest() + + def test_order1_faces(self): + self.name = "2D Face Inner Product - Isotropic" + self.location = 'faces' + self.sigmaTest = 1 + self.orderTest() + + def test_order3_faces(self): + self.name = "2D Face Inner Product - Anisotropic" + self.location = 'faces' + self.sigmaTest = 2 + self.orderTest() + + def test_order6_faces(self): + self.name = "2D Face Inner Product - Full Tensor" + self.location = 'faces' + self.sigmaTest = 3 + self.orderTest() + + if __name__ == '__main__': unittest.main() diff --git a/SimPEG/tests/test_operators.py b/SimPEG/tests/test_operators.py new file mode 100644 index 00000000..03ea36d1 --- /dev/null +++ b/SimPEG/tests/test_operators.py @@ -0,0 +1,144 @@ +import numpy as np +import unittest +import sys +sys.path.append('../') +from OrderTest import OrderTest + +MESHTYPES = ['uniformTensorMesh', 'uniformLOM'] # , 'rotateLOM' + + +class TestCurl(OrderTest): + name = "Curl" + meshTypes = MESHTYPES + + def getError(self): + fun = lambda x: np.cos(x) # i (cos(y)) + j (cos(z)) + k (cos(x)) + sol = lambda x: np.sin(x) # i (sin(z)) + j (sin(x)) + k (sin(y)) + + Ex = fun(self.M.gridEx[:, 1]) + Ey = fun(self.M.gridEy[:, 2]) + Ez = fun(self.M.gridEz[:, 0]) + E = np.concatenate((Ex, Ey, Ez)) + + Fx = sol(self.M.gridFx[:, 2]) + Fy = sol(self.M.gridFy[:, 0]) + Fz = sol(self.M.gridFz[:, 1]) + curlE_anal = np.concatenate((Fx, Fy, Fz)) + + # Generate DIV matrix + CURL = self.M.edgeCurl + + curlE = CURL*E + err = np.linalg.norm((curlE-curlE_anal), np.inf) + return err + + def test_order(self): + self.orderTest() + + +class TestFaceDiv(OrderTest): + name = "Face Divergence" + meshTypes = MESHTYPES + + def getError(self): + DIV = self.M.faceDiv + + #Test function + fun = lambda x: np.sin(x) + Fx = fun(self.M.gridFx[:, 0]) + Fy = fun(self.M.gridFy[:, 1]) + Fz = fun(self.M.gridFz[:, 2]) + + F = np.concatenate((Fx, Fy, Fz)) + divF = DIV*F + sol = lambda x, y, z: (np.cos(x)+np.cos(y)+np.cos(z)) + divF_anal = sol(self.M.gridCC[:, 0], self.M.gridCC[:, 1], self.M.gridCC[:, 2]) + + err = np.linalg.norm((divF-divF_anal), np.inf) + + return err + + def test_order(self): + self.orderTest() + + +class TestFaceDiv2D(OrderTest): + name = "Face Divergence 2D" + meshTypes = MESHTYPES + meshDimension = 2 + + def getError(self): + DIV = self.M.faceDiv + + #Test function + fun = lambda x: np.sin(x) + Fx = fun(self.M.gridFx[:, 0]) + Fy = fun(self.M.gridFy[:, 1]) + + F = np.concatenate((Fx, Fy)) + divF = DIV*F + sol = lambda x, y: (np.cos(x)+np.cos(y)) + divF_anal = sol(self.M.gridCC[:, 0], self.M.gridCC[:, 1]) + + err = np.linalg.norm((divF-divF_anal), np.inf) + + return err + + def test_order(self): + self.orderTest() + + +class TestNodalGrad(OrderTest): + name = "Nodal Gradient" + meshTypes = MESHTYPES + + def getError(self): + GRAD = self.M.nodalGrad + #Test function + fun = lambda x, y, z: (np.cos(x)+np.cos(y)+np.cos(z)) + sol = lambda x: -np.sin(x) # i (sin(x)) + j (sin(y)) + k (sin(z)) + + phi = fun(self.M.gridN[:, 0], self.M.gridN[:, 1], self.M.gridN[:, 2]) + gradE = GRAD*phi + + Ex = sol(self.M.gridEx[:, 0]) + Ey = sol(self.M.gridEy[:, 1]) + Ez = sol(self.M.gridEz[:, 2]) + + gradE_anal = np.concatenate((Ex, Ey, Ez)) + err = np.linalg.norm((gradE-gradE_anal), np.inf) + + return err + + def test_order(self): + self.orderTest() + + +class TestNodalGrad2D(OrderTest): + name = "Nodal Gradient 2D" + meshTypes = MESHTYPES + meshDimension = 2 + + def getError(self): + GRAD = self.M.nodalGrad + #Test function + fun = lambda x, y: (np.cos(x)+np.cos(y)) + sol = lambda x: -np.sin(x) # i (sin(x)) + j (sin(y)) + k (sin(z)) + + phi = fun(self.M.gridN[:, 0], self.M.gridN[:, 1]) + gradE = GRAD*phi + + Ex = sol(self.M.gridEx[:, 0]) + Ey = sol(self.M.gridEy[:, 1]) + + gradE_anal = np.concatenate((Ex, Ey)) + err = np.linalg.norm((gradE-gradE_anal), np.inf) + + return err + + def test_order(self): + self.orderTest() + + +if __name__ == '__main__': + unittest.main() diff --git a/SimPEG/tests/test_tensorMesh.py b/SimPEG/tests/test_tensorMesh.py index bc034e0b..32d47353 100644 --- a/SimPEG/tests/test_tensorMesh.py +++ b/SimPEG/tests/test_tensorMesh.py @@ -58,84 +58,6 @@ class BasicTensorMeshTests(unittest.TestCase): self.assertTrue(t1) -class TestCurl(OrderTest): - name = "Curl" - - def getError(self): - fun = lambda x: np.cos(x) # i (cos(y)) + j (cos(z)) + k (cos(x)) - sol = lambda x: np.sin(x) # i (sin(z)) + j (sin(x)) + k (sin(y)) - - Ex = fun(self.M.gridEx[:, 1]) - Ey = fun(self.M.gridEy[:, 2]) - Ez = fun(self.M.gridEz[:, 0]) - E = np.concatenate((Ex, Ey, Ez)) - - Fx = sol(self.M.gridFx[:, 2]) - Fy = sol(self.M.gridFy[:, 0]) - Fz = sol(self.M.gridFz[:, 1]) - curlE_anal = np.concatenate((Fx, Fy, Fz)) - - # Generate DIV matrix - CURL = self.M.edgeCurl - - curlE = CURL*E - err = np.linalg.norm((curlE-curlE_anal), np.inf) - return err - - def test_order(self): - self.orderTest() - - -class TestFaceDiv(OrderTest): - name = "Face Divergence" - - def getError(self): - DIV = self.M.faceDiv - - #Test function - fun = lambda x: np.sin(x) - Fx = fun(self.M.gridFx[:, 0]) - Fy = fun(self.M.gridFy[:, 1]) - Fz = fun(self.M.gridFz[:, 2]) - - F = np.concatenate((Fx, Fy, Fz)) - divF = DIV*F - sol = lambda x, y, z: (np.cos(x)+np.cos(y)+np.cos(z)) - divF_anal = sol(self.M.gridCC[:, 0], self.M.gridCC[:, 1], self.M.gridCC[:, 2]) - - err = np.linalg.norm((divF-divF_anal), np.inf) - - return err - - def test_order(self): - self.orderTest() - - -class TestNodalGrad(OrderTest): - name = "Nodal Gradient" - - def getError(self): - GRAD = self.M.nodalGrad - #Test function - fun = lambda x, y, z: (np.cos(x)+np.cos(y)+np.cos(z)) - sol = lambda x: -np.sin(x) # i (sin(x)) + j (sin(y)) + k (sin(z)) - - phi = fun(self.M.gridN[:, 0], self.M.gridN[:, 1], self.M.gridN[:, 2]) - gradE = GRAD*phi - - Ex = sol(self.M.gridEx[:, 0]) - Ey = sol(self.M.gridEy[:, 1]) - Ez = sol(self.M.gridEz[:, 2]) - - gradE_anal = np.concatenate((Ex, Ey, Ez)) - err = np.linalg.norm((gradE-gradE_anal), np.inf) - - return err - - def test_order(self): - self.orderTest() - - class TestPoissonEqn(OrderTest): name = "Poisson Equation" meshSizes = [16, 20, 24] @@ -168,5 +90,7 @@ class TestPoissonEqn(OrderTest): self.name = "Poisson Equation - Backward" self.forward = False self.orderTest() + + if __name__ == '__main__': unittest.main() diff --git a/SimPEG/tests/test_utils.py b/SimPEG/tests/test_utils.py index 59b70147..393cd78d 100644 --- a/SimPEG/tests/test_utils.py +++ b/SimPEG/tests/test_utils.py @@ -3,6 +3,7 @@ import unittest import sys sys.path.append('../') from utils import mkvc, ndgrid, indexCube +from sputils import sdiag, inv3X3BlockDiagonal, inv2X2BlockDiagonal, sp class TestSequenceFunctions(unittest.TestCase): @@ -62,5 +63,29 @@ class TestSequenceFunctions(unittest.TestCase): self.assertTrue(np.all(indexCube('G', nN) == np.array([13, 14, 16, 17, 22, 23, 25, 26]))) self.assertTrue(np.all(indexCube('H', nN) == np.array([10, 11, 13, 14, 19, 20, 22, 23]))) + def test_invXXXBlockDiagonal(self): + + a = [np.random.rand(5, 1) for i in range(4)] + + B = inv2X2BlockDiagonal(*a) + + A = sp.vstack((sp.hstack((sdiag(a[0]), sdiag(a[1]))), + sp.hstack((sdiag(a[2]), sdiag(a[3]))))) + + Z2 = B*A - sp.eye(10, 10) + self.assertTrue(np.linalg.norm(Z2.todense().ravel(), 2) < 1e-12) + + a = [np.random.rand(5, 1) for i in range(9)] + B = inv3X3BlockDiagonal(*a) + + A = sp.vstack((sp.hstack((sdiag(a[0]), sdiag(a[1]), sdiag(a[2]))), + sp.hstack((sdiag(a[3]), sdiag(a[4]), sdiag(a[5]))), + sp.hstack((sdiag(a[6]), sdiag(a[7]), sdiag(a[8]))))) + + Z3 = B*A - sp.eye(15, 15) + + self.assertTrue(np.linalg.norm(Z3.todense().ravel(), 2) < 1e-12) + + if __name__ == '__main__': unittest.main() diff --git a/SimPEG/utils.py b/SimPEG/utils.py index 0741b7bb..1b5be6bd 100644 --- a/SimPEG/utils.py +++ b/SimPEG/utils.py @@ -319,4 +319,12 @@ def sub2ind(shape, subs): def getSubArray(A, ind): """subArray""" - return A[ind[0], :, :][:, ind[1], :][:, :, ind[2]] + assert type(ind) == list, "ind must be a list of vectors" + assert len(A.shape) == len(ind), "ind must have the same length as the dimension of A" + + if len(A.shape) == 2: + return A[ind[0], :][:, ind[1]] + elif len(A.shape) == 3: + return A[ind[0], :, :][:, ind[1], :][:, :, ind[2]] + else: + raise Exception("getSubArray does not support dimension asked.") From 3d936e07a1328cffa8cde7dee7da108c9e60fcfc Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Mon, 5 Aug 2013 12:16:55 -0700 Subject: [PATCH 43/51] Edge Inner products in 2D NOTE: still has bugs. --- SimPEG/InnerProducts.py | 71 ++++++++++++++++++++++++++++++- SimPEG/exampleGrid.py | 2 +- SimPEG/tests/OrderTest.py | 6 ++- SimPEG/tests/test_massMatrices.py | 34 +++++++-------- 4 files changed, 92 insertions(+), 21 deletions(-) diff --git a/SimPEG/InnerProducts.py b/SimPEG/InnerProducts.py index 5cc391c4..5105295b 100644 --- a/SimPEG/InnerProducts.py +++ b/SimPEG/InnerProducts.py @@ -26,8 +26,10 @@ class InnerProducts(object): pass elif self._meshType == 'LOM': pass # todo: we should be doing something slightly different here! - return getEdgeInnerProduct(self, sigma, returnP) - + if self.dim == 2: + return getEdgeInnerProduct2D(self, sigma, returnP) + elif self.dim == 3: + return getEdgeInnerProduct(self, sigma, returnP) # ------------------------ Geometries ------------------------------ # @@ -275,6 +277,71 @@ def getEdgeInnerProduct(mesh, sigma=None, returnP=False): return A +def getEdgeInnerProduct2D(mesh, sigma=None, returnP=False): + + if sigma is None: # default is ones + sigma = np.ones((mesh.nC, 1)) + + m = np.array([mesh.nCx, mesh.nCy]) + nc = mesh.nC + + i, j = np.int64(range(m[0])), np.int64(range(m[1])) + + iijj = ndgrid(i, j) + ii, jj = iijj[:, 0], iijj[:, 1] + + if mesh._meshType == 'LOM': + eT1 = mesh.r(mesh.tangents, 'E', 'Ex', 'M') + eT2 = mesh.r(mesh.tangents, 'E', 'Ey', 'M') + + def Pxx(pos): + ind1 = sub2ind(mesh.nEx, np.c_[ii + pos[0][0], jj + pos[0][1]]) + ind2 = sub2ind(mesh.nEy, np.c_[ii + pos[1][0], jj + pos[1][1]]) + mesh.nE[0] + + IND = np.r_[ind1, ind2].flatten() + + PXX = sp.coo_matrix((np.ones(2*nc), (range(2*nc), IND)), shape=(2*nc, np.sum(mesh.nE))).tocsr() + + if mesh._meshType == 'LOM': + I2x2 = inv2X2BlockDiagonal(getSubArray(eT1[0], [i + pos[0][0], j + pos[0][1]]), getSubArray(eT1[1], [i + pos[0][0], j + pos[0][1]]), + getSubArray(eT2[0], [i + pos[1][0], j + pos[1][1]]), getSubArray(eT2[1], [i + pos[1][0], j + pos[1][1]])) + PXX = I2x2 * PXX + + return PXX + + # no | node | e1 | e2 + # 00 | i ,j | i ,j | i ,j + # 10 | i+1,j | i ,j | i+1,j + # 01 | i ,j+1 | i ,j+1 | i ,j + # 11 | i+1,j+1 | i ,j+1 | i+1,j + + # Square root of cell volume multiplied by 1/4 + v = np.sqrt(0.25*mesh.vol) + V2 = sdiag(np.r_[v, v]) # We will multiply on each side to keep symmetry + + P00 = V2*Pxx([[0, 0], [0, 0]]) + P10 = V2*Pxx([[0, 0], [1, 0]]) + P01 = V2*Pxx([[0, 1], [0, 0]]) + P11 = V2*Pxx([[0, 1], [1, 0]]) + + if sigma.size == mesh.nC: # Isotropic! + sigma = mkvc(sigma) # ensure it is a vector. + Sigma = sdiag(np.r_[sigma, sigma]) + elif sigma.shape[1] == 2: # Diagonal tensor + Sigma = sdiag(np.r_[sigma[:, 0], sigma[:, 1]]) + elif sigma.shape[1] == 3: # Fully anisotropic + row1 = sp.hstack((sdiag(sigma[:, 0]), sdiag(sigma[:, 2]))) + row2 = sp.hstack((sdiag(sigma[:, 2]), sdiag(sigma[:, 1]))) + Sigma = sp.vstack((row1, row2)) + + A = P00.T*Sigma*P00 + P10.T*Sigma*P10 + P01.T*Sigma*P01 + P11.T*Sigma*P11 + P = [P00, P10, P01, P11] + if returnP: + return A, P + else: + return A + + if __name__ == '__main__': from TensorMesh import TensorMesh h = [np.array([1, 2, 3, 4]), np.array([1, 2, 1, 4, 2]), np.array([1, 1, 4, 1])] diff --git a/SimPEG/exampleGrid.py b/SimPEG/exampleGrid.py index a49326df..2179a17c 100644 --- a/SimPEG/exampleGrid.py +++ b/SimPEG/exampleGrid.py @@ -1,5 +1,5 @@ import numpy as np -from utils import mkvc, ndgrid +from utils import ndgrid def exampleLomGird(nC, exType): diff --git a/SimPEG/tests/OrderTest.py b/SimPEG/tests/OrderTest.py index 107a8d9f..238c3216 100644 --- a/SimPEG/tests/OrderTest.py +++ b/SimPEG/tests/OrderTest.py @@ -140,7 +140,11 @@ class OrderTest(unittest.TestCase): max_h_old = max_h print '---------------------------------------------' passTest = np.mean(np.array(order)) > self.tolerance*self.expectedOrder - # passTest = len(np.where(np.array(order) > self.tolerance*self.expectedOrder)[0]) > np.floor(0.75*len(order)) + if passTest: + print ['The test be workin!', 'You get a gold star!', 'Yay passed!', 'Happy little convergence test!', 'That was easy!'][np.random.randint(5)] + else: + print 'Failed to pass test on ' + self._meshType + '.' + print '' self.assertTrue(passTest) if __name__ == '__main__': diff --git a/SimPEG/tests/test_massMatrices.py b/SimPEG/tests/test_massMatrices.py index 83d8bf3e..c1276403 100644 --- a/SimPEG/tests/test_massMatrices.py +++ b/SimPEG/tests/test_massMatrices.py @@ -165,23 +165,23 @@ class TestInnerProducts2D(OrderTest): err = np.abs(numeric - analytic) return err - # def test_order1_edges(self): - # self.name = "2D Edge Inner Product - Isotropic" - # self.location = 'edges' - # self.sigmaTest = 1 - # self.orderTest() + def test_order1_edges(self): + self.name = "2D Edge Inner Product - Isotropic" + self.location = 'edges' + self.sigmaTest = 1 + self.orderTest() - # def test_order3_edges(self): - # self.name = "2D Edge Inner Product - Anisotropic" - # self.location = 'edges' - # self.sigmaTest = 2 - # self.orderTest() + def test_order3_edges(self): + self.name = "2D Edge Inner Product - Anisotropic" + self.location = 'edges' + self.sigmaTest = 2 + self.orderTest() - # def test_order6_edges(self): - # self.name = "2D Edge Inner Product - Full Tensor" - # self.location = 'edges' - # self.sigmaTest = 3 - # self.orderTest() + def test_order6_edges(self): + self.name = "2D Edge Inner Product - Full Tensor" + self.location = 'edges' + self.sigmaTest = 3 + self.orderTest() def test_order1_faces(self): self.name = "2D Face Inner Product - Isotropic" @@ -189,13 +189,13 @@ class TestInnerProducts2D(OrderTest): self.sigmaTest = 1 self.orderTest() - def test_order3_faces(self): + def test_order2_faces(self): self.name = "2D Face Inner Product - Anisotropic" self.location = 'faces' self.sigmaTest = 2 self.orderTest() - def test_order6_faces(self): + def test_order3_faces(self): self.name = "2D Face Inner Product - Full Tensor" self.location = 'faces' self.sigmaTest = 3 From 83fe9df7431473c149dcd860fba50370e632bcd9 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Mon, 5 Aug 2013 16:48:05 -0700 Subject: [PATCH 44/51] Ha. Not a bug, the test was wrong. Must project the components of the field. --- SimPEG/tests/test_massMatrices.py | 102 +++++++++++++++++++++++++----- 1 file changed, 87 insertions(+), 15 deletions(-) diff --git a/SimPEG/tests/test_massMatrices.py b/SimPEG/tests/test_massMatrices.py index c1276403..bfcc80c5 100644 --- a/SimPEG/tests/test_massMatrices.py +++ b/SimPEG/tests/test_massMatrices.py @@ -32,7 +32,7 @@ from OrderTest import OrderTest class TestInnerProducts(OrderTest): """Integrate an function over a unit cube domain using edgeInnerProducts and faceInnerProducts.""" - meshTypes = ['uniformTensorMesh', 'uniformLOM'] + meshTypes = ['uniformTensorMesh', 'uniformLOM', 'rotateLOM'] meshDimension = 3 meshSizes = [16, 32] @@ -64,17 +64,61 @@ class TestInnerProducts(OrderTest): analytic = 69881./21600 # Found using matlab symbolic toolbox. if self.location == 'edges': - Ex = call(ex, self.M.gridEx) - Ey = call(ey, self.M.gridEy) - Ez = call(ez, self.M.gridEz) - E = np.matrix(np.r_[Ex, Ey, Ez]).T + if self.M._meshType == 'TENSOR': + Ex = call(ex, self.M.gridEx) + Ey = call(ey, self.M.gridEy) + Ez = call(ez, self.M.gridEz) + E = np.matrix(np.r_[Ex, Ey, Ez]).T + elif self.M._meshType == 'LOM': + Tx = self.M.r(self.M.tangents, 'E', 'Ex', 'V') + Ty = self.M.r(self.M.tangents, 'E', 'Ey', 'V') + Tz = self.M.r(self.M.tangents, 'E', 'Ez', 'V') + + EX_x = call(ex, self.M.gridEx) + EY_x = call(ey, self.M.gridEx) + EZ_x = call(ez, self.M.gridEx) + Ex = np.sum(np.c_[EX_x, EY_x, EZ_x]*np.c_[Tx[0], Tx[1], Tx[2]], 1) + + EX_y = call(ex, self.M.gridEy) + EY_y = call(ey, self.M.gridEy) + EZ_y = call(ez, self.M.gridEy) + Ey = np.sum(np.c_[EX_y, EY_y, EZ_y]*np.c_[Ty[0], Ty[1], Ty[2]], 1) + + EX_z = call(ex, self.M.gridEz) + EY_z = call(ey, self.M.gridEz) + EZ_z = call(ez, self.M.gridEz) + Ez = np.sum(np.c_[EX_z, EY_z, EZ_z]*np.c_[Tz[0], Tz[1], Tz[2]], 1) + + E = np.matrix(np.r_[Ex, Ey, Ez]).T A = self.M.getEdgeInnerProduct(sigma) numeric = E.T*A*E elif self.location == 'faces': - Fx = call(ex, self.M.gridFx) - Fy = call(ey, self.M.gridFy) - Fz = call(ez, self.M.gridFz) - F = np.matrix(np.r_[Fx, Fy, Fz]).T + if self.M._meshType == 'TENSOR': + Fx = call(ex, self.M.gridFx) + Fy = call(ey, self.M.gridFy) + Fz = call(ez, self.M.gridFz) + F = np.matrix(np.r_[Fx, Fy, Fz]).T + elif self.M._meshType == 'LOM': + Nx = self.M.r(self.M.normals, 'F', 'Fx', 'V') + Ny = self.M.r(self.M.normals, 'F', 'Fy', 'V') + Nz = self.M.r(self.M.normals, 'F', 'Fz', 'V') + + FX_x = call(ex, self.M.gridFx) + FY_x = call(ey, self.M.gridFx) + FZ_x = call(ez, self.M.gridFx) + Fx = np.sum(np.c_[FX_x, FY_x, FZ_x]*np.c_[Nx[0], Nx[1], Nx[2]], 1) + + FX_y = call(ex, self.M.gridFy) + FY_y = call(ey, self.M.gridFy) + FZ_y = call(ez, self.M.gridFy) + Fy = np.sum(np.c_[FX_y, FY_y, FZ_y]*np.c_[Ny[0], Ny[1], Ny[2]], 1) + + FX_z = call(ex, self.M.gridFz) + FY_z = call(ey, self.M.gridFz) + FZ_z = call(ez, self.M.gridFz) + Fz = np.sum(np.c_[FX_z, FY_z, FZ_z]*np.c_[Nz[0], Nz[1], Nz[2]], 1) + + F = np.matrix(np.r_[Fx, Fy, Fz]).T A = self.M.getFaceInnerProduct(sigma) numeric = F.T*A*F @@ -150,15 +194,43 @@ class TestInnerProducts2D(OrderTest): analytic = 781427./360 # Found using matlab symbolic toolbox. z=5 if self.location == 'edges': - Ex = call(ex, self.M.gridEx) - Ey = call(ey, self.M.gridEy) - E = np.matrix(np.r_[Ex, Ey]).T + if self.M._meshType == 'TENSOR': + Ex = call(ex, self.M.gridEx) + Ey = call(ey, self.M.gridEy) + E = np.matrix(np.r_[Ex, Ey]).T + elif self.M._meshType == 'LOM': + Tx = self.M.r(self.M.tangents, 'E', 'Ex', 'V') + Ty = self.M.r(self.M.tangents, 'E', 'Ey', 'V') + + EX_x = call(ex, self.M.gridEx) + EY_x = call(ey, self.M.gridEx) + Ex = np.sum(np.c_[EX_x, EY_x]*np.c_[Tx[0], Tx[1]], 1) + + EX_y = call(ex, self.M.gridEy) + EY_y = call(ey, self.M.gridEy) + Ey = np.sum(np.c_[EX_y, EY_y]*np.c_[Ty[0], Ty[1]], 1) + + E = np.matrix(np.r_[Ex, Ey]).T A = self.M.getEdgeInnerProduct(sigma) numeric = E.T*A*E elif self.location == 'faces': - Fx = call(ex, self.M.gridFx) - Fy = call(ey, self.M.gridFy) - F = np.matrix(np.r_[Fx, Fy]).T + if self.M._meshType == 'TENSOR': + Fx = call(ex, self.M.gridFx) + Fy = call(ey, self.M.gridFy) + F = np.matrix(np.r_[Fx, Fy]).T + elif self.M._meshType == 'LOM': + Nx = self.M.r(self.M.normals, 'F', 'Fx', 'V') + Ny = self.M.r(self.M.normals, 'F', 'Fy', 'V') + + FX_x = call(ex, self.M.gridFx) + FY_x = call(ey, self.M.gridFx) + Fx = np.sum(np.c_[FX_x, FY_x]*np.c_[Nx[0], Nx[1]], 1) + + FX_y = call(ex, self.M.gridFy) + FY_y = call(ey, self.M.gridFy) + Fy = np.sum(np.c_[FX_y, FY_y]*np.c_[Ny[0], Ny[1]], 1) + + F = np.matrix(np.r_[Fx, Fy]).T A = self.M.getFaceInnerProduct(sigma) numeric = F.T*A*F From 4b545fb1b75e375917f013743397ada5fda568da Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Mon, 5 Aug 2013 17:16:01 -0700 Subject: [PATCH 45/51] Cleaned up projection code and put it in LOM. --- SimPEG/LogicallyOrthogonalMesh.py | 13 ++++- SimPEG/tests/test_massMatrices.py | 80 +++++++------------------------ 2 files changed, 30 insertions(+), 63 deletions(-) diff --git a/SimPEG/LogicallyOrthogonalMesh.py b/SimPEG/LogicallyOrthogonalMesh.py index 1c29c947..71788230 100644 --- a/SimPEG/LogicallyOrthogonalMesh.py +++ b/SimPEG/LogicallyOrthogonalMesh.py @@ -207,7 +207,7 @@ class LogicallyOrthogonalMesh(BaseMesh, DiffOperators, InnerProducts, LomView): volTetra(self.gridN, A, E, F, H) + # cutted edge top volTetra(self.gridN, A, H, F, C) + # middle volTetra(self.gridN, C, H, D, A) + # cutted edge bottom - volTetra(self.gridN, C, G, H, F)) # cutted edge bottom + volTetra(self.gridN, C, G, H, F)) # cutted edge bottom self._vol = (vol1 + vol2)/2 return self._vol @@ -319,6 +319,17 @@ NyX, NyY, NyZ = M.r(M.normals, 'F', 'Fy', 'M') _tangents = None tangents = property(**tangents()) + def projectFaceVector(self, fV): + """Given a vector, fV, in cartesian coordinates, this will project it onto the mesh using the normals""" + assert type(fV) == np.ndarray, 'fV must be an ndarray' + assert len(fV.shape) == 2 and fV.shape[0] == np.sum(self.nF) and fV.shape[1] == self.dim, 'fV must be an ndarray of shape (nF x dim)' + return mkvc(np.sum(fV*self.normals, 1), 2) + + def projectEdgeVector(self, eV): + """Given a vector, eV, in cartesian coordinates, this will project it onto the mesh using the tangents""" + assert type(eV) == np.ndarray, 'eV must be an ndarray' + assert len(eV.shape) == 2 and eV.shape[0] == np.sum(self.nE) and eV.shape[1] == self.dim, 'eV must be an ndarray of shape (nE x dim)' + return mkvc(np.sum(eV*self.tangents, 1), 2) if __name__ == '__main__': nc = 5 diff --git a/SimPEG/tests/test_massMatrices.py b/SimPEG/tests/test_massMatrices.py index bfcc80c5..07d67e9c 100644 --- a/SimPEG/tests/test_massMatrices.py +++ b/SimPEG/tests/test_massMatrices.py @@ -70,26 +70,11 @@ class TestInnerProducts(OrderTest): Ez = call(ez, self.M.gridEz) E = np.matrix(np.r_[Ex, Ey, Ez]).T elif self.M._meshType == 'LOM': - Tx = self.M.r(self.M.tangents, 'E', 'Ex', 'V') - Ty = self.M.r(self.M.tangents, 'E', 'Ey', 'V') - Tz = self.M.r(self.M.tangents, 'E', 'Ez', 'V') - - EX_x = call(ex, self.M.gridEx) - EY_x = call(ey, self.M.gridEx) - EZ_x = call(ez, self.M.gridEx) - Ex = np.sum(np.c_[EX_x, EY_x, EZ_x]*np.c_[Tx[0], Tx[1], Tx[2]], 1) - - EX_y = call(ex, self.M.gridEy) - EY_y = call(ey, self.M.gridEy) - EZ_y = call(ez, self.M.gridEy) - Ey = np.sum(np.c_[EX_y, EY_y, EZ_y]*np.c_[Ty[0], Ty[1], Ty[2]], 1) - - EX_z = call(ex, self.M.gridEz) - EY_z = call(ey, self.M.gridEz) - EZ_z = call(ez, self.M.gridEz) - Ez = np.sum(np.c_[EX_z, EY_z, EZ_z]*np.c_[Tz[0], Tz[1], Tz[2]], 1) - - E = np.matrix(np.r_[Ex, Ey, Ez]).T + cart = lambda g: np.c_[call(ex, g), call(ey, g), call(ez, g)] + Ec = np.vstack((cart(self.M.gridEx), + cart(self.M.gridEy), + cart(self.M.gridEz))) + E = np.matrix(self.M.projectEdgeVector(Ec)) A = self.M.getEdgeInnerProduct(sigma) numeric = E.T*A*E elif self.location == 'faces': @@ -99,26 +84,11 @@ class TestInnerProducts(OrderTest): Fz = call(ez, self.M.gridFz) F = np.matrix(np.r_[Fx, Fy, Fz]).T elif self.M._meshType == 'LOM': - Nx = self.M.r(self.M.normals, 'F', 'Fx', 'V') - Ny = self.M.r(self.M.normals, 'F', 'Fy', 'V') - Nz = self.M.r(self.M.normals, 'F', 'Fz', 'V') - - FX_x = call(ex, self.M.gridFx) - FY_x = call(ey, self.M.gridFx) - FZ_x = call(ez, self.M.gridFx) - Fx = np.sum(np.c_[FX_x, FY_x, FZ_x]*np.c_[Nx[0], Nx[1], Nx[2]], 1) - - FX_y = call(ex, self.M.gridFy) - FY_y = call(ey, self.M.gridFy) - FZ_y = call(ez, self.M.gridFy) - Fy = np.sum(np.c_[FX_y, FY_y, FZ_y]*np.c_[Ny[0], Ny[1], Ny[2]], 1) - - FX_z = call(ex, self.M.gridFz) - FY_z = call(ey, self.M.gridFz) - FZ_z = call(ez, self.M.gridFz) - Fz = np.sum(np.c_[FX_z, FY_z, FZ_z]*np.c_[Nz[0], Nz[1], Nz[2]], 1) - - F = np.matrix(np.r_[Fx, Fy, Fz]).T + cart = lambda g: np.c_[call(ex, g), call(ey, g), call(ez, g)] + Fc = np.vstack((cart(self.M.gridFx), + cart(self.M.gridFy), + cart(self.M.gridFz))) + F = np.matrix(self.M.projectFaceVector(Fc)) A = self.M.getFaceInnerProduct(sigma) numeric = F.T*A*F @@ -199,18 +169,11 @@ class TestInnerProducts2D(OrderTest): Ey = call(ey, self.M.gridEy) E = np.matrix(np.r_[Ex, Ey]).T elif self.M._meshType == 'LOM': - Tx = self.M.r(self.M.tangents, 'E', 'Ex', 'V') - Ty = self.M.r(self.M.tangents, 'E', 'Ey', 'V') + cart = lambda g: np.c_[call(ex, g), call(ey, g)] + Ec = np.vstack((cart(self.M.gridEx), + cart(self.M.gridEy))) + E = np.matrix(self.M.projectEdgeVector(Ec)) - EX_x = call(ex, self.M.gridEx) - EY_x = call(ey, self.M.gridEx) - Ex = np.sum(np.c_[EX_x, EY_x]*np.c_[Tx[0], Tx[1]], 1) - - EX_y = call(ex, self.M.gridEy) - EY_y = call(ey, self.M.gridEy) - Ey = np.sum(np.c_[EX_y, EY_y]*np.c_[Ty[0], Ty[1]], 1) - - E = np.matrix(np.r_[Ex, Ey]).T A = self.M.getEdgeInnerProduct(sigma) numeric = E.T*A*E elif self.location == 'faces': @@ -219,18 +182,11 @@ class TestInnerProducts2D(OrderTest): Fy = call(ey, self.M.gridFy) F = np.matrix(np.r_[Fx, Fy]).T elif self.M._meshType == 'LOM': - Nx = self.M.r(self.M.normals, 'F', 'Fx', 'V') - Ny = self.M.r(self.M.normals, 'F', 'Fy', 'V') + cart = lambda g: np.c_[call(ex, g), call(ey, g)] + Fc = np.vstack((cart(self.M.gridFx), + cart(self.M.gridFy))) + F = np.matrix(self.M.projectFaceVector(Fc)) - FX_x = call(ex, self.M.gridFx) - FY_x = call(ey, self.M.gridFx) - Fx = np.sum(np.c_[FX_x, FY_x]*np.c_[Nx[0], Nx[1]], 1) - - FX_y = call(ex, self.M.gridFy) - FY_y = call(ey, self.M.gridFy) - Fy = np.sum(np.c_[FX_y, FY_y]*np.c_[Ny[0], Ny[1]], 1) - - F = np.matrix(np.r_[Fx, Fy]).T A = self.M.getFaceInnerProduct(sigma) numeric = F.T*A*F From f01b4370b9f68379684161962dbef2e74630e43f Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Mon, 5 Aug 2013 17:28:55 -0700 Subject: [PATCH 46/51] Added example of plotting the grid for a Lom --- SimPEG/InnerProducts.py | 15 ----- notebooks/exLomPlots.ipynb | 120 +++++++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+), 15 deletions(-) create mode 100644 notebooks/exLomPlots.ipynb diff --git a/SimPEG/InnerProducts.py b/SimPEG/InnerProducts.py index 5105295b..267a9bb0 100644 --- a/SimPEG/InnerProducts.py +++ b/SimPEG/InnerProducts.py @@ -12,20 +12,12 @@ class InnerProducts(object): raise Exception('InnerProducts is a base class providing inner product matrices for meshes and cannot run on its own. Inherit to your favorite Mesh class.') def getFaceInnerProduct(self, mu=None, returnP=False): - if self._meshType == 'TENSOR': - pass - elif self._meshType == 'LOM': - pass # todo: we should be doing something slightly different here! if self.dim == 2: return getFaceInnerProduct2D(self, mu, returnP) elif self.dim == 3: return getFaceInnerProduct(self, mu, returnP) def getEdgeInnerProduct(self, sigma=None, returnP=False): - if self._meshType == 'TENSOR': - pass - elif self._meshType == 'LOM': - pass # todo: we should be doing something slightly different here! if self.dim == 2: return getEdgeInnerProduct2D(self, sigma, returnP) elif self.dim == 3: @@ -154,17 +146,10 @@ def getFaceInnerProduct2D(mesh, mu=None, returnP=False): PXX = sp.coo_matrix((np.ones(2*nc), (range(2*nc), IND)), shape=(2*nc, np.sum(mesh.nF))).tocsr() if mesh._meshType == 'LOM': - # print fN1[0].shape - # print fN2[0].shape - # print np.c_[i+pos[0][0],j+pos[0][1],i+pos[1][0],j+pos[1][1]] - # print fN1[1].shape I2x2 = inv2X2BlockDiagonal(getSubArray(fN1[0], [i + pos[0][0], j + pos[0][1]]), getSubArray(fN1[1], [i + pos[0][0], j + pos[0][1]]), getSubArray(fN2[0], [i + pos[1][0], j + pos[1][1]]), getSubArray(fN2[1], [i + pos[1][0], j + pos[1][1]])) PXX = I2x2 * PXX - # import matplotlib.pyplot as plt - # plt.spy(PXX) - # plt.show() return PXX # no | node | f1 | f2 diff --git a/notebooks/exLomPlots.ipynb b/notebooks/exLomPlots.ipynb new file mode 100644 index 00000000..e2156396 --- /dev/null +++ b/notebooks/exLomPlots.ipynb @@ -0,0 +1,120 @@ +{ + "metadata": { + "name": "exLomPlots" + }, + "nbformat": 3, + "nbformat_minor": 0, + "worksheets": [ + { + "cells": [ + { + "cell_type": "code", + "collapsed": false, + "input": [ + "import sys\n", + "sys.path.append('../')\n", + "\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "from SimPEG import LogicallyOrthogonalMesh, utils, exampleLomGird\n", + "mkvc = utils.mkvc" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 38 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test 2D Plots\n", + "\n", + "For 2D nodal or cell-centered plots are supported.\n" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "X, Y = exampleLomGird([3,3],'rotate')\n", + "M = LogicallyOrthogonalMesh([X, Y])" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 39 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "M.plotGrid()" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "display_data", + "png": "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\n", + "text": [ + "" + ] + } + ], + "prompt_number": 40 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test 3D Plots\n", + "\n", + "Plot x, y, and z coordinates of cell-centred points" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "X, Y, Z = exampleLomGird([3,3,3],'rotate')\n", + "M = LogicallyOrthogonalMesh([X, Y, Z])" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 41 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "M.plotGrid()" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "display_data", + "png": "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\njwAAAABJRU5ErkJggg==\n", + "text": [ + "" + ] + } + ], + "prompt_number": 42 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 42 + } + ], + "metadata": {} + } + ] +} \ No newline at end of file From a510755926ceb8a9ec89fa946932bbc7ecfd9399 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Tue, 6 Aug 2013 15:07:32 -0700 Subject: [PATCH 47/51] Moved projectFace/EdgeVector to the base mesh class, along with some default normal and tangents. --- SimPEG/BaseMesh.py | 45 +++++++++++++++++++++++++++++++ SimPEG/LogicallyOrthogonalMesh.py | 12 --------- SimPEG/TensorMesh.py | 7 ----- 3 files changed, 45 insertions(+), 19 deletions(-) diff --git a/SimPEG/BaseMesh.py b/SimPEG/BaseMesh.py index 9d8982e6..c5a7121a 100644 --- a/SimPEG/BaseMesh.py +++ b/SimPEG/BaseMesh.py @@ -308,6 +308,51 @@ class BaseMesh(object): return locals() nF = property(**nF()) + def normals(): + doc = "Face Normals" + + def fget(self): + if self.dim == 2: + nX = np.c_[np.ones(self.nF[0]), np.zeros(self.nF[0])] + nY = np.c_[np.zeros(self.nF[1]), np.ones(self.nF[1])] + return np.r_[nX, nY] + elif self.dim == 3: + nX = np.c_[np.ones(self.nF[0]), np.zeros(self.nF[0]), np.zeros(self.nF[0])] + nY = np.c_[np.zeros(self.nF[1]), np.ones(self.nF[1]), np.zeros(self.nF[1])] + nZ = np.c_[np.zeros(self.nF[2]), np.zeros(self.nF[2]), np.ones(self.nF[2])] + return np.r_[nX, nY, nZ] + return locals() + normals = property(**normals()) + + def tangents(): + doc = "Edge Tangents" + + def fget(self): + if self.dim == 2: + tX = np.c_[np.ones(self.nE[0]), np.zeros(self.nE[0])] + tY = np.c_[np.zeros(self.nE[1]), np.ones(self.nE[1])] + return np.r_[tX, tY] + elif self.dim == 3: + tX = np.c_[np.ones(self.nE[0]), np.zeros(self.nE[0]), np.zeros(self.nE[0])] + tY = np.c_[np.zeros(self.nE[1]), np.ones(self.nE[1]), np.zeros(self.nE[1])] + tZ = np.c_[np.zeros(self.nE[2]), np.zeros(self.nE[2]), np.ones(self.nE[2])] + return np.r_[tX, tY, tZ] + return locals() + tangents = property(**tangents()) + + def projectFaceVector(self, fV): + """Given a vector, fV, in cartesian coordinates, this will project it onto the mesh using the normals""" + assert type(fV) == np.ndarray, 'fV must be an ndarray' + assert len(fV.shape) == 2 and fV.shape[0] == np.sum(self.nF) and fV.shape[1] == self.dim, 'fV must be an ndarray of shape (nF x dim)' + return np.sum(fV*self.normals, 1) + + def projectEdgeVector(self, eV): + """Given a vector, eV, in cartesian coordinates, this will project it onto the mesh using the tangents""" + assert type(eV) == np.ndarray, 'eV must be an ndarray' + assert len(eV.shape) == 2 and eV.shape[0] == np.sum(self.nE) and eV.shape[1] == self.dim, 'eV must be an ndarray of shape (nE x dim)' + return np.sum(eV*self.tangents, 1) + + if __name__ == '__main__': m = BaseMesh([3, 2, 4]) print m.n diff --git a/SimPEG/LogicallyOrthogonalMesh.py b/SimPEG/LogicallyOrthogonalMesh.py index 71788230..cfa8b7ed 100644 --- a/SimPEG/LogicallyOrthogonalMesh.py +++ b/SimPEG/LogicallyOrthogonalMesh.py @@ -319,18 +319,6 @@ NyX, NyY, NyZ = M.r(M.normals, 'F', 'Fy', 'M') _tangents = None tangents = property(**tangents()) - def projectFaceVector(self, fV): - """Given a vector, fV, in cartesian coordinates, this will project it onto the mesh using the normals""" - assert type(fV) == np.ndarray, 'fV must be an ndarray' - assert len(fV.shape) == 2 and fV.shape[0] == np.sum(self.nF) and fV.shape[1] == self.dim, 'fV must be an ndarray of shape (nF x dim)' - return mkvc(np.sum(fV*self.normals, 1), 2) - - def projectEdgeVector(self, eV): - """Given a vector, eV, in cartesian coordinates, this will project it onto the mesh using the tangents""" - assert type(eV) == np.ndarray, 'eV must be an ndarray' - assert len(eV.shape) == 2 and eV.shape[0] == np.sum(self.nE) and eV.shape[1] == self.dim, 'eV must be an ndarray of shape (nE x dim)' - return mkvc(np.sum(eV*self.tangents, 1), 2) - if __name__ == '__main__': nc = 5 h1 = np.cumsum(np.r_[0, np.ones(nc)/(nc)]) diff --git a/SimPEG/TensorMesh.py b/SimPEG/TensorMesh.py index 5218aec1..67581e10 100644 --- a/SimPEG/TensorMesh.py +++ b/SimPEG/TensorMesh.py @@ -184,13 +184,6 @@ class TensorMesh(BaseMesh, TensorView, DiffOperators, InnerProducts): _gridEz = None # Store grid by default gridEz = property(**gridEz()) - def getBoundaryIndex(self, gridType): - """Needed for faces edges and cells""" - pass - - def getCellNumbering(self): - pass - # --------------- Geometries --------------------- def vol(): doc = "Construct cell volumes of the 3D model as 1d array." From 262aacce85ae2095d92632ac726be07850339db2 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Tue, 6 Aug 2013 15:09:01 -0700 Subject: [PATCH 48/51] Update tests with new calls. Note that there seem to be problems with rotateLOM on some operator tests. --- SimPEG/tests/test_massMatrices.py | 68 +++++++------------ SimPEG/tests/test_operators.py | 106 ++++++++++++++++-------------- 2 files changed, 79 insertions(+), 95 deletions(-) diff --git a/SimPEG/tests/test_massMatrices.py b/SimPEG/tests/test_massMatrices.py index 07d67e9c..13037156 100644 --- a/SimPEG/tests/test_massMatrices.py +++ b/SimPEG/tests/test_massMatrices.py @@ -64,33 +64,21 @@ class TestInnerProducts(OrderTest): analytic = 69881./21600 # Found using matlab symbolic toolbox. if self.location == 'edges': - if self.M._meshType == 'TENSOR': - Ex = call(ex, self.M.gridEx) - Ey = call(ey, self.M.gridEy) - Ez = call(ez, self.M.gridEz) - E = np.matrix(np.r_[Ex, Ey, Ez]).T - elif self.M._meshType == 'LOM': - cart = lambda g: np.c_[call(ex, g), call(ey, g), call(ez, g)] - Ec = np.vstack((cart(self.M.gridEx), - cart(self.M.gridEy), - cart(self.M.gridEz))) - E = np.matrix(self.M.projectEdgeVector(Ec)) + cart = lambda g: np.c_[call(ex, g), call(ey, g), call(ez, g)] + Ec = np.vstack((cart(self.M.gridEx), + cart(self.M.gridEy), + cart(self.M.gridEz))) + E = self.M.projectEdgeVector(Ec) A = self.M.getEdgeInnerProduct(sigma) - numeric = E.T*A*E + numeric = E.T.dot(A.dot(E)) elif self.location == 'faces': - if self.M._meshType == 'TENSOR': - Fx = call(ex, self.M.gridFx) - Fy = call(ey, self.M.gridFy) - Fz = call(ez, self.M.gridFz) - F = np.matrix(np.r_[Fx, Fy, Fz]).T - elif self.M._meshType == 'LOM': - cart = lambda g: np.c_[call(ex, g), call(ey, g), call(ez, g)] - Fc = np.vstack((cart(self.M.gridFx), - cart(self.M.gridFy), - cart(self.M.gridFz))) - F = np.matrix(self.M.projectFaceVector(Fc)) + cart = lambda g: np.c_[call(ex, g), call(ey, g), call(ez, g)] + Fc = np.vstack((cart(self.M.gridFx), + cart(self.M.gridFy), + cart(self.M.gridFz))) + F = self.M.projectFaceVector(Fc) A = self.M.getFaceInnerProduct(sigma) - numeric = F.T*A*F + numeric = F.T.dot(A.dot(F)) err = np.abs(numeric - analytic) return err @@ -164,31 +152,19 @@ class TestInnerProducts2D(OrderTest): analytic = 781427./360 # Found using matlab symbolic toolbox. z=5 if self.location == 'edges': - if self.M._meshType == 'TENSOR': - Ex = call(ex, self.M.gridEx) - Ey = call(ey, self.M.gridEy) - E = np.matrix(np.r_[Ex, Ey]).T - elif self.M._meshType == 'LOM': - cart = lambda g: np.c_[call(ex, g), call(ey, g)] - Ec = np.vstack((cart(self.M.gridEx), - cart(self.M.gridEy))) - E = np.matrix(self.M.projectEdgeVector(Ec)) - + cart = lambda g: np.c_[call(ex, g), call(ey, g)] + Ec = np.vstack((cart(self.M.gridEx), + cart(self.M.gridEy))) + E = self.M.projectEdgeVector(Ec) A = self.M.getEdgeInnerProduct(sigma) - numeric = E.T*A*E + numeric = E.T.dot(A.dot(E)) elif self.location == 'faces': - if self.M._meshType == 'TENSOR': - Fx = call(ex, self.M.gridFx) - Fy = call(ey, self.M.gridFy) - F = np.matrix(np.r_[Fx, Fy]).T - elif self.M._meshType == 'LOM': - cart = lambda g: np.c_[call(ex, g), call(ey, g)] - Fc = np.vstack((cart(self.M.gridFx), - cart(self.M.gridFy))) - F = np.matrix(self.M.projectFaceVector(Fc)) - + cart = lambda g: np.c_[call(ex, g), call(ey, g)] + Fc = np.vstack((cart(self.M.gridFx), + cart(self.M.gridFy))) + F = self.M.projectFaceVector(Fc) A = self.M.getFaceInnerProduct(sigma) - numeric = F.T*A*F + numeric = F.T.dot(A.dot(F)) err = np.abs(numeric - analytic) return err diff --git a/SimPEG/tests/test_operators.py b/SimPEG/tests/test_operators.py index 03ea36d1..315aedf6 100644 --- a/SimPEG/tests/test_operators.py +++ b/SimPEG/tests/test_operators.py @@ -4,7 +4,15 @@ import sys sys.path.append('../') from OrderTest import OrderTest -MESHTYPES = ['uniformTensorMesh', 'uniformLOM'] # , 'rotateLOM' +MESHTYPES = ['uniformTensorMesh', 'uniformLOM', 'rotateLOM'] +call2 = lambda fun, xyz: fun(xyz[:, 0], xyz[:, 1]) +call3 = lambda fun, xyz: fun(xyz[:, 0], xyz[:, 1], xyz[:, 2]) +cart_row2 = lambda g, xfun, yfun: np.c_[call2(xfun, g), call2(yfun, g)] +cart_row3 = lambda g, xfun, yfun, zfun: np.c_[call3(xfun, g), call3(yfun, g), call3(zfun, g)] +cartF2 = lambda M, fx, fy: np.vstack((cart_row2(M.gridFx, fx, fy), cart_row2(M.gridFy, fx, fy))) +cartE2 = lambda M, ex, ey: np.vstack((cart_row2(M.gridEx, ex, ey), cart_row2(M.gridEy, ex, ey))) +cartF3 = lambda M, fx, fy, fz: np.vstack((cart_row3(M.gridFx, fx, fy, fz), cart_row3(M.gridFy, fx, fy, fz), cart_row3(M.gridFz, fx, fy, fz))) +cartE3 = lambda M, ex, ey, ez: np.vstack((cart_row3(M.gridEx, ex, ey, ez), cart_row3(M.gridEy, ex, ey, ez), cart_row3(M.gridEz, ex, ey, ez))) class TestCurl(OrderTest): @@ -12,24 +20,26 @@ class TestCurl(OrderTest): meshTypes = MESHTYPES def getError(self): - fun = lambda x: np.cos(x) # i (cos(y)) + j (cos(z)) + k (cos(x)) - sol = lambda x: np.sin(x) # i (sin(z)) + j (sin(x)) + k (sin(y)) + # fun: i (cos(y)) + j (cos(z)) + k (cos(x)) + # sol: i (sin(z)) + j (sin(x)) + k (sin(y)) - Ex = fun(self.M.gridEx[:, 1]) - Ey = fun(self.M.gridEy[:, 2]) - Ez = fun(self.M.gridEz[:, 0]) - E = np.concatenate((Ex, Ey, Ez)) + funX = lambda x, y, z: np.cos(y) + funY = lambda x, y, z: np.cos(z) + funZ = lambda x, y, z: np.cos(x) - Fx = sol(self.M.gridFx[:, 2]) - Fy = sol(self.M.gridFy[:, 0]) - Fz = sol(self.M.gridFz[:, 1]) - curlE_anal = np.concatenate((Fx, Fy, Fz)) + solX = lambda x, y, z: np.sin(z) + solY = lambda x, y, z: np.sin(x) + solZ = lambda x, y, z: np.sin(y) + + Ec = cartE3(self.M, funX, funY, funZ) + E = self.M.projectEdgeVector(Ec) + + Fc = cartF3(self.M, solX, solY, solZ) + curlE_anal = self.M.projectFaceVector(Fc) # Generate DIV matrix - CURL = self.M.edgeCurl - - curlE = CURL*E - err = np.linalg.norm((curlE-curlE_anal), np.inf) + curlE = self.M.edgeCurl.dot(E) + err = np.linalg.norm((curlE - curlE_anal), np.inf) return err def test_order(self): @@ -41,18 +51,17 @@ class TestFaceDiv(OrderTest): meshTypes = MESHTYPES def getError(self): - DIV = self.M.faceDiv - #Test function - fun = lambda x: np.sin(x) - Fx = fun(self.M.gridFx[:, 0]) - Fy = fun(self.M.gridFy[:, 1]) - Fz = fun(self.M.gridFz[:, 2]) - - F = np.concatenate((Fx, Fy, Fz)) - divF = DIV*F + fx = lambda x, y, z: np.sin(x) + fy = lambda x, y, z: np.sin(y) + fz = lambda x, y, z: np.sin(z) sol = lambda x, y, z: (np.cos(x)+np.cos(y)+np.cos(z)) - divF_anal = sol(self.M.gridCC[:, 0], self.M.gridCC[:, 1], self.M.gridCC[:, 2]) + + Fc = cartF3(self.M, fx, fy, fz) + F = self.M.projectFaceVector(Fc) + + divF = self.M.faceDiv.dot(F) + divF_anal = call3(sol, self.M.gridCC) err = np.linalg.norm((divF-divF_anal), np.inf) @@ -68,17 +77,16 @@ class TestFaceDiv2D(OrderTest): meshDimension = 2 def getError(self): - DIV = self.M.faceDiv - #Test function - fun = lambda x: np.sin(x) - Fx = fun(self.M.gridFx[:, 0]) - Fy = fun(self.M.gridFy[:, 1]) - - F = np.concatenate((Fx, Fy)) - divF = DIV*F + fx = lambda x, y: np.sin(x) + fy = lambda x, y: np.sin(y) sol = lambda x, y: (np.cos(x)+np.cos(y)) - divF_anal = sol(self.M.gridCC[:, 0], self.M.gridCC[:, 1]) + + Fc = cartF2(self.M, fx, fy) + F = self.M.projectFaceVector(Fc) + + divF = self.M.faceDiv.dot(F) + divF_anal = call2(sol, self.M.gridCC) err = np.linalg.norm((divF-divF_anal), np.inf) @@ -93,19 +101,19 @@ class TestNodalGrad(OrderTest): meshTypes = MESHTYPES def getError(self): - GRAD = self.M.nodalGrad #Test function fun = lambda x, y, z: (np.cos(x)+np.cos(y)+np.cos(z)) - sol = lambda x: -np.sin(x) # i (sin(x)) + j (sin(y)) + k (sin(z)) + # i (sin(x)) + j (sin(y)) + k (sin(z)) + solX = lambda x, y, z: -np.sin(x) + solY = lambda x, y, z: -np.sin(y) + solZ = lambda x, y, z: -np.sin(z) - phi = fun(self.M.gridN[:, 0], self.M.gridN[:, 1], self.M.gridN[:, 2]) - gradE = GRAD*phi + phi = call3(fun, self.M.gridN) + gradE = self.M.nodalGrad.dot(phi) - Ex = sol(self.M.gridEx[:, 0]) - Ey = sol(self.M.gridEy[:, 1]) - Ez = sol(self.M.gridEz[:, 2]) + Ec = cartE3(self.M, solX, solY, solZ) + gradE_anal = self.M.projectEdgeVector(Ec) - gradE_anal = np.concatenate((Ex, Ey, Ez)) err = np.linalg.norm((gradE-gradE_anal), np.inf) return err @@ -120,18 +128,18 @@ class TestNodalGrad2D(OrderTest): meshDimension = 2 def getError(self): - GRAD = self.M.nodalGrad #Test function fun = lambda x, y: (np.cos(x)+np.cos(y)) - sol = lambda x: -np.sin(x) # i (sin(x)) + j (sin(y)) + k (sin(z)) + # i (sin(x)) + j (sin(y)) + k (sin(z)) + solX = lambda x, y: -np.sin(x) + solY = lambda x, y: -np.sin(y) - phi = fun(self.M.gridN[:, 0], self.M.gridN[:, 1]) - gradE = GRAD*phi + phi = call2(fun, self.M.gridN) + gradE = self.M.nodalGrad.dot(phi) - Ex = sol(self.M.gridEx[:, 0]) - Ey = sol(self.M.gridEy[:, 1]) + Ec = cartE2(self.M, solX, solY) + gradE_anal = self.M.projectEdgeVector(Ec) - gradE_anal = np.concatenate((Ex, Ey)) err = np.linalg.norm((gradE-gradE_anal), np.inf) return err From 43d2512aea477559c52b8fa651b22214cd46e50b Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Tue, 6 Aug 2013 17:18:04 -0700 Subject: [PATCH 49/51] Updated the test_operators code to work for LOM. need to be careful in which error metric you consider (because some only make sense under integration) i.e. the l-2 norm rather than the infinity norm, and you need to multiply by the integration length/area/volume it you are looking at the l-2 norm. --- SimPEG/tests/OrderTest.py | 18 +++++++++++--- SimPEG/tests/test_operators.py | 44 +++++++++++++++++++++------------- 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/SimPEG/tests/OrderTest.py b/SimPEG/tests/OrderTest.py index 238c3216..c522eb34 100644 --- a/SimPEG/tests/OrderTest.py +++ b/SimPEG/tests/OrderTest.py @@ -62,7 +62,8 @@ class OrderTest(unittest.TestCase): """ name = "Order Test" - expectedOrder = 2 + expectedOrders = 2. # This can be a list of orders, must be the same length as meshTypes + _expectedOrder = 2. tolerance = 0.85 meshSizes = [4, 8, 16, 32] meshTypes = ['uniformTensorMesh'] @@ -118,8 +119,19 @@ class OrderTest(unittest.TestCase): """ - for meshType in self.meshTypes: + assert type(self.meshTypes) == list, 'meshTypes must be a list' + + # if we just provide one expected order, repeat it for each mesh type + if type(self.expectedOrders) == float or type(self.expectedOrders) == int: + self.expectedOrders = [self.expectedOrders for i in self.meshTypes] + + assert type(self.expectedOrders) == list, 'expectedOrders must be a list' + assert len(self.expectedOrders) == len(self.meshTypes), 'expectedOrders must have the same length as the meshTypes' + + for ii_meshType, meshType in enumerate(self.meshTypes): self._meshType = meshType + self._expectedOrder = self.expectedOrders[ii_meshType] + order = [] err_old = 0. max_h_old = 0. @@ -139,7 +151,7 @@ class OrderTest(unittest.TestCase): err_old = err max_h_old = max_h print '---------------------------------------------' - passTest = np.mean(np.array(order)) > self.tolerance*self.expectedOrder + passTest = np.mean(np.array(order)) > self.tolerance*self._expectedOrder if passTest: print ['The test be workin!', 'You get a gold star!', 'Yay passed!', 'Happy little convergence test!', 'That was easy!'][np.random.randint(5)] else: diff --git a/SimPEG/tests/test_operators.py b/SimPEG/tests/test_operators.py index 315aedf6..38d289c3 100644 --- a/SimPEG/tests/test_operators.py +++ b/SimPEG/tests/test_operators.py @@ -23,13 +23,13 @@ class TestCurl(OrderTest): # fun: i (cos(y)) + j (cos(z)) + k (cos(x)) # sol: i (sin(z)) + j (sin(x)) + k (sin(y)) - funX = lambda x, y, z: np.cos(y) - funY = lambda x, y, z: np.cos(z) - funZ = lambda x, y, z: np.cos(x) + funX = lambda x, y, z: np.cos(2*np.pi*y) + funY = lambda x, y, z: np.cos(2*np.pi*z) + funZ = lambda x, y, z: np.cos(2*np.pi*x) - solX = lambda x, y, z: np.sin(z) - solY = lambda x, y, z: np.sin(x) - solZ = lambda x, y, z: np.sin(y) + solX = lambda x, y, z: 2*np.pi*np.sin(2*np.pi*z) + solY = lambda x, y, z: 2*np.pi*np.sin(2*np.pi*x) + solZ = lambda x, y, z: 2*np.pi*np.sin(2*np.pi*y) Ec = cartE3(self.M, funX, funY, funZ) E = self.M.projectEdgeVector(Ec) @@ -37,9 +37,13 @@ class TestCurl(OrderTest): Fc = cartF3(self.M, solX, solY, solZ) curlE_anal = self.M.projectFaceVector(Fc) - # Generate DIV matrix curlE = self.M.edgeCurl.dot(E) - err = np.linalg.norm((curlE - curlE_anal), np.inf) + if self._meshType == 'rotateLOM': + # Really it is the integration we should be caring about: + # So, let us look at the l2 norm. + err = np.linalg.norm(self.M.area*(curlE - curlE_anal), 2) + else: + err = np.linalg.norm((curlE - curlE_anal), np.inf) return err def test_order(self): @@ -49,13 +53,14 @@ class TestCurl(OrderTest): class TestFaceDiv(OrderTest): name = "Face Divergence" meshTypes = MESHTYPES + meshSizes = [8, 16, 32] def getError(self): #Test function - fx = lambda x, y, z: np.sin(x) - fy = lambda x, y, z: np.sin(y) - fz = lambda x, y, z: np.sin(z) - sol = lambda x, y, z: (np.cos(x)+np.cos(y)+np.cos(z)) + fx = lambda x, y, z: np.sin(2*np.pi*x) + fy = lambda x, y, z: np.sin(2*np.pi*y) + fz = lambda x, y, z: np.sin(2*np.pi*z) + sol = lambda x, y, z: (2*np.pi*np.cos(2*np.pi*x)+2*np.pi*np.cos(2*np.pi*y)+2*np.pi*np.cos(2*np.pi*z)) Fc = cartF3(self.M, fx, fy, fz) F = self.M.projectFaceVector(Fc) @@ -63,8 +68,12 @@ class TestFaceDiv(OrderTest): divF = self.M.faceDiv.dot(F) divF_anal = call3(sol, self.M.gridCC) - err = np.linalg.norm((divF-divF_anal), np.inf) - + if self._meshType == 'rotateLOM': + # Really it is the integration we should be caring about: + # So, let us look at the l2 norm. + err = np.linalg.norm(self.M.vol*(divF-divF_anal), 2) + else: + err = np.linalg.norm((divF-divF_anal), np.inf) return err def test_order(self): @@ -75,12 +84,13 @@ class TestFaceDiv2D(OrderTest): name = "Face Divergence 2D" meshTypes = MESHTYPES meshDimension = 2 + meshSizes = [8, 16, 32, 64] def getError(self): #Test function - fx = lambda x, y: np.sin(x) - fy = lambda x, y: np.sin(y) - sol = lambda x, y: (np.cos(x)+np.cos(y)) + fx = lambda x, y: np.sin(2*np.pi*x) + fy = lambda x, y: np.sin(2*np.pi*y) + sol = lambda x, y: 2*np.pi*(np.cos(2*np.pi*x)+np.cos(2*np.pi*y)) Fc = cartF2(self.M, fx, fy) F = self.M.projectFaceVector(Fc) From 7e5ea835e321c664db45ca22aa436c41a0e54f59 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Tue, 6 Aug 2013 17:56:21 -0700 Subject: [PATCH 50/51] Removed eldadCode directory as it is now integrated. Added GaussNewton to the main directory. --- SimPEG/EldadsCode/MFDdriver.py | 36 ---- SimPEG/EldadsCode/getCellVolume.py | 58 ------ SimPEG/EldadsCode/getDiffOps.py | 106 ----------- SimPEG/EldadsCode/getEdgeInnerProduct.py | 213 ---------------------- SimPEG/EldadsCode/getEdgeTangent.py | 60 ------ SimPEG/EldadsCode/getFaceInnerProduct.py | 86 --------- SimPEG/EldadsCode/getFaceNormals.py | 73 -------- SimPEG/EldadsCode/getVolume.py | 34 ---- SimPEG/EldadsCode/inv3X3BlockDiagonal.py | 36 ---- SimPEG/EldadsCode/meshUtils.py | 94 ---------- SimPEG/EldadsCode/sputils.py | 77 -------- SimPEG/EldadsCode/tools.py | 222 ----------------------- SimPEG/EldadsCode/utils.py | 87 --------- SimPEG/EldadsCode/zevel.py | 119 ------------ SimPEG/{EldadsCode => }/GaussNewton.py | 0 15 files changed, 1301 deletions(-) delete mode 100644 SimPEG/EldadsCode/MFDdriver.py delete mode 100644 SimPEG/EldadsCode/getCellVolume.py delete mode 100644 SimPEG/EldadsCode/getDiffOps.py delete mode 100644 SimPEG/EldadsCode/getEdgeInnerProduct.py delete mode 100644 SimPEG/EldadsCode/getEdgeTangent.py delete mode 100644 SimPEG/EldadsCode/getFaceInnerProduct.py delete mode 100644 SimPEG/EldadsCode/getFaceNormals.py delete mode 100644 SimPEG/EldadsCode/getVolume.py delete mode 100644 SimPEG/EldadsCode/inv3X3BlockDiagonal.py delete mode 100644 SimPEG/EldadsCode/meshUtils.py delete mode 100644 SimPEG/EldadsCode/sputils.py delete mode 100644 SimPEG/EldadsCode/tools.py delete mode 100644 SimPEG/EldadsCode/utils.py delete mode 100644 SimPEG/EldadsCode/zevel.py rename SimPEG/{EldadsCode => }/GaussNewton.py (100%) diff --git a/SimPEG/EldadsCode/MFDdriver.py b/SimPEG/EldadsCode/MFDdriver.py deleted file mode 100644 index 16d18fe3..00000000 --- a/SimPEG/EldadsCode/MFDdriver.py +++ /dev/null @@ -1,36 +0,0 @@ -import numpy as np -from numpy.random import randn -from utils import ndgrid -from getDiffOps import getCurlMatrix, getNodalGradient -from getFaceInnerProduct import getFaceInnerProduct -from getEdgeInnerProduct import getEdgeInnerProduct -from scipy.sparse.linalg import dsolve -from pylab import norm - -n = np.array([14, 14, 15]) - -X, Y, Z = ndgrid(*[np.linspace(0, 1, x) for x in n]) -sigma = 1e-2*np.ones(n-1) -sigma[:, :, (n[2]-1)/2:] = 1e-6 -mu = 4*np.pi*1e-7*np.ones(n-1) -w = 10 - -CURL = getCurlMatrix(X, Y, Z) -GRAD = getNodalGradient(X, Y, Z) -Mf = getFaceInnerProduct(X, Y, Z, 1/mu) -Me = getEdgeInnerProduct(X, Y, Z, sigma) - -A = CURL.T * Mf * CURL + 1j * w * Me - -ne = np.shape(A) -b = np.matrix(randn(ne[0])).T -# clean b -DIVb = GRAD.T*b -p = dsolve.spsolve(GRAD.T*GRAD, DIVb, use_umfpack=True).T -b = b - GRAD*p - -#x = spsolve(A, b) -x = dsolve.spsolve(A, b, use_umfpack=True).T - -t = norm(A*x-b)/norm(b) -print t diff --git a/SimPEG/EldadsCode/getCellVolume.py b/SimPEG/EldadsCode/getCellVolume.py deleted file mode 100644 index bb536842..00000000 --- a/SimPEG/EldadsCode/getCellVolume.py +++ /dev/null @@ -1,58 +0,0 @@ -from sputils import * -from utils import * -from sputils import * -from numpy import * -from getEdgeTangent import * - - -def volTetra(y, m, I, A, B, C, D): - - a11 = array(y[A, 0]-y[B, 0]); a12 = array(y[A, 0]-y[C, 0]); a13 = array(y[A, 0]-y[D, 0]) - a21 = array(y[A, 1]-y[B, 1]); a22 = array(y[A, 1]-y[C, 1]); a23 = array(y[A, 1]-y[D, 1]) - a31 = array(y[A, 2]-y[B, 2]); a32 = array(y[A, 2]-y[C, 2]); a33 = array(y[A, 2]-y[D, 2]) - - return abs(a11*a22*a33 + a12*a23*a31 + a13*a21*a32 - a31*a22*a13 - a32*a23*a11 - a33*a21*a12) - - -def getCellVolume(X, Y, Z): - - m = array(shape(X))-1 - y = hstack3(mkvc(X), mkvc(Y), mkvc(Z)) - - i = int64(linspace(0, m[0]-1, m[0])) - j = int64(linspace(0, m[1]-1, m[1])) - k = int64(linspace(0, m[2]-1, m[2])) - - ii, jj, kk = ndgrid(i, j, k) - ii = mkvc(ii) - jj = mkvc(jj) - kk = mkvc(kk) - - I = int64(sub2ind(m, hstack3(ii, jj, kk))) - A = int64(sub2ind(m+1, hstack3(ii, jj, kk))) - B = int64(sub2ind(m+1, hstack3(ii, jj+1, kk))) - C = int64(sub2ind(m+1, hstack3(ii+1, jj+1, kk))) - D = int64(sub2ind(m+1, hstack3(ii+1, jj, kk))) - E = int64(sub2ind(m+1, hstack3(ii, jj, kk+1))) - F = int64(sub2ind(m+1, hstack3(ii, jj+1, kk+1))) - G = int64(sub2ind(m+1, hstack3(ii+1, jj+1, kk+1))) - H = int64(sub2ind(m+1, hstack3(ii+1, jj, kk+1))) - - v1 = volTetra(y, m, I, A, B, D, E) - v2 = volTetra(y, m, I, B, E, F, G) - v3 = volTetra(y, m, I, B, D, E, G) - v4 = volTetra(y, m, I, B, C, D, G) - v5 = volTetra(y, m, I, D, E, G, H) - - v = 1.0/6.0 * (v1 + v2 + v3 + v4 + v5) - return v.flatten() - - -if __name__ == '__main__': - - X, Y, Z = ndgrid(linspace(0, 2, 3), linspace(0, 2, 3), linspace(0, 2, 3)) - Z[2, 2, 2] = 2.5; Z[0, 0, 0] = -0.5 - X[2, 2, 2] = 2.5; X[0, 0, 0] = -0.5 - - v = getCellVolume(X, Y, Z) - print v diff --git a/SimPEG/EldadsCode/getDiffOps.py b/SimPEG/EldadsCode/getDiffOps.py deleted file mode 100644 index dbb4e5b0..00000000 --- a/SimPEG/EldadsCode/getDiffOps.py +++ /dev/null @@ -1,106 +0,0 @@ -from sputils import * -from utils import * -from numpy import * -from getEdgeTangent import * -from getCellVolume import getCellVolume -from getFaceNormals import getFaceNormals - - -def getDivMatrix(X, Y, Z): - """Face DIV""" - - n = array(shape(X))-1 - n1 = n[0] - n2 = n[1] - n3 = n[2] - - n1x, n1y, n1z, n2x, n2y, n2z, n3x, n3y, n3z, area1, area2, area3 = getFaceNormals(X, Y, Z) - - area = hstack((hstack((mkvc(area1), mkvc(area2))), mkvc(area3))) - S = sdiag(area) - V = getCellVolume(X, Y, Z) - - d1 = ddx(n1) - d2 = ddx(n2) - d3 = ddx(n3) - D1 = kron3(speye(n3), speye(n2), d1) - D2 = kron3(speye(n3), d2, speye(n1)) - D3 = kron3(d3, speye(n2), speye(n1)) - - # divergence on faces - D = appendRight3(D1, D2, D3) - - return sdiag(1/V)*D*S - - -def getCurlMatrix(X, Y, Z): - """Edge CURL """ - - n = array(shape(X))-1 - n1 = n[0]; n2 = n[1]; n3 = n[2] - - d1 = ddx(n1); d2 = ddx(n2); d3 = ddx(n3) - # derivatives on x-edge variables - D32 = kron3(d3, speye(n2), speye(n1+1)) - D23 = kron3(speye(n3), d2, speye(n1+1)) - D31 = kron3(d3, speye(n2+1), speye(n1)) - D13 = kron3(speye(n3), speye(n2+1), d1) - D21 = kron3(speye(n3+1), d2, speye(n1)) - D12 = kron3(speye(n3+1), speye(n2), d1) - - O1 = spzeros(shape(D32)[0], shape(D31)[1]) - O2 = spzeros(shape(D31)[0], shape(D32)[1]) - O3 = spzeros(shape(D21)[0], shape(D13)[1]) - - CURL = appendBottom3( - appendRight3(O1, -D32, D23), - appendRight3(D31, O2, -D13), - appendRight3(-D21, D12, O3)) - - # scale for non-uniform mesh - e1x, e1y, e1z, e2x, e2y, e2z, e3x, e3y, e3z, norme1, norme2, norme3 = getEdgeTangent(X, Y, Z) - n1x, n1y, n1z, n2x, n2y, n2z, n3x, n3y, n3z, area1, area2, area3 = getFaceNormals(X, Y, Z) - - area = hstack((hstack((mkvc(area1), mkvc(area2))), mkvc(area3))) - S = sdiag(1/area) - lngth = hstack((hstack((mkvc(norme1), mkvc(norme2))), mkvc(norme3))) - L = sdiag(lngth) - - return S*(CURL*L) - - -def getNodalGradient(X, Y, Z): - """Nodal Gradients""" - - n = array(shape(X))-1 - n1 = n[0]; n2 = n[1]; n3 = n[2] - - D1 = kron3(speye(n3+1), speye(n2+1), ddx(n1)) - D2 = kron3(speye(n3+1), ddx(n2), speye(n1+1)) - D3 = kron3(ddx(n3), speye(n2+1), speye(n1+1)) - - # topological gradient - GRAD = appendBottom3(D1, D2, D3) - - # scale for non-uniform mesh - e1x, e1y, e1z, e2x, e2y, e2z, e3x, e3y, e3z, norme1, norme2, norme3 = getEdgeTangent(X, Y, Z) - lngth = hstack((hstack((mkvc(norme1), mkvc(norme2))), mkvc(norme3))) - L = sdiag(1/lngth) - - return L*GRAD - - -if __name__ == '__main__': - - X, Y, Z = ndgrid(linspace(0, 2, 3), linspace(0, 2, 3), linspace(0, 2, 3)) - Z[2, 2, 2] = 2.5 - Z[0, 0, 0] = -0.5 - X[2, 2, 2] = 2.5 - X[0, 0, 0] = -0.5 - sig = ones([2, 2, 2]) - C = getCurlMatrix(X, Y, Z) - - G = getNodalGradient(X, Y, Z) - - tt = C*G - print(tt) diff --git a/SimPEG/EldadsCode/getEdgeInnerProduct.py b/SimPEG/EldadsCode/getEdgeInnerProduct.py deleted file mode 100644 index 776f31f7..00000000 --- a/SimPEG/EldadsCode/getEdgeInnerProduct.py +++ /dev/null @@ -1,213 +0,0 @@ -from scipy.sparse import linalg -from scipy import sparse -from sputils import * -from utils import * -from sputils import * -from numpy import * -from getEdgeTangent import * -from inv3X3BlockDiagonal import * -from getCellVolume import getCellVolume - - -def subarray(T, i1, i2, i3): - return take(take(take(T, i1, 0), i2, 1), i3, 2) - - -def getEdgeInnerProduct(X, Y, Z, sigma): - """A = getEdgeInnerProduct(X, Y, Z, sigma) - - - node(i,j,k+1) ------ edge2(i,j,k+1) ----- node(i,j+1,k+1) - / / - / / | - edge3(i,j,k) face1(i,j,k) edge3(i,j+1,k) - / / | - / / | - node(i,j,k) ------ edge2(i,j,k) ----- node(i,j+1,k) - | | | - | | node(i+1,j+1,k+1) - | | / - edge1(i,j,k) face3(i,j,k) edge1(i,j+1,k) - | | / - | | / - | |/ - node(i+1,j,k) ------ edge2(i+1,j,k) ----- node(i+1,j+1,k) - - no | node | e1 | e2 | e3 - 000 | i ,j ,k | i ,j ,k | i ,j ,k | i ,j ,k - 100 | i+1,j ,k | i ,j ,k | i+1,j ,k | i+1,j ,k - 010 | i ,j+1,k | i ,j+1,k | i ,j ,k | i ,j+1,k - 110 | i+1,j+1,k | i ,j+1,k | i+1,j ,k | i+1,j+1,k - 001 | i ,j ,k+1 | i ,j ,k+1 | i ,j ,k+1 | i ,j ,k - 101 | i+1,j ,k+1 | i ,j ,k+1 | i+1,j ,k+1 | i+1,j ,k - 011 | i ,j+1,k+1 | i ,j+1,k+1 | i ,j ,k+1 | i ,j+1,k - 111 | i+1,j+1,k+1 | i ,j+1,k+1 | i+1,j ,k+1 | i+1,j+1,k - """ - m = array(shape(X))-1 - nc = prod(m) - - me1 = m + array([0, 1, 1]); ne1 = prod(me1) - me2 = m + array([1, 0, 1]); ne2 = prod(me2) - me3 = m + array([1, 1, 0]); ne3 = prod(me3) - - e1x,e1y,e1z,e2x,e2y,e2z,e3x,e3y,e3z,norme1,norme2,norme3 = getEdgeTangent(X,Y,Z) - - i = int64(linspace(0,m[0]-1,m[0])) - j = int64(linspace(0,m[1]-1,m[1])) - k = int64(linspace(0,m[2]-1,m[2])) - - ii,jj,kk = ndgrid(i,j,k) - ii = mkvc(ii); jj = mkvc(jj); kk = mkvc(kk) - - ## -------- - # no | node | e1 | e2 | e3 - # 000 | i ,j ,k | i ,j ,k | i ,j ,k | i ,j ,k - ind1 = sub2ind(me1,hstack3(ii,jj,kk)) - ind2 = sub2ind(me2,hstack3(ii,jj,kk)) + ne1 - ind3 = sub2ind(me3,hstack3(ii,jj,kk)) + ne1 + ne2 - - IND = vstack((vstack((ind1,ind2)),ind3)) - IND = array(IND).flatten() - - P000 = sparse.coo_matrix((ones(3*nc),(linspace(0,3*nc-1,3*nc),IND)),shape=(3*nc,ne1+ne2+ne3)).tocsr() - - invT000 = inv3X3BlockDiagonal(subarray(e1x,i,j,k) , subarray(e1y,i,j,k), subarray(e1z,i,j,k), - subarray(e2x,i,j,k) , subarray(e2y,i,j,k), subarray(e2z,i,j,k) , - subarray(e3x,i,j,k) , subarray(e3y,i,j,k), subarray(e3z,i,j,k) ) - - ## -------- - # no | node | e1 | e2 | e3 - # 100 | i+1,j ,k | i ,j ,k | i+1,j ,k | i+1,j ,k - ind1 = sub2ind(me1,hstack3(ii,jj,kk)) - ind2 = sub2ind(me2,hstack3(ii+1,jj,kk)) + ne1 - ind3 = sub2ind(me3,hstack3(ii+1,jj,kk)) + ne1 + ne2 - - IND = vstack((vstack((ind1,ind2)),ind3)) - IND = array(IND).flatten() - - P100 = sparse.coo_matrix((ones(3*nc),(linspace(0,3*nc-1,3*nc),IND)),shape=(3*nc,ne1+ne2+ne3)).tocsr() - - invT100 = inv3X3BlockDiagonal(subarray(e1x,i,j,k), subarray(e1y,i,j,k), subarray(e1z,i,j,k), - subarray(e2x,i+1,j,k), subarray(e2y,i+1,j,k), subarray(e2z,i+1,j,k), - subarray(e3x,i+1,j,k) , subarray(e3y,i+1,j,k), subarray(e3z,i+1,j,k)) - - ## -------- - # no | node | e1 | e2 | e3 - # 010 | i ,j+1,k | i ,j+1,k | i ,j ,k | i ,j+1,k - ind1 = sub2ind(me1,hstack3(ii,jj+1,kk)) - ind2 = sub2ind(me2,hstack3(ii,jj,kk)) + ne1 - ind3 = sub2ind(me3,hstack3(ii,jj+1,kk)) + ne1 + ne2 - - IND = vstack((vstack((ind1,ind2)),ind3)) - IND = array(IND).flatten() - - P010 = sparse.coo_matrix((ones(3*nc),(linspace(0,3*nc-1,3*nc),IND)),shape=(3*nc,ne1+ne2+ne3)).tocsr() - - invT010 = inv3X3BlockDiagonal(subarray(e1x,i,j+1,k) , subarray(e1y,i,j+1,k) , subarray(e1z,i,j+1,k) , - subarray(e2x,i,j,k) , subarray(e2y,i,j,k) , subarray(e2z,i,j,k) , - subarray(e3x,i,j+1,k) , subarray(e3y,i,j+1,k) ,subarray( e3z,i,j+1,k) ) - - ## -------- - # no | node | e1 | e2 | e3 - # 110 | i+1,j+1,k | i ,j+1,k | i+1,j ,k | i+1,j+1,k - ind1 = sub2ind(me1,hstack3(ii,jj+1,kk)) - ind2 = sub2ind(me2,hstack3(ii+1,jj,kk)) + ne1 - ind3 = sub2ind(me3,hstack3(ii+1,jj+1,kk)) + ne1 + ne2 - - IND = vstack((vstack((ind1,ind2)),ind3)) - IND = array(IND).flatten() - - P110 = sparse.coo_matrix((ones(3*nc),(linspace(0,3*nc-1,3*nc),IND)),shape=(3*nc,ne1+ne2+ne3)).tocsr() - - invT110 = inv3X3BlockDiagonal(subarray(e1x,i,j+1,k) ,subarray(e1y,i,j+1,k) , subarray(e1z,i,j+1,k) , - subarray(e2x,i+1,j,k) ,subarray(e2y,i+1,j,k) , subarray(e2z,i+1,j,k), - subarray(e3x,i+1,j+1,k) ,subarray(e3y,i+1,j+1,k) , subarray(e3z,i+1,j+1,k) ) - - ###### - - ## -------- - # no | node | e1 | e2 | e3 - # 001 | i ,j ,k+1 | i ,j ,k+1 | i ,j ,k+1 | i ,j ,k - ind1 = sub2ind(me1,hstack3(ii,jj,kk+1)) - ind2 = sub2ind(me2,hstack3(ii,jj,kk+1)) + ne1 - ind3 = sub2ind(me3,hstack3(ii,jj,kk)) + ne1 + ne2 - - IND = vstack((vstack((ind1,ind2)),ind3)) - IND = array(IND).flatten() - - P001 = sparse.coo_matrix((ones(3*nc),(linspace(0,3*nc-1,3*nc),IND)),shape=(3*nc,ne1+ne2+ne3)).tocsr() - - invT001 = inv3X3BlockDiagonal(subarray(e1x,i,j,k+1) ,subarray(e1y,i,j,k+1) , subarray(e1z,i,j,k+1) , - subarray(e2x,i,j,k+1) , subarray(e2y,i,j,k+1) , subarray(e2z,i,j,k+1) , - subarray(e3x,i,j,k) , subarray(e3y,i,j,k) , subarray(e3z,i,j,k) ) - - ## -------- - # no | node | e1 | e2 | e3 - # 101 | i+1,j ,k+1 | i ,j ,k+1 | i+1,j ,k+1 | i+1,j ,k+1 - ind1 = sub2ind(me1,hstack3(ii,jj,kk+1)) - ind2 = sub2ind(me2,hstack3(ii+1,jj,kk+1)) + ne1 - ind3 = sub2ind(me3,hstack3(ii+1,jj,kk)) + ne1 + ne2 - - IND = vstack((vstack((ind1,ind2)),ind3)) - IND = array(IND).flatten() - - P101 = sparse.coo_matrix((ones(3*nc),(linspace(0,3*nc-1,3*nc),IND)),shape=(3*nc,ne1+ne2+ne3)).tocsr() - - invT101 = inv3X3BlockDiagonal(subarray(e1x,i,j,k+1), subarray(e1y,i,j,k+1), subarray(e1z,i,j,k+1) , - subarray(e2x,i+1,j,k+1), subarray(e2y,i+1,j,k+1) , subarray(e2z,i+1,j,k+1) , - subarray(e3x,i+1,j,k), subarray(e3y,i+1,j,k) , subarray(e3z,i+1,j,k) ) - - ## -------- - # no | node | e1 | e2 | e3 - # 011 | i ,j+1,k+1 | i ,j+1,k+1 | i ,j ,k+1 | i ,j+1,k+1 - ind1 = sub2ind(me1,hstack3(ii,jj+1,kk+1)) - ind2 = sub2ind(me2,hstack3(ii,jj,kk+1)) + ne1 - ind3 = sub2ind(me3,hstack3(ii,jj+1,kk)) + ne1 + ne2 - - IND = vstack((vstack((ind1,ind2)),ind3)) - IND = array(IND).flatten() - - P011 = sparse.coo_matrix((ones(3*nc),(linspace(0,3*nc-1,3*nc),IND)),shape=(3*nc,ne1+ne2+ne3)).tocsr() - - invT011 = inv3X3BlockDiagonal(subarray(e1x,i,j+1,k+1) , subarray(e1y,i,j+1,k+1) , subarray(e1z,i,j+1,k+1) , - subarray(e2x,i,j,k+1) , subarray(e2y,i,j,k+1) , subarray(e2z,i,j,k+1) , - subarray(e3x,i,j+1,k) , subarray(e3y,i,j+1,k) , subarray(e3z,i,j+1,k) ) - - ## -------- - # no | node | e1 | e2 | e3 - # 111 | i+1,j+1,k+1 | i ,j+1,k+1 | i+1,j ,k+1 | i+1,j+1,k+1 - ind1 = sub2ind(me1,hstack3(ii,jj+1,kk+1)) - ind2 = sub2ind(me2,hstack3(ii+1,jj,kk+1)) + ne1 - ind3 = sub2ind(me3,hstack3(ii+1,jj+1,kk)) + ne1 + ne2 - - IND = vstack((vstack((ind1,ind2)),ind3)) - IND = array(IND).flatten() - - P111 = sparse.coo_matrix((ones(3*nc),(linspace(0,3*nc-1,3*nc),IND)),shape=(3*nc,ne1+ne2+ne3)).tocsr() - - invT111 = inv3X3BlockDiagonal(subarray(e1x,i,j+1,k+1) , subarray(e1y,i,j+1,k+1) , subarray(e1z,i,j+1,k+1) , - subarray(e2x,i+1,j,k+1) , subarray(e2y,i+1,j,k+1) , subarray(e2z,i+1,j,k+1) , - subarray(e3x,i+1,j+1,k) , subarray(e3y,i+1,j+1,k) , subarray(e3z,i+1,j+1,k) ) - - # Cell volume - v = mkvc(getCellVolume(X,Y,Z)) #mkvc(getVolume(X,Y,Z)) - vsig = v*mkvc(sigma) - v3 = vstack((vstack((vsig,vsig)),vsig)) - v3 = v3.flatten() - - V = sdiag(v3) - - A = P000.T*invT000.T*V*invT000*P000 + P001.T*invT001.T*V*invT001*P001 + P010.T*invT010.T*V*invT010*P010 + P011.T*invT011.T*V*invT011*P011 + P100.T*invT100.T*V*invT100*P100 + P101.T*invT101.T*V*invT101*P101 + P110.T*invT110.T*V*invT110*P110 + P111.T*invT111.T*V*invT111*P111 - - A = 0.125*A - - return A - - -if __name__ == '__main__': - - X,Y,Z = ndgrid(linspace(0,2,3),linspace(0,2,3),linspace(0,2,3)) - Z[2,2,2] = 2.5; Z[0,0,0] = -0.5 - X[2,2,2] = 2.5; X[0,0,0] = -0.5 - sig = ones([2,2,2]) - A = getEdgeInnerProduct(X,Y,Z,sig) \ No newline at end of file diff --git a/SimPEG/EldadsCode/getEdgeTangent.py b/SimPEG/EldadsCode/getEdgeTangent.py deleted file mode 100644 index a8426df3..00000000 --- a/SimPEG/EldadsCode/getEdgeTangent.py +++ /dev/null @@ -1,60 +0,0 @@ -from numpy import * -from utils import diff - -#function[t1x,t1y,t1z,t2x,t2y,t2z,t3x,t3y,t3z,normt1,normt2,normt3] = getEdgeTangent(X,Y,Z) -#%[t1x,t1y,t1z,t2x,t2y,t2z,t3x,t3y,t3z,normt1,normt2,normt3] = getEdgeTangent(X,Y,Z) -#% -#% node(i,j,k+1) ------ edgt2(i,j,k+1) ----- node(i,j+1,k+1) -#% / / -#% / / | -#% edgt3(i,j,k) fact1(i,j,k) edgt3(i,j+1,k) -#% / / | -#% / / | -#% node(i,j,k) ------ edgt2(i,j,k) ----- node(i,j+1,k) -#% | | | -#% | | node(i+1,j+1,k+1) -#% | | / -#% edgt1(i,j,k) fact3(i,j,k) edgt1(i,j+1.k) -#% | | / -#% | | / -#% | |/ -#% node(i+1,j,k) ------ edgt2(i+1,j,k) ----- node(i+1,j+1,k) - - -def getEdgeTangent(X, Y, Z): - - t1x = diff(X, 1) - t1y = diff(Y, 1) - t1z = diff(Z, 1) - - normt1 = sqrt(t1x**2+t1y**2+t1z**2) - t1x = t1x/normt1 - t1y = t1y/normt1 - t1z = t1z/normt1 - - t2x = diff(X, 2) - t2y = diff(Y, 2) - t2z = diff(Z, 2) - normt2 = sqrt(t2x**2 + t2y**2 + t2z**2) - t2x = t2x/normt2 - t2y = t2y/normt2 - t2z = t2z/normt2 - - t3x = diff(X, 3) - t3y = diff(Y, 3) - t3z = diff(Z, 3) - normt3 = sqrt(t3x**2+t3y**2+t3z**2) - t3x = t3x/normt3 - t3y = t3y/normt3 - t3z = t3z/normt3 - - # print t3x - - return (t1x, t1y, t1z, t2x, t2y, t2z, t3x, t3y, t3z, normt1, normt2, normt3) - - -if __name__ == '__main__': - - X, Y, Z = mgrid[0:4, 0:5, 0:6] - - t = getEdgeTangent(X, Y, Z) diff --git a/SimPEG/EldadsCode/getFaceInnerProduct.py b/SimPEG/EldadsCode/getFaceInnerProduct.py deleted file mode 100644 index 2870eb88..00000000 --- a/SimPEG/EldadsCode/getFaceInnerProduct.py +++ /dev/null @@ -1,86 +0,0 @@ -from scipy.sparse import linalg -from scipy import sparse -from sputils import * -from utils import * -from numpy import * -from getEdgeTangent import * -from inv3X3BlockDiagonal import * -from getCellVolume import getCellVolume -from getFaceNormals import getFaceNormals - - -#----------------------- -def subarray(T,i1,i2,i3): - return take(take(take(T,i1,0),i2,1),i3,2) - -#----------------------- - -def getFaceInnerProduct(X,Y,Z,sigma): - - m = array(shape(X))-1 - nc = prod(m) - mf1 = m+[1, 0, 0] - mf2 = m+[0, 1, 0] - mf3 = m+[0, 0, 1] - - nf1 = prod(m+[1, 0, 0]) - nf2 = prod(m+[0, 1, 0]) - nf3 = prod(m+[0, 0, 1]) - - # compute the normals - n1x,n1y,n1z,n2x,n2y,n2z,n3x,n3y,n3z,area1,area2,area3 = getFaceNormals(X,Y,Z) - - i = int64(linspace(0,m[0]-1,m[0])) - j = int64(linspace(0,m[1]-1,m[1])) - k = int64(linspace(0,m[2]-1,m[2])) - - ii,jj,kk = ndgrid(i,j,k) - ii = mkvc(ii); jj = mkvc(jj); kk = mkvc(kk) - - ind1 = sub2ind(mf1,hstack3(ii,jj,kk)) - ind2 = sub2ind(mf2,hstack3(ii,jj,kk)) + nf1 - ind3 = sub2ind(mf3,hstack3(ii,jj,kk)) + nf1 + nf2 - - IND = vstack((vstack((ind1,ind2)),ind3)) - IND = array(IND).flatten() - - P1 = sparse.coo_matrix((ones(3*nc),(linspace(0,3*nc-1,3*nc),IND)),shape=(3*nc,nf1+nf2+nf3)).tocsr() - - ind1 = sub2ind(mf1,hstack3(ii+1,jj,kk)) - ind2 = sub2ind(mf2,hstack3(ii,jj+1,kk)) + nf1 - ind3 = sub2ind(mf3,hstack3(ii,jj,kk+1)) + nf1 + nf2 - - IND = vstack((vstack((ind1,ind2)),ind3)) - IND = array(IND).flatten() - - P2 = sparse.coo_matrix((ones(3*nc),(linspace(0,3*nc-1,3*nc),IND)),shape=(3*nc,nf1+nf2+nf3)).tocsr() - - - invN1 = inv3X3BlockDiagonal(subarray(n1x,i,j,k) , subarray(n1y,i,j,k), subarray(n1z,i,j,k), - subarray(n2x,i,j,k) , subarray(n2y,i,j,k), subarray(n2z,i,j,k), - subarray(n3x,i,j,k) , subarray(n3y,i,j,k), subarray(n3z,i,j,k) ) - - - invN2 = inv3X3BlockDiagonal(subarray(n1x,i+1,j,k) , subarray(n1y,i+1,j,k), subarray(n1z,i+1,j,k), - subarray(n2x,i,j+1,k) , subarray(n2y,i,j+1,k), subarray(n2z,i,j+1,k), - subarray(n3x,i,j,k+1) , subarray(n3y,i,j,k+1), subarray(n3z,i,j,k+1) ) - - # Cell volume - v = mkvc(getCellVolume(X,Y,Z)) #mkvc(getVolume(X,Y,Z)) - vsig = v*mkvc(sigma) - v3 = vstack((vstack((vsig,vsig)),vsig)) - v3 = v3.flatten() - - V = sdiag(v3) - - return (P1.T*invN1.T*V*invN1*P1 + P2.T*invN2.T*V*invN2*P2)/2.0 - - -if __name__ == '__main__': - - X,Y,Z = ndgrid(linspace(0,2,3),linspace(0,2,3),linspace(0,2,3)) - Z[2,2,2] = 2.5; Z[0,0,0] = -0.5 - X[2,2,2] = 2.5; X[0,0,0] = -0.5 - sigma = ones([2,2,2]) - A = getFaceInnerProduct(X,Y,Z,sigma) - print(A) \ No newline at end of file diff --git a/SimPEG/EldadsCode/getFaceNormals.py b/SimPEG/EldadsCode/getFaceNormals.py deleted file mode 100644 index 2038a301..00000000 --- a/SimPEG/EldadsCode/getFaceNormals.py +++ /dev/null @@ -1,73 +0,0 @@ -from numpy import * -from utils import * - - -def getFaceNormals(X, Y, Z): -# compute the x normals - d1xp = diffp(X,2,3) - d1yp = diffp(Y,2,3) - d1zp = diffp(Z,2,3) - - d1xm = diffm(X,3,2) - d1ym = diffm(Y,3,2) - d1zm = diffm(Z,3,2) - - # normals - n1x = d1yp*d1zm - d1zp*d1ym - n1y = d1zp*d1xm - d1xp*d1zm - n1z = d1xp*d1ym - d1yp*d1xm - normn1 = sqrt(n1x**2 + n1y**2 + n1z**2) - n1x = n1x / normn1; - n1y = n1y / normn1; - n1z = n1z / normn1; - - area1 = normn1/2 - - - # compute the y normals - d2xp = diffp(X,1,3) - d2yp = diffp(Y,1,3) - d2zp = diffp(Z,1,3) - - d2xm = diffm(X,1,3) - d2ym = diffm(Y,1,3) - d2zm = diffm(Z,1,3) - - # normals - n2x = d2yp*d2zm - d2zp*d2ym - n2y = d2zp*d2xm - d2xp*d2zm - n2z = d2xp*d2ym - d2yp*d2xm - normn2 = sqrt(n2x**2 + n2y**2 + n2z**2) - n2x = n2x / normn2 - n2y = n2y / normn2 - n2z = n2z / normn2 - - area2 = normn2/2 - - # compute the z normals - d3xp = diffp(X,1,2) - d3yp = diffp(Y,1,2) - d3zp = diffp(Z,1,2) - - d3xm = diffm(X,2,1) - d3ym = diffm(Y,2,1) - d3zm = diffm(Z,2,1) - - # normals - n3x = d3yp*d3zm - d3zp*d3ym - n3y = d3zp*d3xm - d3xp*d3zm - n3z = d3xp*d3ym - d3yp*d3xm; - normn3 = sqrt(n3x**2 + n3y**2 + n3z**2); - n3x = n3x / normn3; - n3y = n3y / normn3; - n3z = n3z / normn3; - - area3 = normn3/2; - - return (n1x,n1y,n1z,n2x,n2y,n2z,n3x,n3y,n3z,area1,area2,area3) - -if __name__ == '__main__': - - X, Y, Z = mgrid[0:4, 0:5, 0:6] - - t = getFaceNormals(X, Y, Z) diff --git a/SimPEG/EldadsCode/getVolume.py b/SimPEG/EldadsCode/getVolume.py deleted file mode 100644 index 3b4cd250..00000000 --- a/SimPEG/EldadsCode/getVolume.py +++ /dev/null @@ -1,34 +0,0 @@ -from numpy import * -from utils import diff, ave - - -def getVolume(X,Y,Z): - - # compute edge vectors - t1x = ave(ave(diff(X, 1),2),3) - t1y = ave(ave(diff(Y, 1),2),3) - t1z = ave(ave(diff(Z, 1),2),3) - - t2x = ave(ave(diff(X, 2),1),3) - t2y = ave(ave(diff(Y, 2),1),3) - t2z = ave(ave(diff(Z, 2),1),3) - - t3x = ave(ave(diff(X, 3),1),2) - t3y = ave(ave(diff(Y, 3),1),2) - t3z = ave(ave(diff(Z, 3),1),2) - - # v = [t1x t1y t1z][i j k] - # [t2x t2y t2z] - # [t3x t3y t3z] - - v = t1x*(t2y*t3z - t2z*t3y) - t1y*(t2x*t3z - t2z*t3x) + t1z*(t2x*t3y-t2y*t3x) - - return v - - -if __name__ == '__main__': - - X, Y, Z = mgrid[0:4, 0:5, 0:6] - X = (1.0*X)/2 - v = getVolume(X, Y, Z) - print v \ No newline at end of file diff --git a/SimPEG/EldadsCode/inv3X3BlockDiagonal.py b/SimPEG/EldadsCode/inv3X3BlockDiagonal.py deleted file mode 100644 index 2bfd86c4..00000000 --- a/SimPEG/EldadsCode/inv3X3BlockDiagonal.py +++ /dev/null @@ -1,36 +0,0 @@ -from utils import * -from sputils import * - - -def inv3X3BlockDiagonal(a11, a12, a13, a21, a22, a23, a31, a32, a33): - - a11 = mkvc(a11) - a12 = mkvc(a12) - a13 = mkvc(a13) - a21 = mkvc(a21) - a22 = mkvc(a22) - a23 = mkvc(a23) - a31 = mkvc(a31) - a32 = mkvc(a32) - a33 = mkvc(a33) - - detA = a31*a12*a23 - a31*a13*a22 - a21*a12*a33 + a21*a13*a32 + a11*a22*a33 - a11*a23*a32 - - b11 = +(a22*a33 - a23*a32)/detA - b12 = -(a12*a33 - a13*a32)/detA - b13 = +(a12*a23 - a13*a22)/detA - - b21 = +(a31*a23 - a21*a33)/detA - b22 = -(a31*a13 - a11*a33)/detA - b23 = +(a21*a13 - a11*a23)/detA - - b31 = -(a31*a22 - a21*a32)/detA - b32 = +(a31*a12 - a11*a32)/detA - b33 = -(a21*a12 - a11*a22)/detA - - B = appendBottom3( - appendRight3(sdiag(b11), sdiag(b12), sdiag(b13)), - appendRight3(sdiag(b21), sdiag(b22), sdiag(b23)), - appendRight3(sdiag(b31), sdiag(b32), sdiag(b33))) - - return B diff --git a/SimPEG/EldadsCode/meshUtils.py b/SimPEG/EldadsCode/meshUtils.py deleted file mode 100644 index 5d3f071d..00000000 --- a/SimPEG/EldadsCode/meshUtils.py +++ /dev/null @@ -1,94 +0,0 @@ -from sputils import * -from utils import * -from numpy import * - - -def getCellCenterFromNodal(X, Y, Z): - """Cell Centers from Nodal locations""" - XC = 1.0/8.0 * (X[:-1, :-1, :-1] + X[1:, :-1, :-1] + X[:-1, 1:, :-1] + X[1:, 1:, :-1] + - X[:-1, :-1, 1:] + X[1:, :-1, 1:] + X[:-1, 1:, 1:] + X[1:, 1:, 1:]) - - YC = 1.0/8.0 * (Y[:-1, :-1, :-1] + Y[1:, :-1, :-1] + Y[:-1, 1:, :-1] + Y[1:, 1:, :-1] + - Y[:-1, :-1, 1:] + Y[1:, :-1, 1:] + Y[:-1, 1:, 1:] + Y[1:, 1:, 1:]) - - ZC = 1.0/8.0 * (Z[:-1, :-1, :-1] + Z[1:, :-1, :-1] + Z[:-1, 1:, :-1] + Z[1:, 1:, :-1] + - Z[:-1, :-1, 1:] + Z[1:, :-1, 1:] + Z[:-1, 1:, 1:] + Z[1:, 1:, 1:]) - - return (XC, YC, ZC) - - -def getEdgesFromNodal(X, Y, Z): - """Edges from Nodal locations - - node(i,j,k+1) ------ edge2(i,j,k+1) ----- node(i,j+1,k+1) - / / - / / | - edge3(i,j,k) face1(i,j,k) edge3(i,j+1,k) - / / | - / / | - node(i,j,k) ------ edge2(i,j,k) ----- node(i,j+1,k) - | | | - | | node(i+1,j+1,k+1) - | | / - edge1(i,j,k) face3(i,j,k) edge1(i,j+1.k) - | | / - | | / - | |/ - node(i+1,j,k) ------ edge2(i+1,j,k) ----- node(i+1,j+1,k) - """ - - XE1 = (X[1:, :, :]+X[:-1, :, :])/2.0 - YE1 = (Y[1:, :, :]+Y[:-1, :, :])/2.0 - ZE1 = (Z[1:, :, :]+Z[:-1, :, :])/2.0 - - XE2 = (X[:, 1:, :]+X[:, :-1, :])/2.0 - YE2 = (Y[:, 1:, :]+Y[:, :-1, :])/2.0 - ZE2 = (Z[:, 1:, :]+Z[:, :-1, :])/2.0 - - XE3 = (X[:, :, 1:]+X[:, :, :-1])/2.0 - YE3 = (Y[:, :, 1:]+Y[:, :, :-1])/2.0 - ZE3 = (Z[:, :, 1:]+Z[:, :, :-1])/2.0 - - return (XE1, YE1, ZE1, XE2, YE2, ZE2, XE3, YE3, ZE3) - - -def getFacesFromNodal(X, Y, Z): - """Get faces from nodal --""" - - XF1 = 1.0/4.0*(X[:, :-1, :-1]+X[:, 1:, :-1]+X[:, :-1, 1:]+X[:, 1:, 1:]) - YF1 = 1.0/4.0*(Y[:, :-1, :-1]+Y[:, 1:, :-1]+Y[:, :-1, 1:]+Y[:, 1:, 1:]) - ZF1 = 1.0/4.0*(Z[:, :-1, :-1]+Z[:, 1:, :-1]+Z[:, :-1, 1:]+Z[:, 1:, 1:]) - - XF2 = 1.0/4.0*(X[:-1, :, :-1]+X[1:, :, :-1]+X[:-1, :, 1:]+X[1:, :, 1:]) - YF2 = 1.0/4.0*(Y[:-1, :, :-1]+Y[1:, :, :-1]+Y[:-1, :, 1:]+Y[1:, :, 1:]) - ZF2 = 1.0/4.0*(Z[:-1, :, :-1]+Z[1:, :, :-1]+Z[:-1, :, 1:]+Z[1:, :, 1:]) - - XF3 = 1.0/4.0*(X[:-1, :-1, :]+X[1:, :-1, :]+X[:-1, 1:, :]+X[1:, 1:, :]) - YF3 = 1.0/4.0*(Y[:-1, :-1, :]+Y[1:, :-1, :]+Y[:-1, 1:, :]+Y[1:, 1:, :]) - ZF3 = 1.0/4.0*(Z[:-1, :-1, :]+Z[1:, :-1, :]+Z[:-1, 1:, :]+Z[1:, 1:, :]) - - return (XF1, YF1, ZF1, XF2, YF2, ZF2, XF3, YF3, ZF3) - - -def projectEdgeVectorField(EV1, EV2, EV3, X, Y, Z): - """Project Edge vector field""" - - t1x, t1y, t1z, t2x, t2y, t2z, t3x, t3y, t3z, nrm1, nrm2, nrm3 = getEdgeTangent(X, Y, Z) - - E1 = EV1[:, 0]*mkvc(t1x) + EV1[:, 1]*mkvc(t1y) + EV1[:, 2]*mkvc(t1z) - E2 = EV2[:, 0]*mkvc(t2x) + EV2[:, 1]*mkvc(t2y) + EV2[:, 2]*mkvc(t2z) - E3 = EV3[:, 0]*mkvc(t3x) + EV3[:, 1]*mkvc(t3y) + EV3[:, 2]*mkvc(t3z) - - return hstack((hstack((mkvc(E1), mkvc(E2))), mkvc(E3))) - - -def projectFaceVectorField(FV1, FV2, FV3, X, Y, Z): - """Prolect Face vector field""" - - n1x, n1y, n1z, n2x, n2y, n2z, n3x, n3y, n3z, ar1, ar2, ar3 = getFaceNormals(X, Y, Z) - - F1 = FV1[:, 0]*mkvc(n1x) + FV1[:, 1]*mkvc(n1y) + FV1[:, 2]*mkvc(n1z) - F2 = FV2[:, 0]*mkvc(n2x) + FV2[:, 1]*mkvc(n2y) + FV2[:, 2]*mkvc(n2z) - F3 = FV3[:, 0]*mkvc(n3x) + FV3[:, 1]*mkvc(n3y) + FV3[:, 2]*mkvc(n3z) - - return hstack((hstack((mkvc(F1), mkvc(F2))), mkvc(F3))) diff --git a/SimPEG/EldadsCode/sputils.py b/SimPEG/EldadsCode/sputils.py deleted file mode 100644 index bf54b15a..00000000 --- a/SimPEG/EldadsCode/sputils.py +++ /dev/null @@ -1,77 +0,0 @@ -from scipy import sparse -from numpy import * - - -def ddx(n): - """Define 1D derivatives""" - # ddx = lambda n: sparse.spdiags((np.ones((n+1, 1))*[-1, 1]).T, [0, 1], n, n+1, format='csr') - return sparse.spdiags(-ones(n), 0, n, n+1) + sparse.spdiags(ones(n+1), 1, n, n+1) - - -def av(n): - """Define 1D average""" - return 0.5*(sparse.spdiags(ones(n+1), 0, n, n+1) + sparse.spdiags(ones(n+1), 1, n, n+1)) - - -def sdiag(h): - """Diagonal matrix""" - return sparse.spdiags(h, 0, size(h), size(h)) - - -def speye(n): - """sparse identity""" - return sparse.spdiags(ones(n), 0, n, n) - - -def kron3(A, B, C): - """two kron prods""" - return sparse.kron(sparse.kron(A, B), C) - - -def appendBottom(A, B): - """append on bottom""" - C = sparse.vstack((A, B)) - C = C.tocsr() - return C - - -def appendBottom3(A, B, C): - """append on bottom""" - C = appendBottom(appendBottom(A, B), C) - C = C.tocsr() - return C - - -def appendRight(A, B): - """append on right""" - C = sparse.hstack((A, B)) - C = C.tocsr() - return C - - -def appendRight3(A, B, C): - """append on right""" - C = appendRight(appendRight(A, B), C) - C = C.tocsr() - return C - - -def blkDiag(A, B): - """blockdigonal""" - O12 = sparse.coo_matrix((shape(A)[0], shape(B)[1])) - O21 = sparse.coo_matrix((shape(B)[0], shape(A)[1])) - C = sparse.vstack((sparse.hstack((A, O12)), sparse.hstack((O21, B)))) - C = C.tocsr() - return C - - -def blkDiag3(A, B, C): - """blockdigonal 3""" - ABC = blkDiag(blkDiag(A, B), C) - ABC = ABC.tocsr() - return ABC - - -def spzeros(n1, n2): - """spzeros""" - return sparse.coo_matrix((n1, n2)) diff --git a/SimPEG/EldadsCode/tools.py b/SimPEG/EldadsCode/tools.py deleted file mode 100644 index 132962a7..00000000 --- a/SimPEG/EldadsCode/tools.py +++ /dev/null @@ -1,222 +0,0 @@ -import numpy; -import cmath; -import math; - -def prod(arg): - """ returns the product of elements in arg. - arg can be list, tuple, set, and array with numerical values. """ - ret = 1; - for i in range(0,len(arg)): - ret = ret * arg[i]; - return ret; - - -def allIndices(dim): - """ From the given shape of dimenions (e.g. (2,3,4)), - generate a numpy.array of all, sorted indices.""" - - length = len(dim); - - sub = numpy.arange(dim[length-1]).reshape(dim[length-1],1); - - for d in range(length-2, -1, -1): - for i in range(0, dim[d]): - temp = numpy.ndarray([len(sub), 1]); - temp.fill(i); - temp = numpy.concatenate((temp,sub), axis=1); - if(i == 0): - newsub = temp; - else: - newsub = numpy.concatenate((newsub, temp), axis = 0); - - sub = newsub; - - return sub; - -def find(nda, obj): - """returns the index of the obj in the given nda(ndarray, list, or tuple)""" - for i in range(0, len(nda)): - if(nda[i] == obj): - return i; - return -1; - - -def notin(n, vector): - """returns a numpy.array object that contains - elements in [0,1, ... n-1] but not in vector.""" - ret = numpy.arange(n).tolist(); - for i in vector: - if (0 <= i and i < n): - ret.remove(i); - return numpy.array(ret); - - - -def getelts(nda, indices): - """From the given nda(ndarray, list, or tuple), returns the list located at the given indices""" - ret = []; - for i in indices: - ret.extend([nda[i]]); - return numpy.array(ret); - -def sub2ind(shape, subs): - """ From the given shape, returns the index of the given subscript""" - revshp = list(shape); - revshp.reverse(); - mult = [1]; - for i in range(0, len(revshp)-1): - mult.extend([mult[i]*revshp[i]]); - mult.reverse(); - mult = numpy.array(mult).reshape(len(mult),1); - - idx = numpy.dot((subs) , (mult)); - return idx; - -def ind2sub(shape, ind): - """ From the given shape, returns the subscrips of the given index""" - revshp = []; - revshp.extend(shape); - revshp.reverse(); - mult = [1]; - for i in range(0, len(revshp)-1): - mult.extend([mult[i]*revshp[i]]); - mult.reverse(); - mult = numpy.array(mult).reshape(len(mult)); - - sub = []; - - for i in range(0,len(shape)): - sub.extend([math.floor(ind / mult[i])]); - ind = ind - (math.floor(ind/mult[i]) * mult[i]); - return sub; - -def tt_dimscehck(dims, N, M = None, exceptdims = False): - """Checks whether the specified dimensions are valid in a tensor of N-dimension. - If M is given, then it will also retuns an index for M multiplicands. - If exceptdims == True, then it will compute for the dimensions not specified.""" - - # if exceptdims is true - if(exceptdims): - dims = listdiff(range(0,N), dims); - - #check vals in between 0 and N-1 - for i in range(0, len(dims)): - if(dims[i] < 0 or dims[i] >= N): - raise ValueError("invalid dimensions specified"); - - # number of dimensions in dims - p = len(dims); - - sdims = []; - sdims.extend(dims); - sdims.sort(); - - #indices of the elements in the sorted array - sidx = []; - #table that denotes whether the index is used - table = numpy.ndarray([len(sdims)]); - table.fill(0); - - for i in range(0, len(sdims)): - for j in range(0, len(dims)): - if(sdims[i] == dims[j] and table[j] == 0): - sidx.extend([j]); - table[j] = 1; - break; - - if (M == None): - return sdims - - if(M > N): - raise ValueError("Cannot have more multiplicands than dimensions"); - - if(M != N and M != p): - raise ValueError("invalid number of multiplicands"); - - if(M == p): - vidx = sidx; - else: - vidx = sdims; - - return (sdims, vidx); - -def listtimes(list, c): - """multiplies the elements in the list by the given scalar value c""" - ret = [] - for i in range(0, len(list)): - ret.extend([list[i]]*c); - return ret; - -def listdiff(list1, list2): - """returns the list of elements that are in list 1 but not in list2""" - if(list1.__class__ == numpy.ndarray): - list1 = list1.tolist(); - if(list2.__class__ == numpy.ndarray): - list2 = list2.tolist(); - ret = [] - for i in range(0,len(list1)): - ok = true - for j in range(0, len(list2)): - if(list[i] == list[j]): - ok = false; - break; - if(ok): - ret.extend([list[i]]); - return ret; - - - -def tt_subscheck(subs): - """Check whether the given list of subscripts are valid. Used for sptensor""" - isOk = True; - if(subs.size == 0): - isOk = True; - - elif(subs.ndim != 2): - isOk = False; - - else: - for i in range(0, (subs.size / subs[0].size)): - for j in range(0, (subs[0].size)): - val = subs[i][j]; - if( cmath.isnan(val) or cmath.isinf(val) or val < 0 or val != round(val) ): - isOk = False; - - if(not isOk): - raise ValueError("Subscripts must be a matrix of non-negative integers"); - - return isOk; - - -def tt_valscheck(vals): - """Check whether the given list of values are valid. Used for sptensor""" - isOk = True; - - if(vals.size == 0): - isOk = True; - - elif(vals.ndim != 2 or vals[0].size != 1): - isOk = False; - - if(not isOk): - raise ValueError("values must be a column array"); - - return isOk; - -def tt_sizecheck(size): - """Check whether the given size is valid. Used for sptensor""" - size = numpy.array(size); - isOk = True; - - if(size.ndim != 1): - isOk = False; - else: - for i in range(0, len(size)): - val = size[i]; - if(cmath.isnan(val) or cmath.isinf(val) - or val <= 0 or val != round(val)): - isOk = False; - - if(not isOk): - raise ValueError("size must be a row vector of real positive integers"); - return isOk; diff --git a/SimPEG/EldadsCode/utils.py b/SimPEG/EldadsCode/utils.py deleted file mode 100644 index e912c5b8..00000000 --- a/SimPEG/EldadsCode/utils.py +++ /dev/null @@ -1,87 +0,0 @@ -from numpy import * -import numpy as np - - -def diff(A, d): - if(d == 1): - return A[1:, :, :] - A[:-1, :, :] - elif(d == 2): - return A[:, 1:, :] - A[:, :-1, :] - else: - return A[:, :, 1:] - A[:, :, :-1] - #else: - # print('d must be 1,2 or 3') - - -def diffp(A, d1, d2): - if(d1 == 1 and d2 == 2): - return A[1:, 1:, :] - A[:-1, :-1, :] - elif(d1 == 1 and d2 == 3): - return A[1:, :, 1:] - A[:-1, :, :-1] - else: - return A[:, 1:, 1:] - A[:, :-1, :-1] - - -def diffm(A, d1, d2): - if(d1 == 3 and d2 == 2): - return A[:, :-1, 1:] - A[:, 1:, :-1] - elif(d1 == 1 and d2 == 3): - return A[1:, :, :-1] - A[:-1, :, 1:] - elif(d1 == 2 and d2 == 1): - return A[:-1, 1:, :] - A[1:, :-1, :] - else: - print('d must be 1, 2 or 3') - - -def ave(A, d): - if(d == 1): - return 0.5*(A[1:, :, :] + A[:-1, :, :]) - elif(d == 2): - return 0.5*(A[:, 1:, :] + A[:, :-1, :]) - elif(d == 3): - return 0.5*(A[:, :, 1:] + A[:, :, :-1]) - else: - print('d must be 1,2 or 3') - - -def mkmat(x): - return reshape(matrix(x), (size(x), 1), 'F') - - -def hstack3(a, b, c): - a = mkvc(a) - b = mkvc(b) - c = mkvc(c) - a = mkmat(a) - b = mkmat(b) - c = mkmat(c) - return hstack((hstack((a, b)), c)) - - -def ind2sub(shape, ind): - """From the given shape, returns the subscrips of the given index""" - revshp = [] - revshp.extend(shape) - mult = [1] - for i in range(0, len(revshp)-1): - mult.extend([mult[i]*revshp[i]]) - mult = array(mult).reshape(len(mult)) - - sub = [] - - for i in range(0, len(shape)): - sub.extend([math.floor(ind / mult[i])]) - ind = ind - (math.floor(ind/mult[i]) * mult[i]) - return sub - - -def sub2ind(shape, subs): - """From the given shape, returns the index of the given subscript""" - revshp = list(shape) - mult = [1] - for i in range(0, len(revshp)-1): - mult.extend([mult[i]*revshp[i]]) - mult = array(mult).reshape(len(mult), 1) - - idx = dot((subs), (mult)) - return idx diff --git a/SimPEG/EldadsCode/zevel.py b/SimPEG/EldadsCode/zevel.py deleted file mode 100644 index c3e9ae2d..00000000 --- a/SimPEG/EldadsCode/zevel.py +++ /dev/null @@ -1,119 +0,0 @@ -#============= Nodal Gradients =========================== -def getNodalGradient(h1,h2,h3): - - n1 = size(h1) - n2 = size(h2) - n3 = size(h3) - D1 = kron3(speye(n3+1),speye(n2+1),ddx(n1)) - D2 = kron3(speye(n3+1),ddx(n2),speye(n1+1)) - D3 = kron3(ddx(n3),speye(n2+1),speye(n1+1)) - - # topological gradient - GRAD = appendBottom3(D1,D2,D3) - - # scale for non-uniform mesh - L = blkDiag3(kron3(speye(n3+1),speye(n2+1),sdiag(1/h1)), - kron3(speye(n3+1),sdiag(1/h2),speye(n1+1)), - kron3(sdiag(1/h3),speye(n2+1),speye(n1+1))) - - return L*GRAD - -#============= Edge CURL =========================== -def getCurlMatrix(h1,h2,h3): - - n1 = size(h1) - n2 = size(h2) - n3 = size(h3) - - d1 = ddx(n1) - d2 = ddx(n2) - d3 = ddx(n3) - # derivatives on x-edge variables - D32 = kron3(d3,speye(n2),speye(n1+1)) - D23 = kron3(speye(n3),d2,speye(n1+1)) - D31 = kron3(d3,speye(n2+1),speye(n1)) - D13 = kron3(speye(n3),speye(n2+1),d1) - D21 = kron3(speye(n3+1),d2,speye(n1)) - D12 = kron3(speye(n3+1),speye(n2),d1) - - O1 = spzeros(shape(D32)[0],shape(D31)[1]) - O2 = spzeros(shape(D31)[0],shape(D32)[1]) - O3 = spzeros(shape(D21)[0],shape(D13)[1]) - - CURL = appendBottom3( - appendRight3(O1, -D32, D23), - appendRight3(D31, O2, -D13), - appendRight3(-D21, D12, O3)) - - # scale for non-uniform mesh - F = blkDiag3(kron3(sdiag(1/h3),sdiag(1/h2),speye(n1+1)), - kron3(sdiag(1/h3),speye(n2+1),sdiag(1/h1)), - kron3(speye(n3+1),sdiag(1/h2),sdiag(1/h1))) - - L = blkDiag3(kron3(speye(n3+1),speye(n2+1),sdiag(h1)), - kron3(speye(n3+1),sdiag(h2),speye(n1+1)), - kron3(sdiag(h3),speye(n2+1),speye(n1+1))) - - - return F*(CURL*L) - -#============= Face DIV =========================== -def getDivMatrix(h1,h2,h3): - - n1 = size(h1) - n2 = size(h2) - n3 = size(h3) - - d1 = ddx(n1) - d2 = ddx(n2) - d3 = ddx(n3) - D1 = kron3(speye(n3),speye(n2),d1) - D2 = kron3(speye(n3),d2,speye(n1)) - D3 = kron3(d3,speye(n2),speye(n1)) - - # divergence on faces - D = appendRight3(D1, D2, D3) - - # scale for non-uniform mesh - F = blkDiag3(kron3(sdiag(h3),sdiag(h2),speye(n1+1)), - kron3(sdiag(h3),speye(n2+1),sdiag(h1)), - kron3(speye(n3+1),sdiag(h2),sdiag(h1))) - - V = kron3(sdiag(1/h3),sdiag(1/h2),sdiag(1/h1)) - - return V*(D*F) - -#====== Face Averageing ================= -def getFaceAverage(n1,n2,n3): - - av1 = av(n1) - av2 = av(n2) - av3 = av(n3) - - Af = appendRight3(kron3(speye(n3),speye(n2),av1), - kron3(speye(n3),av2,speye(n1)), - kron3(av3,speye(n2),speye(n1))) - return Af - -#====== Edge Averageing ================= -def getEdgeAverage(n1,n2,n3): - - av1 = av(n1) - av2 = av(n2) - av3 = av(n3) - - Ae = appendRight3(kron3(av3,av2,speye(n1)), - kron3(av3,speye(n2),av1), - kron3(speye(n3),av2,av1)) - return Ae - -#====== Node Averageing ================= -def getNodeAverage(n1,n2,n3): - - av1 = av(n1) - av2 = av(n2) - av3 = av(n3) - - return kron3(av3,av2,av1) - - diff --git a/SimPEG/EldadsCode/GaussNewton.py b/SimPEG/GaussNewton.py similarity index 100% rename from SimPEG/EldadsCode/GaussNewton.py rename to SimPEG/GaussNewton.py From c3476321a87f8da1289fa4520d2aef77fdc859c0 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Tue, 6 Aug 2013 18:07:21 -0700 Subject: [PATCH 51/51] Removed EMforward code for merging into master. (This is still being developed in branch: eldadsWork) --- SimPEG/EMforward.py | 79 ------------------------------------- SimPEG/interpmat.py | 94 --------------------------------------------- SimPEG/ops.py | 62 ------------------------------ 3 files changed, 235 deletions(-) delete mode 100644 SimPEG/EMforward.py delete mode 100644 SimPEG/interpmat.py delete mode 100644 SimPEG/ops.py diff --git a/SimPEG/EMforward.py b/SimPEG/EMforward.py deleted file mode 100644 index 9c332c9b..00000000 --- a/SimPEG/EMforward.py +++ /dev/null @@ -1,79 +0,0 @@ -import numpy as np -from utils import mkvc -import scipy.sparse.linalg.dsolve as dsl -from InnerProducts import getFaceInnerProduct, getEdgeInnerProduct - -def getMisfit(m,mesh,forward,param): - - mu0 = 4*np.pi*1e-7 - omega = forward['omega'] #[param['indomega']] - rhs = forward['rhs'] #[:,param['indrhs']] - mis = 0 - dmis = m*0 - - # Maxwell's system for E - for i in range(len(omega)): - for j in range(rhs.shape[1]): - Curl = mesh.edgeCurl - #Grad = mesh.nodalGrad - sigma = np.exp(m) - Me,PP = getEdgeInnerProduct(mesh,sigma) - Mf = 1/mu0 * getFaceInnerProduct(mesh) # assume mu = mu0 - - A = Curl.T * Mf * Curl - 1j * omega[i] * Me - b = mkvc(np.array(rhs[:,j])) - e = dsl.spsolve(A,b) - e = mkvc(e,2) - #print np.linalg.norm(A*e-b)/np.linalg.norm(b) - P = forward['projection'] - d = P*e - r = mkvc(d - param.dobs[i,j,:],2) - - mis = mis + 0.5*(r.T*r) - # get derivatives - lam = dsl.spsolve(A.T,P.T*r) - lam = mkvc(lam,2) - Gij = - 1j * omega[i] * PP.T*sp.diag((PP*e)*mesh.vol) - dmis = dmis - Gij.T*lam - - - return mis, dmis, d - - - -if __name__ == '__main__': - from TensorMesh import TensorMesh - from interpmat import interpmat - from scipy import sparse as sp - - h = [np.ones(7),np.ones(8),np.ones(9)] - mesh = TensorMesh(h) - xs = np.array([3.1,4.3,5.4,6.5]) - ys = np.array([3.2,4.1,5.4,6.2]) - zs = np.array([4.3,4.2,4.1,4.1]); - - xyz = mesh.gridEx - x = xyz[:,0]; y = xyz[:,1]; z = xyz[:,2] - x = list(set(x)); y = list(set(y)); z = list(set(z)) - Px = interpmat(x,y,z,xs,ys,zs) - xyz = mesh.gridEy - x = xyz[:,0]; y = xyz[:,1]; z = xyz[:,2] - x = list(set(x)); y = list(set(y)); z = list(set(z)) - Py = interpmat(x,y,z,xs,ys,zs) - xyz = mesh.gridEz - x = xyz[:,0]; y = xyz[:,1]; z = xyz[:,2] - x = list(set(x)); y = list(set(y)); z = list(set(z)) - Pz = interpmat(x,y,z,xs,ys,zs) - P = sp.hstack((Px,Py,Pz)) - - ne = np.sum(mesh.nE) - Q = np.matrix(np.random.randn(ne,5)) - omega = [1,2,3] - forward = {'omega':omega, 'rhs':Q,'projection':P} - dobs = np.ones([np.size(xs),5,np.size(omega)]) - param = {'dobs':dobs} - - - - m = np.ones(mesh.nC) - getMisfit(m,mesh,forward,param) diff --git a/SimPEG/interpmat.py b/SimPEG/interpmat.py deleted file mode 100644 index bfb80291..00000000 --- a/SimPEG/interpmat.py +++ /dev/null @@ -1,94 +0,0 @@ -from scipy import sparse as sp -import numpy as np - - -def interpmat(x,y,z,xr,yr,zr): -# -# This function does local linear interpolation -# computed for each receiver point in turn -# -# [Q] = linint(x,y,z,xr,yr,zr) -# Interpolation matrix -# - - nx = np.size(x) - ny = np.size(y) - nz = np.size(z) - - nps = np.size(xr) - - #Q = spalloc(np,nx*ny*nz,8*np); - Q = sp.lil_matrix((nps,nx*ny*nz)) - ind_x = np.array([0,0]) - ind_y = np.array([0,0]) - ind_z = np.array([0,0]) - dx, dy, dz = np.zeros(2), np.zeros(2), np.zeros(2) - for i in range(0, nps): - im = np.argmin(abs(xr[i]-x)) - print i,im - if xr[i] - x[im] >= 0: # Point on the left - ind_x[0] = im; ind_x[1] = im+1 - else: # Point on the right - ind_x[0] = im-1; ind_x[1] = im - - - dx[0] = xr[i] - x[ind_x[0]] - dx[1] = x[ind_x[1]] - xr[i] - - im = np.argmin(abs(yr[i] - y)) - if yr[i] - y[im] >= 0: # Point on the left - ind_y[0] = im; ind_y[1] = im+1 - else: # Point on the right - ind_y[0] = im-1; ind_y[1] = im - - - dy[0] = yr[i] - y[ind_y[0]] - dy[1] = y[ind_y[1]] - yr[i]; - - im = np.argmin(abs(zr[i] - z)); - if zr[i] -z[im] >= 0: # Point on the left - ind_z[0] = im; ind_z[1] = im+1 - else: # Point on the right - ind_z[0] = im-1; ind_z[1] = im; - - dz[0] = zr[i] - z[ind_z[0]]; dz[1] = z[ind_z[1]] - zr[i] - - Dx = x[ind_x[1]] - x[ind_x[0]] - Dy = y[ind_y[1]] - y[ind_y[0]] - Dz = z[ind_z[1]] - z[ind_z[0]] - #dv = Dx*Dy*Dz - - # Get the row in the matrix - v = np.zeros([nx, ny,nz]) - - v[ ind_x[0], ind_y[0], ind_z[0]] = (1-dx[0]/Dx)*(1-dy[0]/Dy)*(1-dz[0]/Dz) - v[ ind_x[0], ind_y[1], ind_z[0]] = (1-dx[0]/Dx)*(1-dy[1]/Dy)*(1-dz[0]/Dz) - v[ ind_x[1], ind_y[0], ind_z[0]] = (1-dx[1]/Dx)*(1-dy[0]/Dy)*(1-dz[0]/Dz) - v[ ind_x[1], ind_y[1], ind_z[0]] = (1-dx[1]/Dx)*(1-dy[1]/Dy)*(1-dz[0]/Dz) - v[ ind_x[0], ind_y[0], ind_z[1]] = (1-dx[0]/Dx)*(1-dy[0]/Dy)*(1-dz[1]/Dz) - v[ ind_x[0], ind_y[1], ind_z[1]] = (1-dx[0]/Dx)*(1-dy[1]/Dy)*(1-dz[1]/Dz) - v[ ind_x[1], ind_y[0], ind_z[1]] = (1-dx[1]/Dx)*(1-dy[0]/Dy)*(1-dz[1]/Dz) - v[ ind_x[1], ind_y[1], ind_z[1]] = (1-dx[1]/Dx)*(1-dy[1]/Dy)*(1-dz[1]/Dz) - - - print(np.shape(v.flatten('F'))) - print(np.shape(Q)) - - Q[i,:] = v.flatten('F') - - - return Q.tocsr() - - -if __name__ == '__main__': - - x = np.array([1.1, 2.1, 3.6, 4.9]) - y = np.array([1.2, 2.2, 3.3, 4.9, 5.6]) - z = np.array([0.8, 1.7, 4.9, 6.5]) - - xr = np.array([2.5,3.2]) - yr = np.array([2.4,3.6]) - zr = np.array([2.5,3.9]) - - A = interpmat(x,y,z,xr,yr,zr) - \ No newline at end of file diff --git a/SimPEG/ops.py b/SimPEG/ops.py deleted file mode 100644 index 4997cde9..00000000 --- a/SimPEG/ops.py +++ /dev/null @@ -1,62 +0,0 @@ -import numpy as np -from utils import mkvc -import scipy.sparse.linalg as spla -import scipy.sparse as sp - - -def matmul(A,B): - - # first check shape - if np.shape(A)[1] != np.shape(B)[0]: - print 'error in sizes' - return - - # Check types - sA = sp.issparse(A) - sB = sp.issparse(B) - - if ((sA == False) & (sB == True)): # doesno't work unless we trick it - return (B.T.dot(A.T)).T - else: - return A.dot(B) - - -def dot(A,B): - A = mkvc(A,1) - B = mkvc(B,1) - return np.dot(A,B) - -def inner(A,B): - A = mkvc(A,1) - B = mkvc(B,1) - return np.dot(A,B) - - -if __name__ == '__main__': - import numpy as np - from utils import mkvc - import scipy.sparse as sp - - # generate sparse and dense matrices - A = sp.rand(100, 200, density=0.05, format='csr', dtype=None) - B = sp.rand(200, 150, density=0.05, format='csr', dtype=None) - C = np.random.rand(200,150) - D = np.random.rand(150,100) - b = mkvc(np.arange(200),1) - c = np.reshape(b,(1,200)) - matmul(A,B) - matmul(A,C) - matmul(C,D) - matmul(D,A) - matmul(A,b) - dot(c,b) - dot(C,C) - print np.shape(c), np.shape(b)[0] - print matmul(c,b),dot(c,b) - - - - - - -