Files
simpeg/SimPEG/utils.py
T

331 lines
9.1 KiB
Python

import numpy as np
def mkvc(x, numDims=1):
"""Creates a vector with the number of dimension specified
e.g.:
a = np.array([1, 2, 3])
mkvc(a, 1).shape
> (3, )
mkvc(a, 2).shape
> (3, 1)
mkvc(a, 3).shape
> (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:
return x.flatten(order='F')
elif numDims == 2:
return x.flatten(order='F')[:, np.newaxis]
elif numDims == 3:
return x.flatten(order='F')[:, np.newaxis, np.newaxis]
def ndgrid(*args, **kwargs):
"""
Form tensorial grid for 1, 2, or 3 dimensions.
Returns as column vectors by default.
To return as matrix input:
ndgrid(..., vector=False)
The inputs can be a list or separate arguments.
e.g.
a = np.array([1, 2, 3])
b = np.array([1, 2])
XY = ndgrid(a, b)
> [[1 1]
[2 1]
[3 1]
[1 2]
[2 2]
[3 2]]
X, Y = ndgrid(a, b, vector=False)
> X = [[1 1]
[2 2]
[3 3]]
> Y = [[1 2]
[1 2]
[1 2]]
"""
# Read the keyword arguments, and only accept a vector=True/False
vector = kwargs.pop('vector', True)
assert type(vector) == bool, "'vector' keyword must be a bool"
assert len(kwargs) == 0, "Only 'vector' keyword accepted"
# you can either pass a list [x1, x2, x3] or each seperately
if type(args[0]) == list:
xin = args[0]
else:
xin = args
# Each vector needs to be a numpy array
assert np.all([type(x) == np.ndarray for x in xin]), "All vectors must be numpy arrays."
if len(xin) == 1:
return xin[0]
elif len(xin) == 2:
XY = np.broadcast_arrays(mkvc(xin[1], 1), mkvc(xin[0], 2))
if vector:
X2, X1 = [mkvc(x) for x in XY]
return np.c_[X1, X2]
else:
return XY[1], XY[0]
elif len(xin) == 3:
XYZ = np.broadcast_arrays(mkvc(xin[2], 1), mkvc(xin[1], 2), mkvc(xin[0], 3))
if vector:
X3, X2, X1 = [mkvc(x) for x in XYZ]
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
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'
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)
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/07/26
"""
assert type(nodes) == str, "Nodes must be a str variable: e.g. 'ABCD'"
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 gridSize.size == 2 else 'ABCDEFGH'
for node in nodes:
assert node in possibleNodes, "Nodes must be chosen from: '%s'" % possibleNodes
dim = gridSize.size
if n is None:
n = gridSize - 1
if dim == 2:
ij = ndgrid(np.arange(n[0]), np.arange(n[1]))
i, j = ij[:, 0], ij[:, 1]
elif dim == 3:
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.')
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(gridSize, np.c_[i+shift[0], j+shift[1]]).flatten(), )
elif dim == 3:
out += (sub2ind(gridSize, np.c_[i+shift[0], j+shift[1], k+shift[2]]).flatten(), )
return out
def faceInfo(xyz, A, B, C, D, average=True, normalizeNormals=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
"""
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
# | |
# | |
# 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: 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.
N = (nA + nB + nC + nD)/4 # this is intrinsically weighted by area
# normalize
N = normalize(N)
else:
if normalizeNormals:
N = [normalize(nA), normalize(nB), normalize(nC), normalize(nD)]
else:
N = [nA, nB, nC, 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.
#
# WARNING: This does not compute correctly for concave quadrilaterals
area = (length(nA)+length(nB)+length(nC)+length(nD))/4
return N, area
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 = np.array(mult).reshape(len(mult))
sub = []
for i in range(0, len(shape)):
sub.extend([np.math.floor(ind / mult[i])])
ind = ind - (np.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 = np.array(mult).reshape(len(mult), 1)
idx = np.dot((subs), (mult))
return idx
def getSubArray(A, ind):
"""subArray"""
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.")