def interpolate(clas,F,G,div): """Create interpolations between two Coords. F and G are two Coords with the same shape. v is a list of floating point values. The result is the concatenation of the interpolations of F and G at all the values in div. An interpolation of F and G at value v is a Coords H where each coordinate Hijk is obtained from: Hijk = Fijk + v * (Gijk-Fijk). Thus, a Coords interpolate(F,G,[0.,0.5,1.0]) will contain all points of F and G and all points with mean coordinates between those of F and G. As a convenience, if an integer is specified for div, it is taken as a number of divisions for the interval [0..1]. Thus, interpolate(F,G,n) is equivalent with interpolate(F,G,arange(0,n+1)/float(n)) The resulting Coords array has an extra axis (the first). Its shape is (n,) + F.shape, where n is the number of divisions. """ if F.shape != G.shape: raise RuntimeError,"Expected Coords objects with equal shape!" if type(div) == int: div = arange(div+1) / float(div) else: div = array(div).ravel() return F + outer(div,G-F).reshape((-1,)+F.shape)
def __new__(cls, data=None, dtyp=Float, copy=False): """Create a new instance of class Coords. If no data are given, a single point (0.,0.,0.) will be created. If specified, data should evaluate to an (...,3) shaped array of floats. If copy==True, the data are copied. If no dtyp is given that of data are used, or float32 by default. """ if data is None: data = zeros((3,),dtype=Float) # Turn the data into an array, and copy if requested ar = array(data, dtype=dtyp, copy=copy) if ar.shape[-1] == 3: pass elif ar.shape[-1] in [1,2]: ar = concatenate([ar,zeros(ar.shape[:-1]+(3-ar.shape[-1],))],axis=-1) else: raise ValueError,"Expected a length 1,2 or 3 for last array axis" # Make sure dtype is a float type if ar.dtype.kind != 'f': ar = ar.astype(Float) # Transform 'subarr' from an ndarray to our new subclass. ar = ar.view(cls) return ar
def rotationMatrix(angle,axis=None): """Return a rotation matrix over angle, optionally around axis. The angle is specified in degrees. If axis==None (default), a 2x2 rotation matrix is returned. Else, axis should specifying the rotation axis in a 3D world. It is either one of 0,1,2, specifying a global axis, or a vector with 3 components specifying an axis through the origin. In either case a 3x3 rotation matrix is returned. Note that: rotationMatrix(angle,[1,0,0]) == rotationMatrix(angle,0) rotationMatrix(angle,[0,1,0]) == rotationMatrix(angle,1) rotationMatrix(angle,[0,0,1]) == rotationMatrix(angle,2) but the latter functions calls are more efficient. The result is returned as an array. """ a = angle*rad c = cos(a) s = sin(a) if axis==None: f = [[c,s],[-s,c]] elif type(axis) == int: f = [[0.0 for i in range(3)] for j in range(3)] axes = range(3) i,j,k = axes[axis:]+axes[:axis] f[i][i] = 1.0 f[j][j] = c f[j][k] = s f[k][j] = -s f[k][k] = c else: t = 1-c X,Y,Z = axis f = [ [ t*X*X + c , t*X*Y + s*Z, t*X*Z - s*Y ], [ t*Y*X - s*Z, t*Y*Y + c , t*Y*Z + s*X ], [ t*Z*X + s*Y, t*Z*Y - s*X, t*Z*Z + c ] ] return array(f)
def fuse(self,nodesperbox=1,shift=0.5,rtol=1.e-5,atol=1.e-5): """Find (almost) identical nodes and return a compressed set. This method finds the points that are very close and replaces them with a single point. The return value is a tuple of two arrays: - the unique points as a Coords object, - an integer (nnod) array holding an index in the unique coordinates array for each of the original nodes. This index will have the same shape as the pshape() of the coords array. The procedure works by first dividing the 3D space in a number of equally sized boxes, with a mean population of nodesperbox. The boxes are numbered in the 3 directions and a unique integer scalar is computed, that is then used to sort the nodes. Then only nodes inside the same box are compared on almost equal coordinates, using the numpy allclose() function. Two coordinates are considered close if they are within a relative tolerance rtol or absolute tolerance atol. See numpy for detail. The default atol is set larger than in numpy, because pyformex typically runs with single precision. Close nodes are replaced by a single one. Currently the procedure does not guarantee to find all close nodes: two close nodes might be in adjacent boxes. The performance hit for testing adjacent boxes is rather high, and the probability of separating two close nodes with the computed box limits is very small. Nevertheless we intend to access this problem by repeating the procedure with the boxes shifted in space. """ x = self.points() nnod = x.shape[0] # Calculate box size lo = array([ x[:,i].min() for i in range(3) ]) hi = array([ x[:,i].max() for i in range(3) ]) sz = hi-lo esz = sz[sz > 0.0] # only keep the nonzero dimensions if esz.size == 0: # All points are coincident x = x[:1] e = zeros(nnod,dtype=int32) return x,e vol = esz.prod() nboxes = nnod / nodesperbox # ideal total number of boxes boxsz = (vol/nboxes) ** (1./esz.shape[0]) nx = (sz/boxsz).astype(int32) # avoid error message on the global sz/nx calculation errh = seterr(all='ignore') dx = where(nx>0,sz/nx,boxsz) seterr(**errh) # nx = array(nx) + 1 ox = lo - dx*shift # origin : 0 < shift < 1 # Create box coordinates for all nodes ind = floor((x-ox)/dx).astype(int32) # Create unique box numbers in smallest direction first o = argsort(nx) val = ( ind[:,o[2]] * nx[o[2]] + ind[:,o[1]] ) * nx[o[1]] + ind[:,o[0]] # sort according to box number srt = argsort(val) # rearrange the data according to the sort order val = val[srt] x = x[srt] # now compact # make sure we use int32 (for the fast fuse function) # Using int32 limits this procedure to 10**9 points, which is more # than enough for all practical purposes x = x.astype(float32) val = val.astype(int32) flag = ones((nnod,),dtype=int32) # 1 = new, 0 = existing node sel = arange(nnod).astype(int32) # replacement unique node nr tol = float32(max(abs(rtol*self.sizes()).max(),atol)) if hasattr(misc,'fuse'): # use the lib misc.fuse(x,val,flag,sel,tol) else: # !!!! this code should be moved to the emulated lib ! for i in range(nnod): j = i-1 while j>=0 and val[i]==val[j]: if allclose(x[i],x[j],rtol=rtol,atol=atol): # node i is same as node j flag[i] = 0 sel[i] = sel[j] sel[i+1:nnod] -= 1 break j = j-1 x = x[flag>0] # extract unique nodes s = sel[argsort(srt)] # and indices for old nodes return (x,s.reshape(self.shape[:-1]))