Files
simpeg/SimPEG/Mesh/CylMesh.py
T
2014-02-21 11:26:14 -08:00

463 lines
16 KiB
Python

import numpy as np
import scipy.sparse as sp
from scipy.constants import pi
from SimPEG.Utils import mkvc, ndgrid, sdiag, kron3, speye, ddx, av, avExtrap
from TensorMesh import BaseTensorMesh
class CylMesh(BaseTensorMesh):
"""
CylMesh is a mesh class for cylindrical problems
"""
_meshType = 'CYL'
_unitDimensions = [1, 2*np.pi, 1]
def __init__(self, h, x0=None):
BaseTensorMesh.__init__(self, h, x0)
assert self.dim == 3, "dim of mesh must equal 3, for a cylindrically symmetric mesh use [hx, 1, hz]"
assert self.hy.sum() == 2*np.pi, "The 2nd dimension must sum to 2*pi"
@property
def nNx(self):
"""
Number of nodes in the x-direction
:rtype: int
:return: nNx
"""
if self.nCy == 1:
return self.nCx
return self.nCx + 1
@property
def nNy(self):
"""
Number of nodes in the y-direction
:rtype: int
:return: nNy
"""
if self.nCy == 1:
return self.nCy - 1
return self.nCy
@property
def nN(self):
"""
Total number of nodes
:rtype: int
:return: nN
"""
return (np.r_[self.nNx, self.nNy, self.nNz]).prod()
@property
def nFx(self):
"""
Number of x-faces
:rtype: int
:return: nFx
"""
return self.nC
@property
def vnFx(self):
"""
Number of x-faces in each direction
:rtype: numpy.array (dim, )
:return: vnFx
"""
return self.vnC
@property
def nFy(self):
"""
Number of y-faces
:rtype: int
:return: nFy
"""
return (self.vnC + np.r_[0,-1,0][:self.dim]).prod()
@property
def nEx(self):
"""
Number of x-edges
:rtype: int
:return: nEx
"""
return (self._n + np.r_[0,-1,1]).prod()
@property
def nEy(self):
"""
Number of y-edges
:rtype: int
:return: nEy
"""
return (self._n + np.r_[0,0,1]).prod()
@property
def nEz(self):
"""
Number of z-edges
:rtype: int
:return: nEz
"""
return (self._n + np.r_[0,-1,0]).prod()
@property
def vectorCCx(self):
"""Cell-centered grid vector (1D) in the x direction."""
firstEl = -self.hx[0]*0.5 if self.nCy == 1 else 0
return np.r_[firstEl, self.hx[:-1].cumsum()] + self.hx*0.5
@property
def vectorCCy(self):
"""Cell-centered grid vector (1D) in the y direction."""
return np.r_[0, self.hy[:-1]]
@property
def vectorNx(self):
"""Nodal grid vector (1D) in the x direction."""
if self.nCy == 1:
return self.hx.cumsum()
return np.r_[0, self.hx].cumsum()
@property
def vectorNy(self):
"""Nodal grid vector (1D) in the y direction."""
if self.nCy == 1:
# There aren't really any nodes, but all the grids need
# somewhere to live, why not zero?!
return np.r_[0]
return np.r_[0, self.hy[:-1].cumsum()] + self.hy[0]*0.5
@property
def edge(self):
"""Edge lengths"""
if getattr(self, '_edge', None) is None:
if self.nCy == 1:
self._edge = 2*pi*self.gridN[:,0]
else:
raise NotImplementedError('edges not yet implemented for 3D cyl mesh')
return self._edge
@property
def area(self):
"""Face areas"""
if getattr(self, '_area', None) is None:
if self.nCy > 1:
raise NotImplementedError('area not yet implemented for 3D cyl mesh')
areaR = np.kron(self.hz, 2*pi*self.vectorNx)
areaZ = np.kron(np.ones_like(self.vectorNz),pi*(self.vectorNx**2 - np.r_[0, self.vectorNx[:-1]]**2))
self._area = np.r_[areaR, areaZ]
return self._area
@property
def vol(self):
"""Volume of each cell"""
if getattr(self, '_vol', None) is None:
if self.nCy > 1:
raise NotImplementedError('vol not yet implemented for 3D cyl mesh')
az = pi*(self.vectorNx**2 - np.r_[0, self.vectorNx[:-1]]**2)
self._vol = np.kron(self.hz,az)
return self._vol
####################################################
# Operators
####################################################
@property
def faceDiv(self):
"""Construct divergence operator (face-stg to cell-centres)."""
if getattr(self, '_faceDiv', None) is None:
n = self.vnC
# Compute faceDivergence operator on faces
D1 = self.faceDivx
D3 = self.faceDivz
if self.nCy == 1:
D = sp.hstack((D1, D3), format="csr")
elif self.nCy > 1:
D2 = self.faceDivy
D = sp.hstack((D1, D2, D3), format="csr")
self._faceDiv = D
return self._faceDiv
@property
def faceDivx(self):
"""Construct divergence operator in the x component (face-stg to cell-centres)."""
if getattr(self, '_faceDivx', None) is None:
D1 = kron3(speye(self.nCz), speye(self.nCy), ddx(self.nCx)[:,1:])
S = self.r(self.area, 'F', 'Fx', 'V')
V = self.vol
self._faceDivx = sdiag(1/V)*D1*sdiag(S)
return self._faceDivx
@property
def faceDivy(self):
"""Construct divergence operator in the y component (face-stg to cell-centres)."""
raise NotImplementedError('Wrapping the ddx is not yet implemented.')
if getattr(self, '_faceDivy', None) is None:
# TODO: this needs to wrap to join up faces which are connected in the cylinder
D2 = kron3(speye(self.nCz), ddx(self.nCy), speye(self.nCx))
S = self.r(self.area, 'F', 'Fy', 'V')
V = self.vol
self._faceDivy = sdiag(1/V)*D2*sdiag(S)
return self._faceDivy
@property
def faceDivz(self):
"""Construct divergence operator in the z component (face-stg to cell-centres)."""
if getattr(self, '_faceDivz', None) is None:
D3 = kron3(ddx(self.nCz), speye(self.nCy), speye(self.nCx))
S = self.r(self.area, 'F', 'Fz', 'V')
V = self.vol
self._faceDivz = sdiag(1/V)*D3*sdiag(S)
return self._faceDivz
@property
def cellGrad(self):
"""The cell centered Gradient, takes you to cell faces."""
raise NotImplementedError('Cell Grad is not yet implemented.')
@property
def nodalGrad(self):
"""Construct gradient operator (nodes to edges)."""
if self.nCy == 1:
raise Exception('Nodal grad does not make sense for cylindrically symmetric mesh.')
raise NotImplementedError('nodalGrad not yet implemented')
@property
def nodalLaplacian(self):
"""Construct laplacian operator (nodes to edges)."""
raise NotImplementedError('nodalLaplacian not yet implemented')
@property
def edgeCurl(self):
"""The edgeCurl property."""
if getattr(self, '_edgeCurl', None) is None:
#1D Difference matricies
dr = sp.spdiags((np.ones((self.nCx+1, 1))*[-1, 1]).T, [-1,0], self.nCx, self.nCx, format="csr")
dz = sp.spdiags((np.ones((self.nCz+1, 1))*[-1, 1]).T, [0,1], self.nCz, self.nCz+1, format="csr")
#2D Difference matricies
Dr = sp.kron(sp.eye(self.nNz), dr)
Dz = -sp.kron(dz, sp.eye(self.nCx)) #Not sure about this negative
#Edge curl operator
self._edgeCurl = sp.diags(1/self.area,0)*sp.vstack((Dz, Dr))*sp.diags(self.edge,0)
return self._edgeCurl
@property
def aveE2CC(self):
"""Averaging operator from cell edges to cell centres"""
if getattr(self, '_aveE2CC', None) is None:
az = sp.spdiags(0.5*np.ones((2, self.nNz)), [-1,0], self.nNz, self.nCz, format='csr')
ar = sp.spdiags(0.5*np.ones((2, self.nCx)), [0, 1], self.nCx, self.nCx, format='csr')
ar[0,0] = 1
self._aveE2CC = sp.kron(az, ar).T
return self._aveE2CC
@property
def aveF2CC(self):
"""Averaging operator from cell faces to cell centres"""
if getattr(self, '_aveF2CC', None) is None:
az = sp.spdiags(0.5*np.ones((2, self.nNz)), [-1,0], self.nNz, self.nCz, format='csr')
ar = sp.spdiags(0.5*np.ones((2, self.nCx)), [0, 1], self.nCx, self.nCx, format='csr')
ar[0,0] = 1
Afr = sp.kron(sp.eye(self.nCz),ar)
Afz = sp.kron(az,sp.eye(self.nCx))
self._aveF2CC = sp.vstack((Afr,Afz)).T
return self._aveF2CC
def getFaceMassDeriv(self):
Av = self.aveF2CC
return Av.T * sdiag(self.vol)
def getEdgeMassDeriv(self):
Av = self.aveE2CC
return Av.T * sdiag(self.vol)
####################################################
# Methods
####################################################
def getMass(self, materialProp=None, loc='e'):
""" Produces mass matricies.
:param None,float,numpy.ndarray materialProp: property to be averaged (see below)
:param str loc: Average to location: 'e'-edges, 'f'-faces
:rtype: scipy.sparse.csr.csr_matrix
:return: M, the mass matrix
materialProp can be::
None -> takes materialProp = 1 (default)
float -> a constant value for entire domain
numpy.ndarray -> if materialProp.size == self.nC
3D property model
if materialProp.size = self.nCz
1D (layered eath) property model
"""
if materialProp is None:
materialProp = np.ones(self.nC)
elif type(materialProp) is float:
materialProp = np.ones(self.nC)*materialProp
elif materialProp.shape == (self.nCz,):
materialProp = materialProp.repeat(self.nCx)
materialProp = mkvc(materialProp)
assert materialProp.shape == (self.nC,), "materialProp incorrect shape"
if loc=='e':
Av = self.aveE2CC
elif loc=='f':
Av = self.aveF2CC
else:
raise ValueError('Invalid loc')
diag = Av.T * (self.vol * mkvc(materialProp))
return sdiag(diag)
def getEdgeMass(self, materialProp=None):
"""mass matrix for products of edge functions w'*M(materialProp)*e"""
return self.getMass(loc='e', materialProp=materialProp)
def getFaceMass(self, materialProp=None):
"""mass matrix for products of face functions w'*M(materialProp)*f"""
return self.getMass(loc='f', materialProp=materialProp)
def getInterpolationMat(self, loc, locType='fz'):
""" Produces intrpolation matrix
:param numpy.ndarray loc: Location of points to interpolate to
:param str locType: What to interpolate (see below)
:rtype: scipy.sparse.csr.csr_matrix
:return: M, the intrpolation matrix
locType can be::
'fz' -> z-component of field defined on faces
'fr' -> r-component of field defined on faces
'et' -> theta-component of field defined on edges
"""
loc = np.atleast_2d(loc)
assert np.all(loc[:,0]<=self.vectorNx.max()) & \
np.all(loc[:,1]>=self.vectorNz.min()) & \
np.all(loc[:,1]<=self.vectorNz.max()), \
"Points outside of mesh"
if locType=='fz':
Q = sp.lil_matrix((loc.shape[0], self.nF), dtype=float)
for i, iloc in enumerate(loc):
# Point is on a z-interface
if np.any(np.abs(self.vectorNz-iloc[1])<0.001):
dFz = self.gridFz-iloc #Distance to z faces
dFz[dFz[:,0]>0,:] = np.inf #Looking for next face to the left...
indL = np.argmin(np.sum(dFz**2, axis=1)) #Closest one
if self.gridFz[indL,0] == self.vectorCCr.max(): #Point in outer half cell (linear extrapolation)
zFL = self.gridFz[indL,:]
zFLL = self.gridFz[indL-1,:]
Q[i, indL+self.nFr] = (iloc[0] - zFLL[0])/(zFL[0] - zFLL[0])
Q[i, indL+self.nFr-1] = -(iloc[0] - zFL[0])/(zFL[0] - zFLL[0])
else:
zFL = self.gridFz[indL,:]
zFR = self.gridFz[indL+1,:]
Q[i,indL+self.nFr] = (zFR[0] - iloc[0])/(zFR[0] - zFL[0])
Q[i,indL+self.nFr+1] = (iloc[0] - zFL[0])/(zFR[0] - zFL[0])
# Point is in a cell
else:
dFz = self.gridFz-iloc
dFz[dFz>0] = np.inf
dFz = np.sum(dFz**2, axis=1)
indBL = np.argmin(dFz) # Face below and to the left
indAL = indBL + self.nCx # Face above and to the left
zF_BL = self.gridFz[indBL,:]
zF_AL = self.gridFz[indAL,:]
dzB = iloc[1] - zF_BL[1] # z-distance to face below
dzA = zF_AL[1] - iloc[1] # z-distance to face above
if self.gridFz[indBL,0] == self.vectorCCr.max(): #Point in outer half cell (linear extrapolation)
zF_BLL = self.gridFz[indBL-1,:]
zF_ALL = self.gridFz[indAL-1,:]
DZ = zF_AL[1] - zF_BL[1]
DR = zF_AL[0] - zF_ALL[0]
drL = iloc[0] - zF_AL[0]
drLL = iloc[0] - zF_ALL[0]
Q[i, indBL+self.nFr-1] = -(1 - dzB/DZ)*(drL/DR)
Q[i, indBL+self.nFr] = (1 - dzB/DZ)*(drLL/DR)
Q[i, indAL+self.nFr-1] = -(dzB/DZ)*(drL/DR)
Q[i, indAL+self.nFr] = (dzB/DZ)*(drLL/DR)
else:
indBR = indBL+1 # Face below and to the right
indAR = indAL + 1 # Face above and to the right
zF_BR = self.gridFz[indBR,:]
drL = iloc[0] - zF_BL[0] # r-distance to face on left
drR = zF_BR[0] - iloc[0] # r-distance to face on right
drz = (drL + drR)*(dzB + dzA)
Q[i,indBL+self.nFr] = drR*dzA/drz
Q[i,indBR+self.nFr] = drL*dzA/drz
Q[i,indAL+self.nFr] = drR*dzB/drz
Q[i,indAR+self.nFr] = drL*dzB/drz
elif locType=='fr':
raise NotImplementedError('locType==fr')
elif locType=='et':
raise NotImplementedError('locType==et')
else:
raise ValueError('Invalid locType')
return Q.tocsr()
def getNearest(self, loc, locType):
""" Returns the index of the closest face or edge to a given location
:param numpy.ndarray loc: Test point
:param str locType: Type of location desired (see below)
:rtype: int
:return: ind:
locType can be::
'fz' -> location of nearest z-face
'fr' -> location of nearest r-face
'et' -> location of nearest edge
"""
if locType=='et':
dr = self.gridN[:,0] - loc[0]
dz = self.gridN[:,1] - loc[1]
elif locType=='fz':
dr = self.gridFz[:,0] - loc[0]
dz = self.gridFz[:,1] - loc[1]
elif locType=='fr':
dr = self.gridFr[:,0] - loc[0]
dz = self.gridFr[:,1] - loc[1]
else:
raise ValueError('Invalid locType')
R = np.sqrt(dr**2 + dz**2)
ind = np.argmin(R)
return ind