Source code for ilpm.networks

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from matplotlib.collections import LineCollection
from matplotlib.collections import PatchCollection
import matplotlib.path as mplpath
from scipy.spatial import Delaunay
import copy
import vector
import pylab as P

'''Functions for handling data for networks of points and bonds.
Also includes functions for creating lattices from xy, NL, KL, BL, etc.
NPM 2015-6
'''


###################################################
# Below is the entire Data Handling Section and string formatting section
###################################################
[docs]def distance_periodic(xy, com, LL): """Compute the distance of xy points from com given periodic rectangular BCs with extents LL Parameters ---------- xy : NP x 2 float array Positions to evaluate distance in periodic rectangular domain com : 1 x 2 float array position to compute distances with respect to LL : tuple of floats spatial extent of each periodic dimension (ie x(LL[0]) = x(0), y(LL[1]) = y(0)) Returns ------- dist : len(xy) x 1 float array distance of xy from com given periodic boundaries in 2d""" dist2d = np.abs(xy - com) dist2d[dist2d[:, 0] > LL[0] * 0.5, 0] -= LL[0] dist2d[dist2d[:, 1] > LL[1] * 0.5, 1] -= LL[1] return np.sqrt(dist2d[:, 0] ** 2 + dist2d[:, 1] ** 2)
[docs]def center_of_mass(xy, masses): """Compute the center of mass for a 2D finite, non-PBC system. Parameters ---------- xy : NP x 2 float array The positions of the particles/masses/weighted objects masses : NP x 1 float array The weight to attribute to each particle. Returns ------- com : 2 x 1 float array The position in simulation space of the periodic center of mass """ return np.sum(masses.reshape(len(xy), 1) * xy.astype(np.float), axis=0) / float(np.sum(masses))
[docs]def com_periodic(xy, LL, masses=1.): """Compute the center of mass for a 2D periodic system. When a cluster straddles the periodic boundary, a naive calculation of the center of mass will be incorrect. A generalized method for calculating the center of mass for periodic systems is to treat each coordinate, x and y, as if it were on a circle instead of a line. The calculation takes every particle's x coordinate and maps it to an angle, averages the angle, then maps back. Parameters ---------- xy : NP x 2 float array The positions of the particles/masses/weighted objects LL : masses : NP x 1 float array or float The weight to attribute to each particle. If masses is a float, it is ignored (all particles weighted equally) Returns ------- com : 2 x 1 float array The position in simulation space of the periodic center of mass """ # test case: # import lepm.lattice_elasticity as le # import matplotlib.pyplot as plt # import numpy as np # xy = np.random.rand(100, 2) - np.array([0.5, 0.5]) # LL = (1.0, 1.0) # plt.scatter(xy[:, 0], xy[:, 1]) # com = le.com_periodic(xy, LL) # plt.plot(com[0], com[1], 'ro') # plt.show() minxy = np.min(xy, axis=0) if isinstance(LL, tuple): LL = np.array([LL[0], LL[1]]) # map to xi and zeta coordinates. Each xi element has x component and y component. if isinstance(masses, np.ndarray): xi = np.cos(((xy - minxy) / LL) * 2. * np.pi) * masses.reshape(len(masses), 1) zeta = np.sin(((xy - minxy) / LL) * 2. * np.pi) * masses.reshape(len(masses), 1) else: xi = np.cos(((xy - minxy) / LL) * 2. * np.pi) zeta = np.sin(((xy - minxy) / LL) * 2. * np.pi) # average to get center of mass on each circle xibar = np.mean(xi, axis=0) zetabar = np.mean(zeta, axis=0) thetabar = np.arctan2(-zetabar, -xibar) + np.pi com = LL * thetabar / (2. * np.pi) + minxy return com
[docs]def running_mean(x, N): """Compute running mean of an array x, averaged over a window of N elements. If the array x is 2d, then a running mean is performed on each row of the array. Parameters ---------- x : N x (1 or 2) array The array to take a running average over N : int The window size of the running average Returns ------- output : 1d or 2d array The averaged array (each row is averaged if 2d), preserving datatype of input array x """ if len(np.shape(x)) == 1: cumsum = np.cumsum(np.insert(x, 0, 0)) return (cumsum[N:] - cumsum[:-N]) / N elif len(np.shape(x)) == 2: # Apply same reasoning to the array row-by-row dmyi = 0 for row in x: tmpsum = np.cumsum(np.insert(row, 0, 0)) outrow = (tmpsum[N:] - tmpsum[:-N]) / N if dmyi == 0: outarray = np.zeros((np.shape(x)[0], len(outrow)), dtype=x.dtype) outarray[dmyi, :] = outrow dmyi += 1 return outarray else: raise RuntimeError('Input array x in running_mean(x, N) must be 1d or 2d.')
[docs]def interpol_meshgrid(x, y, z, n, method='nearest'): """Interpolate z on irregular or unordered grid data (x,y) by supplying # points along each dimension. Note that this does not guarantee a square mesh, if ranges of x and y differ. Parameters ---------- x : unstructured 1D array data along first dimension y : unstructured 1D array data along second dimension z : 1D array values evaluated at x,y n : int number of spacings in meshgrid of unstructured xy data method : {'linear', 'nearest', 'cubic'}, optional Method of interpolation. One of ``nearest`` return the value at the data point closest to the point of interpolation. See `NearestNDInterpolator` for more details. ``linear`` tesselate the input point set to n-dimensional simplices, and interpolate linearly on each simplex. See `LinearNDInterpolator` for more details. ``cubic`` (1-D) return the value determined from a cubic spline. ``cubic`` (2-D) return the value determined from a piecewise cubic, continuously differentiable (C1), and approximately curvature-minimizing polynomial surface. See `CloughTocher2DInterpolator` for more details. Returns ------- X : n x 1 float array meshgrid of first dimension X : n x 1 float array meshgrid of second dimension Zm : n x 1 float array (interpolated) values of z on XY meshgrid, with nans masked """ # define regular grid spatially covering input data xg = np.linspace(x.min(), x.max(), n) yg = np.linspace(y.min(), y.max(), n) X, Y = np.meshgrid(xg, yg) # interpolate Z values on defined grid Z = interpolate.griddata(np.vstack((x.flatten(), y.flatten())).T, np.vstack(z.flatten()), (X, Y), method=method).reshape(X.shape) # mask nan values, so they will not appear on plot Zm = np.ma.masked_where(np.isnan(Z), Z) return X, Y, Zm
[docs]def dist_pts(pts, nbrs, dim=-1, square_norm=False): """ Compute the distance between all pairs of two sets of points, returning an array of distances, in an optimized way. Parameters ---------- pts: N x 2 array (float or int) points to measure distances from nbrs: M x 2 array (float or int) points to measure distances to dim: int (default -1) dimension along which to measure distance. Default is -1, which measures the Euclidean distance in 2D square_norm: bool Abstain from taking square root, so that if dim==-1, returns the square norm (distance squared). Returns ------- dist : N x M float array i,jth element is distance between pts[i] and nbrs[j], along dimension specified (default is normed distance) """ if dim < 1: Xarr = np.ones((len(pts), len(nbrs)), dtype=float) * nbrs[:, 0] # Computing dist(x) # gxy_x = np.array([gxy_Xarr[i] - xyip[i,0] for i in range(len(xyip)) ]) dist_x = Xarr - np.dstack(np.array([pts[:, 0].tolist()] * np.shape(Xarr)[1]))[0] if np.abs(dim) > 0.5: Yarr = np.ones((len(pts), len(nbrs)), dtype=float) * nbrs[:, 1] # Computing dist(y) # gxy_y = np.array([gxy_Yarr[i] - xyip[i,1] for i in range(len(xyip)) ]) dist_y = Yarr - np.dstack(np.array([pts[:, 1].tolist()] * np.shape(Yarr)[1]))[0] if dim == -1: dist = dist_x ** 2 + dist_y ** 2 if not square_norm: dist = np.sqrt(dist) return dist elif dim == 0: return dist_x elif dim == 1: return dist_y
[docs]def diff_matrix(AA, BB): """ Compute the difference between all pairs of two sets of values, returning an array of differences. Parameters ---------- pts: N x 1 array (float or int) points to measure distances from nbrs: M x 1 array (float or int) points to measure distances to Returns ------- Mdiff : N x M float array i,jth element is difference between AA[i] and BB[j] """ arr = np.ones((len(AA), len(BB)), dtype=float) * BB # gxy_x = np.array([gxy_Xarr[i] - xyip[i,0] for i in range(len(xyip)) ]) Mdiff = arr - np.dstack(np.array([AA.tolist()] * np.shape(arr)[1]))[0] return Mdiff
[docs]def sort_arrays_by_first_array(arr2sort, arrayList2sort): """Sort many arrays in the same way, based on sorting of first array Parameters ---------- arr2sort : N x 1 array Array to sort arrayList2sort : List of N x 1 arrays Other arrays to sort, by the indexing of arr2sort Returns ---------- arr_s, arrList_s : sorted N x 1 arrays """ IND = np.argsort(arr2sort) arr_s = arr2sort[IND] arrList_s = [] for arr in arrayList2sort: arrList_s.append(arr[IND]) return arr_s, arrList_s
[docs]def count_NN(KL): """Count how many nearest neighbors each particle has. Parameters ---------- KL : NP x max(#neighbors) array Connectivity matrix. All nonzero elements (whether =1 or simply !=0) are interpreted as connections. Returns ---------- zvals : NP x 1 int array The ith element gives the number of neighbors for the ith particle """ zvals = (KL != 0).sum(1) return zvals
[docs]def NL2NLdict(NL, KL): """Convert NL into a dictionary which gives neighbors (values) of the particle index (key). Parameters ---------- NL : array of dimension #pts x max(#neighbors) The ith row contains indices for the neighbors for the ith point. KL : NP x max(#neighbors) array Connectivity matrix. All nonzero elements (whether =1 or simply !=0) are interpreted as connections. Returns ---------- NLdict : dict NLdict[i] returns an array listing neighbors of the ith particle, where i is an integer. """ ii = 0 NLdict = {} for row in NL: connections = np.where(KL[ii] != 0)[0] NLdict[int(ii)] = row[connections] ii += 1 return NLdict
[docs]def Tri2BL(TRI): """Convert triangulation array (#tris x 3) to bond list (#bonds x 2) for 2D lattice of triangulated points. Parameters ---------- TRI : array of dimension #tris x 3 Each row contains indices of the 3 points lying at the vertices of the tri. Returns ---------- BL : array of dimension #bonds x 2 Each row is a bond and contains indices of connected points""" BL1 = TRI[:, [0, 1]] BL2 = np.vstack((BL1, TRI[:, [0, 2]])) BL3 = np.vstack((BL2, TRI[:, [1, 2]])) BLt = np.sort(BL3, axis=1); # select unique rows of BL BL = np.unique(BLt.view(np.dtype((np.void, BLt.dtype.itemsize * BLt.shape[1])))).view(BLt.dtype).reshape(-1, BLt.shape[ 1]) # BL = unique_rows(BLt) return BL
[docs]def BL2TRI(BL, xy): """Convert bond list (#bonds x 2) to Triangulation array (#tris x 3) (using dictionaries for speedup and scaling) Parameters ---------- BL : array of dimension #bonds x 2 Each row is a bond and contains indices of connected points Returns ---------- TRI : array of dimension #tris x 3 Each row contains indices of the 3 points lying at the vertices of the tri. """ d = {} # preallocate for speed tri = np.zeros((len(BL), 3), dtype=np.int) # c is dmy index to fill up and cut off tri c = 0 for i in BL: # reorder row if [big, small] if (i[0] > i[1]): t = i[0] i[0] = i[1] i[1] = t # Check if small val in row is key of dict d. # If not, then initialize the key, value pair. if (i[0] in d): d[i[0]].append(i[1]) else: d[i[0]] = [i[1]] # From dict d, make TRI for key in d: for n in d[key]: for n2 in d[key]: if (n > n2) or n not in d: continue if (n2 in d[n]): tri[c, :] = [key, n, n2] c += 1 tri = tri[0:c] # Check for points inside each triangle. If they exist, remove that triangle keep = np.ones(len(tri), dtype=bool) index = 0 for row in tri: mask = np.ones(len(xy), dtype=bool) mask[row] = False remove = where_points_in_triangle(xy[mask, :], xy[row[0], :], xy[row[1], :], xy[row[2], :]) if remove.any(): keep[index] = False # if check: # plt.triplot(xy[:,0],xy[:,1], tri, 'g.-') # plt.plot(xy[row,0], xy[row,1],'ro') # plt.show() index += 1 TRI = tri[keep] return TRI
[docs]def where_points_in_triangle(pts, v1, v2, v3): """Determine whether point pt is inside triangle with vertices v1,v2,v3, in 2D. """ b1 = cross_pts_triangle(pts, v1, v2) < 0.0 b2 = cross_pts_triangle(pts, v2, v3) < 0.0 b3 = cross_pts_triangle(pts, v3, v1) < 0.0 return np.logical_and((b1 == b2), (b2 == b3))
[docs]def cross_pts_triangle(p1, p2, p3): """Return the cross product for arrays of triangles or triads of points """ return ((p1[:, 0] - p3[0]) * (p2[1] - p3[1]) - (p2[0] - p3[0]) * (p1[:, 1] - p3[1]))
[docs]def memberIDs(a, b): """Return array (c) of indices where elements of a are members of b. If ith a elem is member of b, ith elem of c is index of b where a[i] = b[index]. If ith a elem is not a member of b, ith element of c is 'None'. The speed is O(len(a)+len(b)), so it's fast. """ bind = {} for i, elt in enumerate(b): if elt not in bind: bind[elt] = i return [bind.get(itm, None) for itm in a] # None can be replaced by any other "not in b" value
[docs]def ismember(a, b): """Return logical array (c) testing where elements of a are members of b. The speed is Order(len(a)+len(b)), so it's fast. """ bind = {} for i, elt in enumerate(b): if elt not in bind: bind[elt] = True return np.array([bind.get(itm, False) for itm in a]) # None can be replaced by any other "not in b" value
[docs]def unique_rows(a): """Clean up an array such that all its rows are unique. Reference: http://stackoverflow.com/questions/7989722/finding-unique-points-in-numpy-array Parameters ---------- a : N x M array of variable dtype array from which to return only the unique rows """ return np.array(list(set(tuple(p) for p in a)))
[docs]def unique_rows_threshold(a, thres): """Clean up an array such that all its rows are at least 'thres' different in value. Reference: http://stackoverflow.com/questions/8560440/removing-duplicate-columns-and-rows-from-a-numpy-2d-array Parameters ---------- a : N x M array of variable dtype array from which to return only the unique rows thres : float threshold for deleting a row that has slightly different values from another row Returns ---------- a : N x M array of variable dtype unique rows of input array """ if a.ndim > 1: order = np.lexsort(a.T) a = a[order] diff = np.diff(a, axis=0) ui = np.ones(len(a), 'bool') ui[1:] = (np.abs(diff) > thres).any(axis=1) else: a = np.sort(a) diff = np.diff(a, axis=0) ui = np.ones(len(a), 'bool') ui[1:] = np.abs(diff) > thres return a[ui]
[docs]def args_unique_rows_threshold(a, thres): """Clean up an array such that all its rows are at least 'thres' different in value. Reference: http://stackoverflow.com/questions/8560440/removing-duplicate-columns-and-rows-from-a-numpy-2d-array Parameters ---------- a : N x M array of variable dtype array from which to return only the unique rows thres : float threshold for deleting a row that has slightly different values from another row Returns ---------- a : N x M array of variable dtype unique rows of input array order : N x 1 int array indices used to sort a in order ui : N x 1 boolean array True where row of a[order] is unique. """ order = np.lexsort(a.T) a = a[order] diff = np.diff(a, axis=0) ui = np.ones(len(a), 'bool') ui[1:] = (np.abs(diff) > thres).any(axis=1) return a[ui], order, ui
[docs]def rows_matching(BL, indices): """Find rows with elements matching elements of the supplied array named 'indices'. Right now just set up for N x 2 int arrays. --> GENERALIZE Returns row indices of BL whose row elements are ALL contained in array named 'indices'. """ inBL0 = np.in1d(BL[:, 0], indices) # print 'BLUC[:,0] = ', BL[:,0] # print 'indices = ', indices # print 'inBL0 = ', inBL0 inBL1 = np.in1d(BL[:, 1], indices) matches = np.where(np.logical_and(inBL0, inBL1))[0] return matches
[docs]def minimum_distance_between_pts(R): """ Parameters ---------- R : NP x ndim array the points over which to find the min distance between points Returns ---------- min_dist : float the minimum distance between points """ for ind in range(len(R)): mask = np.ones(len(R), dtype=bool) mask[ind] = 0 row = R[ind] # dist = scipy.spatial.distance.cdist(R,row) dist = np.sqrt(np.sum((R - row) ** 2, axis=1)) if ind == 0: min_dist = np.min(dist[mask]) else: min_dist = min(min_dist, np.min(dist[mask])) return min_dist
[docs]def find_nearest(array, value): """return the value in array that is nearest to the supplied value""" idx = (np.abs(array - value)).argmin() return array[idx]
[docs]def NL2BL(NL, KL): """Convert neighbor list and KL (NPxNN) to bond list (#bonds x 2) for lattice of bonded points. Parameters ---------- NL : array of dimension #pts x max(#neighbors) The ith row contains indices for the neighbors for the ith point. KL : NP x max(#neighbors) array Connectivity matrix. All nonzero elements (whether =1 or simply !=0) are interpreted as connections. Returns ---------- BL : array of dimension #bonds x 2 Each row is a bond and contains indices of connected points. Values are negative for periodic BCs. """ # reshape to get BL array of particle pairs. Below, 't' stands for 'temporary'. BLttnormal = np.asarray([[i, NL[i, j]] for i in range(len(KL)) for j in np.where(KL[i, :] > 0)[0]]) BLt = np.sort(BLttnormal) if (KL.ravel() == -1).any(): # print 'detected periodic BCs...' BLperiodic = np.asarray([[-i, -NL[i, j]] for i in range(len(KL)) for j in np.where(KL[i, :] == -1)[0]]) # print 'BLperiodic = ', BLperiodic BL2 = np.sort(BLperiodic) BLt = np.vstack((BLt, np.dstack((BL2[:, 1], BL2[:, 0]))[0])) # select unique rows of BL # BL = np.unique(BLt.view(np.dtype((np.void, BLt.dtype.itemsize*BLt.shape[1])))).view(BLt.dtype).reshape(-1, BLt.shape[1]) BL = unique_rows(BLt) return BL.astype(np.intc)
[docs]def KL2kL(NL, KL, BL): """Convert neighbor list to bond list (#bonds x 2) for lattice of bonded points. Parameters ---------- NL : array of dimension #pts x max(#neighbors) The ith row contains indices for the neighbors for the ith point. KL : array of dimension #pts x (max number of neighbors) Spring constant list, where nonzero corresponds to a true connection while 0 signifies that there is not a connection. BL : array of dimension #bonds x 2 Each row is a bond and contains indices of connected points Returns ---------- kL : array of length #bonds The ith element is the spring constant of the ith bond in BL, taken from KL """ # cycle through BL, finding matching inds in NL and thus KL # for row in BL, get KL value in row BL[i,0] and col where(NL[BL[i,0],:]==BL[i,1])[0] kL = np.array([KL[BL[i, 0], np.where(NL[BL[i, 0], :] == BL[i, 1])[0]][0] for i in range(len(BL))]) return kL
[docs]def BL2KL(BL, NL): """Convert the bond list (#bonds x 2) to connectivity list using NL for its structure. Parameters ---------- BL : array of dimension #bonds x 2 Each row is a bond and contains indices of connected points NL : array of dimension #pts x max(#neighbors) The ith row contains indices for the neighbors for the ith point. Returns ---------- KL : array of dimension #pts x (max number of neighbors) Spring constant list, where 1 corresponds to a true connection while 0 signifies that there is not a connection. """ # NP, NN = np.shape(NL) # check for periodic bonds if (BL.ravel() < 0).any(): # periodic bonds exist KL = np.zeros_like(NL) for row in BL: if (row < 0).any(): KL[row[0], np.where(NL[abs(row[0])] == abs(row[1]))[0][0]] = -1 KL[row[1], np.where(NL[abs(row[1])] == abs(row[0]))[0][0]] = -1 else: KL[row[0], np.where(NL[abs(row[0])] == abs(row[1]))[0][0]] = 1 KL[row[1], np.where(NL[abs(row[1])] == abs(row[0]))[0][0]] = 1 # print 'KL =', KL else: # all regular bonds KL = np.zeros_like(NL) for row in BL: KL[row[0], np.where(NL[row[0]] == row[1])[0][0]] = 1 KL[row[1], np.where(NL[row[1]] == row[0])[0][0]] = 1 # print 'KL =', KL return KL
[docs]def BL2NLandKL(BL, NP='auto', NN='min'): """Convert bond list (#bonds x 2) to neighbor list (NL) and connectivity list (KL) for lattice of bonded points. Returns KL as ones where there is a bond and zero where there is not. (Even if you just want NL from BL, you have to compute KL anyway.) Note that this makes no attempt to preserve any previous version of NL, which in the philosophy of these simulations should remain constant during a simulation. If NL is known, use BL2KL instead, which creates KL according to the existing NL. Parameters ---------- BL : array of dimension #bonds x 2 Each row is a bond and contains indices of connected points NP : int number of points (defines the length of NL and KL) NN : int maximum number of neighbors (defines the width of NL and KL) Returns ---------- NL : array of dimension #pts x max(#neighbors) The ith row contains indices for the neighbors for the ith point. KL : array of dimension #pts x (max number of neighbors) Spring constant list, where 1 corresponds to a true connection while 0 signifies that there is not a connection. """ if NN == 'min': # Start with many many nearest neighbors, and cut columns until its ok NN = 20 # pick NN so that it is inconceviable to have more than NN neighbors trimNN = True else: trimNN = False if NP == 'auto': if BL.size > 0: NL = np.zeros((max(BL.ravel()) + 1, NN), dtype=np.intc) KL = np.zeros((max(BL.ravel()) + 1, NN), dtype=np.intc) else: raise RuntimeError('ERROR: there is no BL to use to define NL and KL, so cannot run BL2NLandKL()') else: NL = np.zeros((NP, NN), dtype=np.intc) KL = np.zeros((NP, NN), dtype=np.intc) # print 'shape(NL) =', np.shape(NL) # print 'shape(KL) =', np.shape(KL) if BL.size > 0: if (BL.ravel() < 0).any(): # periodic bonds exist for row in BL: col = np.where(KL[abs(row[0]), :] == 0)[0][0] NL[abs(row[0]), col] = abs(row[1]) if (row < 0).any(): KL[abs(row[0]), col] = -1 else: KL[abs(row[0]), col] = 1 col = np.where(KL[abs(row[1]), :] == 0)[0][0] NL[abs(row[1]), col] = abs(row[0]) if (row < 0).any(): KL[abs(row[1]), col] = -1 else: KL[abs(row[1]), col] = 1 else: for row in BL: # print 'row = ', row # print 'KL = ', KL # print np.where(KL[row[0],:]==0)[0] # print KL # print NL col = np.where(KL[row[0], :] == 0)[0][0] NL[row[0], col] = row[1] KL[row[0], col] = 1 col = np.where(KL[row[1], :] == 0)[0][0] NL[row[1], col] = row[0] KL[row[1], col] = 1 if trimNN: NLout, KLout = trim_cols_NLandKL(NL, KL) else: NLout, KLout = NL, KL return NLout, KLout
[docs]def NL2BM(xy, NL, KL, PVx=None, PVy=None): """Convert bond list (#bonds x 2) to bond matrix (#pts x #nn) for lattice of bonded points. Parameters ---------- xy : array of dimension nxd 2D lattice of points (positions x,y(,z) ) NL : int array of dimension #pts x max(#neighbors) The ith row contains indices for the neighbors for the ith point. KL : array of dimension #pts x (max number of neighbors) spring connection/constant list, where one corresponds to a true connection while 0 signifies that there is not a connection. PVx : NP x NN float array (optional, for periodic lattices) ijth element of PVx is the x-component of the vector taking NL[i,j] to its image as seen by particle i PVy : NP x NN float array (optional, for periodic lattices) ijth element of PVy is the y-component of the vector taking NL[i,j] to its image as seen by particle i Returns ---------- BM : array of length #pts x max(#neighbors) The (i,j)th element is the bond length of the bond connecting the ith particle to its jth neighbor (the particle with index NL[i,j]). """ BM = np.zeros_like(NL).astype(float) NL = NL.astype(int) if np.shape(xy)[1] > 3: print '\n\n#####################################################' + \ 'WARNING! xy supplied to NL2BM() has dim > 3.' + \ 'Using only first TWO dimensions to measure distances.' + \ '#####################################################' xy = xy[:, 0:2] for i in range(len(NL)): BM[i] = KL[i] * vector.mag(xy[NL[i], :] - xy[i, :]) # Check for periodic bonds/boundaries. Then calc bond lengths if (KL < 0).any(): try: # There are periodic bonds # print 'np.where(KL < 0) = ', np.where(KL < 0) for (i, j) in zip(np.where(KL < 0)[0], np.where(KL < 0)[1]): BM[i, j] = vector.mag(xy[NL[i, j]] + np.array([PVx[i, j], PVy[i, j]]) - xy[i, :]) except TypeError: raise RuntimeError('When calling NL2BM() on a network with periodic BCs, you must specify PVx and PVy!') return BM
[docs]def BL2BM(xy, NL, BL): """Convert bond list (#bonds x 2) to bond matrix (#pts x #nn) for lattice of bonded points. Parameters ---------- xy : array of dimension nx2 2D lattice of points (positions x,y) NL : array of dimension #pts x max(#neighbors) The ith row contains indices for the neighbors for the ith point. BL : array of dimension #bonds x 2 Each row is a bond and contains indices of connected points Returns ---------- BM : array of length #pts x max(#neighbors) The (i,j)th element is the bond length of the bond connecting the ith particle to its jth neighbor (the particle with index NL[i,j]). """ bL = bond_length_list(xy, BL) # clear periodicity information BL = np.abs(BL) BM = np.zeros_like(NL).astype(float) for i in range(len(bL)): BM[BL[i, 0], np.where(NL[BL[i, 0], :] == BL[i, 1])[0][0]] = bL[i] BM[BL[i, 1], np.where(NL[BL[i, 1], :] == BL[i, 0])[0][0]] = bL[i] # print '\n\n Made BM =', BM return BM
[docs]def BM2bL(NL, BM, BL): """Convert bond list (#bonds x 2) to bond length list (#bonds x 1) for lattice of bonded points. Assumes that BM has correctly accounted for periodic BCs, if applicable. Parameters ---------- NL : array of dimension #pts x max(#neighbors) The ith row contains indices for the neighbors for the ith point. BM : array of length #pts x max(#neighbors) The (i,j)th element is the bond length of the bond connecting the ith particle to its jth neighbor (the particle with index NL[i,j]). BL : array of dimension #bonds x 2 Each row is a bond and contains indices of connected points Returns ---------- bL : array of length #bonds The ith element is the length of of the ith bond in BL. """ # clear periodicity information BL = np.abs(BL) # cycle through BL, finding matching inds in NL and thus KL # for row in BL, get KL value in row BL[i,0] and col where(NL[BL[i,0],:]==BL[i,1])[0] bL = np.array([BM[BL[i, 0], np.where(NL[BL[i, 0], :] == BL[i, 1])[0]][0] for i in range(len(BL))]) return bL
[docs]def BM2BSM(xy, NL, KL, BM0): """Calc strain in each bond, reported in NP x NN array called BSM ('bond strain matrix') """ # Check if 3D or 2D # np.sqrt( (xy[NL[i,0],0]-xy[BL[:,1],0])**2+(xy[BL[:,0],1]-xy[BL[:,1],1])**2) ] '''this isn't finished....'''
[docs]def bond_length_list(xy, BL, NL=None, KL=None, PVx=None, PVy=None): """Convert bond list (#bonds x 2) to bond length list (#bonds x 1) for lattice of bonded points. (BL2bL) Parameters ---------- xy : array of dimension nx2 2D lattice of points (positions x,y) BL : array of dimension #bonds x 2 Each row is a bond and contains indices of connected points. Returns ---------- bL : array of dimension #bonds x 1 Bond lengths, in order of BL (lowercase 'b' denotes 1D array) """ if (BL < 0).any(): if PVx is None or PVy is None: raise RuntimeError('Periodic BCs are much faster if PVx and PVy supplied -- Slow, so exiting.') # PVxydict = BL2PVxydict(xy, BL, PV) else: BM = NL2BM(xy, NL, KL, PVx=PVx, PVy=PVy) bL = BM2bL(NL, BM, BL) else: bL = np.array([np.sqrt(np.dot(xy[BL[i, 1], :] - xy[BL[i, 0], :], xy[BL[i, 1], :] - xy[BL[i, 0], :])) for i in range(len(BL))]) return bL
[docs]def bond_strain_list(xy, BL, bo): """Convert neighbor list to bond list (#bonds x 2) for lattice of bonded points. (makes bs) Parameters ---------- xy : array of dimension nx2 2D lattice of points (positions x,y) BL : array of dimension #bonds x 2 Each row is a bond and contains indices of connected points. bo : array of dimension #bonds x 1 Rest lengths of bonds, in order of BL, lowercase to denote 1D array. Returns ---------- bs : array of dimension #bonds x 1 The strain of each bond, in order of BL """ bL = bond_length_list(xy, BL) # print 'len(bL) = ', len(bL) # print 'len(bo) = ', len(bo) bs = (bL - bo) / bo return bs
[docs]def xyandTRI2centroid(xy, TRI): """Convert xy and TRI to centroid xy array """ centxy = np.zeros((len(TRI), 2), dtype=float) for ii in range(len(TRI)): row = TRI[ii] centxy[ii] = (xy[row[0]] + xy[row[1]] + xy[row[2]]) / 3. # Check # plt.triplot(xy[:,0], xy[:,1], TRI, 'go-') # print 'TRI = ', TRI # plt.scatter(centxy[:,0], centxy[:,1], s=100,c='r') # plt.show() return centxy
[docs]def TRI2centroidNLandKL(TRI): """Convert triangular rep of lattice (such as triangulation) to neighbor array and connectivity array of the triangle centroids. Parameters ---------- TRI : Ntris x 3 int array Triangulation of a point set. Each row gives indices of vertices of single triangle. Returns ---------- NL : NP x NN int array Neighbor list KL : NP x NN int array (optional, for speed) Connectivity list, with -1 entries for periodic bonds """ BL = TRI2centroidBL(TRI) NL, KL = BL2NLandKL(BL, NP='auto', NN='min') return NL, KL
[docs]def TRI2centroidNLandKLandBL(TRI): """Convert triangular rep of lattice (such as triangulation) to neighbor array, connectivity array, and bond list of the triangle centroids. Parameters ---------- TRI : Ntris x 3 int array Triangulation of a point set. Each row gives indices of vertices of single triangle. Returns ---------- NL : NP x NN int array Neighbor list KL : NP x NN int array (optional, for speed) Connectivity list, with -1 entries for periodic bonds BL : array of dimension #bonds x 2 Each row is a bond and contains indices of connected points """ BL = TRI2centroidBL(TRI) NL, KL = BL2NLandKL(BL, NP='auto', NN='min') return NL, KL, BL
[docs]def TRI2centroidBL(TRI): """Convert triangular rep of lattice (such as triangulation) to bond list of the triangle centroids Parameters ---------- TRI : Ntris x 3 int array Triangulation of a point set. Each row gives indices of vertices of single triangle. Returns ---------- BL : array of dimension #bonds x 2 Each row is a bond and contains indices of connected points """ # For each row of TRI, find rows that share a bond (up to 3 of these) dic = {} # nbrs = np.zeros((len(BL),3), dtype=np.int) BL = np.zeros((len(TRI) * 3, 2), dtype=np.int) dmyi = 0 # make sure each row has indices increasing in each row TRI.sort() # Compose dictionary with pairs of neighboring triangles for row in TRI: if (row[0], row[1]) in dic: dic[(row[0], row[1])].append(dmyi) else: dic[(row[0], row[1])] = [dmyi] if (row[0], row[2]) in dic: dic[(row[0], row[2])].append(dmyi) else: dic[(row[0], row[2])] = [dmyi] if (row[1], row[2]) in dic: dic[(row[1], row[2])].append(dmyi) else: dic[(row[1], row[2])] = [dmyi] dmyi += 1 # print 'dic = ', dic # Compose BL from the pairs of indices in each dict entry dmyi = 0 for key in dic: # if the bond is not an edge of the lattice or # a dangling bond, add to BL. # print 'key = ', key, ' -> d[key] = ', dic[key] if len(dic[key]) == 2: BL[dmyi] = dic[key] dmyi += 1 return BL[0:dmyi]
[docs]def TRI2NLandKL(TRI, remove_negatives=False): """ Convert triangulation index array (Ntris x 3) to Neighbor List (Nbonds x 2) array and Connectivity array. Parameters ---------- TRI : Ntris x 3 int array Triangulation of a point set. Each row gives indices of vertices of single triangle. remove_negatives : bool If any of the indices are -1 in TRI, remove those bonds; this is for triangular reps of the lattice that have non-triangular elements (ex dangling bonds) Returns ---------- NL : NP x NN int array Neighbor list KL : NP x NN int array (optional, for speed) Connectivity list, with -1 entries for periodic bonds """ BL = TRI2BL(TRI, remove_negatives=remove_negatives) NL, KL = BL2NLandKL(BL, NP='auto', NN='min') return NL, KL
[docs]def TRI2BL(TRI, remove_negatives=False): """ Convert triangulation index array (Ntris x 3) to Bond List (Nbonds x 2) array. Parameters ---------- TRI : Ntris x 3 int array Triangulation of a point set. Each row gives indices of vertices of single triangle. remove_negatives : bool If any of the indices are -1 in TRI, remove those bonds; this is for triangular reps of the lattice that have non-triangular elements (ex dangling bonds) Returns ---------- BL : Nbonds x 2 int array Bond list """ # each edge is shared by 2 triangles unless at the boundary. # each row contains 3 edges. # An upper bound on the number bonds is 3*len(TRI) BL = np.zeros((3 * len(TRI), 2), dtype=int) dmyi = 0 for row in TRI: BL[dmyi] = [row[0], row[1]] BL[dmyi + 1] = [row[1], row[2]] BL[dmyi + 2] = [row[0], row[2]] dmyi += 3 # Sort each row to be ascending BL_sort = np.sort(BL, axis=1) BLtrim = unique_rows(BL_sort) if remove_negatives: # If any of the indices are -1, remove those bonds BL = BL[np.where(np.logical_and(BL[:, 0] >= 0, BL[:, 1] >= 0))[0]] return BLtrim
[docs]def PVxydict2PVxPVy(PVxydict, NL, check=False): """Convert dictionary of periodic bonds (keys) to periodic vectors (values) denoting periodic 'virtual' displacement for bond (i,j) of particle j as viewed by i. Parameters ---------- PVxydict : dict dictionary of periodic bonds (keys) to periodic vectors (values) NL : NP x NN int array Neighbor list check : bool view intermediate results Returns ---------- PVx : NP x NN float array ijth element of PVx is the x-component of the vector taking NL[i,j] to its image as seen by particle i PVy : NP x NN float array ijth element of PVy is the y-component of the vector taking NL[i,j] to its image as seen by particle i """ if NL is None or np.size(NL) == 0: raise RuntimeError('Must supply non-empty NL to PVxydict2PVxPVy.') PVx = np.zeros_like(NL, dtype='float') PVy = np.zeros_like(NL, dtype='float') for key in PVxydict: if check: print 'key = ', key print 'NL[key[0]] = ', NL[key[0]] col = np.argwhere(NL[key[0]] == key[1])[0][0] PVx[key[0], col] = PVxydict[key][0] PVy[key[0], col] = PVxydict[key][1] col = np.argwhere(NL[key[1]] == key[0])[0] PVx[key[1], col] = -PVxydict[key][0] PVy[key[1], col] = -PVxydict[key][1] return PVx, PVy
[docs]def flexible_PVxydict2PVxPVy(PVxydict, NL, check=False): """Flexibly (defined later) convert dictionary of periodic bonds (keys) to periodic vectors (values) denoting periodic 'virtual' displacement for bond (i,j) of particle j as viewed by i. The flexibility is in that if there is no matching neighbor for a periodic bond, it is destroyed: ie if col = np.argwhere(NL[key[0]] == key[1])[0][0] returns IndexError, delete key from PVxydict and don't add anything for that bond to PVx and PVy Parameters ---------- PVxydict : dict dictionary of periodic bonds (keys) to periodic vectors (values) NL : NP x NN int array Neighbor list check : bool view intermediate results Returns ---------- PVx : NP x NN float array ijth element of PVx is the x-component of the vector taking NL[i,j] to its image as seen by particle i PVy : NP x NN float array ijth element of PVy is the y-component of the vector taking NL[i,j] to its image as seen by particle i """ if NL is None or np.size(NL) == 0: raise RuntimeError('Must supply non-empty NL to PVxydict2PVxPVy.') PVx = np.zeros_like(NL, dtype='float') PVy = np.zeros_like(NL, dtype='float') PVxydict_out = {} for key in PVxydict: if check: print 'key = ', key print 'NL[key[0]] = ', NL[key[0]] try: col = np.argwhere(NL[key[0]] == key[1])[0][0] PVx[key[0], col] = PVxydict[key][0] PVy[key[0], col] = PVxydict[key][1] col = np.argwhere(NL[key[1]] == key[0])[0] PVx[key[1], col] = -PVxydict[key][0] PVy[key[1], col] = -PVxydict[key][1] PVxydict_out[key] = PVxydict[key] except IndexError: print 'skipping this key: ', key
[docs]def PVxy2PVxydict(PVx, PVy, NL, KL=None, eps=1e-7): """ Parameters ---------- PVx : NP x NN float array ijth element of PVx is the x-component of the vector taking NL[i,j] to its image as seen by particle i PVy : NP x NN float array ijth element of PVy is the y-component of the vector taking NL[i,j] to its image as seen by particle i NL : NP x NN int array Neighbor list KL : NP x NN int array (optional, for speed, could be None) Connectivity list, with -1 entries for periodic bonds eps : float Threshold norm value for periodic boundary vector to be recognized as such Returns ---------- PVxydict : dict dictionary of periodic bonds (keys) to periodic vectors (values) """ if KL is None: todo = np.where(abs(PVx) > eps)[0] todo = np.where(abs(PVy) > eps)[0] """finish this by taking unique intersection""" else: rows, cols = np.where(KL == -1) PVxydict = {} for ii in rows: # Get jj from NL # make sure we haven't already added the bond in reverse order if (jj, ii) not in PVxydict: PVxydict[(ii, jj)] = np.array([PVx[ii, jj], PVy[ii, jj]]) # Check print 'PVxydict = ', PVxydict return PVxydict
[docs]def BL2PVxydict(BL, xy, PV): """Extract dictionary of periodic boundary condition vectors from bond list, particle positions Parameters ---------- BL : #bonds x 2 int array Bond list array, with negative-valued rows denoting periodic bonds xy : NP x 2 float array positions of the particles in 2D NL : NP x #NN int array Neighbor list -- the i,jth element is the index (of xy) of the jth neighbor of the ith particle polygon : Nsides x 2 float array the bounding box of the sample (can be any polygonal shape with pairs of parellel sides) PV : Nsides x 2 float array The ith row of PV is the vector taking the ith side of the polygon (connecting polygon[i] to polygon[i+1 % len(polygon)] Returns ---------- PVxydict : dict dictionary of periodic bonds (keys) to periodic vectors (values) --> transforms into: ijth element of PVx is the x-component of the vector taking NL[i,j] to its image as seen by particle i """ # The ijth element of PVx is the xcomponent of the vector taking NL[i,j] to its image as seen by particle i. PVxydict = {} # For each bond that is a periodic bond, determine its periodic boundary vector (a row of the array PV) pBs = np.unique(np.where(BL < 0)[0]) print 'BL[pBs] = ', BL[pBs] print 'pBs = ', pBs for ind in pBs: # Find the PV (periodic vector) that brings the second particle (j) closest to the first (i). # This will be PVxydict[(i,j)], since particle i sees j at xy[j]+PVxydict[(i,j)] a1 = xy[np.abs(BL[ind, 0])] a2 = xy[np.abs(BL[ind, 1])] distxy = a2 + PV - a1 dist = distxy[:, 0] ** 2 + distxy[:, 1] ** 2 PVxydict[(np.abs(BL[ind, 0]), np.abs(BL[ind, 1]))] = PV[np.argmin(dist)] print 'PVxydict = ', PVxydict return PVxydict
[docs]def extract_boundary(xy, NL, KL, BL, check=False): """Extract the boundary of a 2D network (xy,NL,KL). If periodic, discards this information. Parameters ---------- xy : NP x 2 float array point set in 2D BL : #bonds x 2 int array Bond list NL : NP x NN int array Neighbor list. The ith row contains the indices of xy that are the bonded pts to the ith pt. Nonexistent bonds are replaced by zero. KL : NP x NN int array Connectivity list. The jth column of the ith row ==1 if pt i is bonded to pt NL[i,j]. The jth column of the ith row ==0 if pt i is not bonded to point NL[i,j]. check: bool Whether to show intermediate results Returns ---------- boundary : #points on boundary x 1 int array indices of points living on boundary of the network """ # Clear periodic bonds from KL pbonds = np.where(KL.ravel() < 0)[0] if len(pbonds) > 0: print 'Found periodic bonds in extract_boundary(), clearing...' KLr = KL.ravel() KLr[pbonds] = 0 KL = KLr.reshape(np.shape(KL)) print 'pbonds = ', pbonds # If there are dangling points, remove them for now and adjust indices later dangles = np.where(~KL.any(axis=1))[0] if len(dangles) > 0: print 'extract_boundary: Removing dangling points: dangles = ', dangles if check: plt.plot(xy[:, 0], xy[:, 1], 'b.') for ii in range(len(xy)): plt.text(xy[ii, 0] + 0.1, xy[ii, 1], str(ii)) plt.plot(xy[dangles, 0], xy[dangles, 1], 'ro') plt.title('Original point indices, before removing dangles. Dangles circled in red.') plt.show() translate_at_end = True NP = len(xy) nondangles = np.setdiff1d(np.arange(NP), dangles) # Note that remove_pts can handle periodic BL xy, NL, KL, BL = remove_pts(nondangles, xy, BL) # Remove bonds which were periodic. pbonds = np.where(KL.ravel() < 0)[0] print 'pbonds = ', pbonds if pbonds: print 'Found periodic bonds in extract_boundary(), clearing...' KLr = KL.ravel() KLr[pbonds] = 0 KL = KLr.reshape(np.shape(KL)) print 'pbonds = ', pbonds if check: print 'NL = ', NL display_lattice_2D(xy, BL, NL=NL, KL=KL, title='Removed points in extract_boundary()') # xy = xy[nondangles] # NL = NL[nondangles] # KL = KL[nondangles] # translation converts indices of long old xy to small new xy # backtrans converts indices of small, new xy to indices of long, old xy # .1 .0 # .0 trans -----> # . 2 <----- backtrans .1 # .3 .2 translation = np.arange(NP, dtype=int) for IND in dangles: translation[IND:] -= 1 # mark the removed point by -5 translation[IND] = -5 backtrans = np.where(translation > -1)[0] if check: print 'backtrans = ', backtrans print 'translation = ', translation # translation = np.where() else: translate_at_end = False # Initialize the list of boundary indices to be larger than necessary bb = np.zeros(2 * len(xy), dtype=int) # Start with the rightmost point, which is guaranteed to be # at the convex hull and thus also at the outer edge. # Then take the first step to be along the minimum angle bond rightIND = np.where(xy[:, 0] == max(xy[:, 0]))[0] # If there are more than one rightmost point, choose one if rightIND.size > 1: rightIND = rightIND[0] if check: print 'Found rightmost pt: ', rightIND print ' with neighbors: ', NL[rightIND] print ' with connectns: ', KL[rightIND] plt.plot(xy[:, 0], xy[:, 1], 'k.') plt.plot(xy[rightIND, 0], xy[rightIND, 1], 'bo') for ii in range(len(xy)): plt.text(xy[ii, 0] + 0.1, xy[ii, 1], str(ii)) plt.plot(xy[rightIND, 0], xy[rightIND, 1], 'ro') plt.pause(0.01) # Grab the true neighbors of this starting point # print 'NL[rightIND, :] = ', NL[rightIND, :] neighbors = NL[rightIND, np.argwhere(KL[rightIND].ravel()).ravel()] # Compute the angles of the neighbor bonds angles = np.mod(np.arctan2(xy[neighbors, 1] - xy[rightIND, 1], xy[neighbors, 0] - xy[rightIND, 0]).ravel(), 2 * np.pi) if check: print 'KL[rightIND] = ', KL[rightIND] print 'KL[rightIND,0] = ', KL[rightIND, 0] print 'KL[rightIND,0] ==0 ', KL[rightIND, 0] == 0 print 'np.argwhere(KL[rightIND]) = ', np.argwhere(KL[rightIND]) print 'np.argwhere(KL[rightIND].ravel())= ', np.argwhere(KL[rightIND].ravel()) print 'neighbors = ', neighbors print 'angles = ', angles # Take the second particle to be the one with the lowest bond angle (will be >= pi/2) # print ' angles==min--> ', angles==min(angles) nextIND = neighbors[angles == min(angles)][0] bb[0] = rightIND dmyi = 1 # as long as we haven't completed the full outer edge/boundary, add nextIND while nextIND != rightIND: # print '\n nextIND = ', nextIND # print 'np.argwhere(KL[nextIND]) = ', np.argwhere(KL[nextIND]).ravel() bb[dmyi] = nextIND n_tmp = NL[nextIND, np.argwhere(KL[nextIND].ravel())] # Exclude previous boundary particle from the neighbors array, UNLESS IT IS THE ONLY ONE, # since its angle with itself is zero! if len(n_tmp) == 1: print 'The bond is a lone bond, not part of a triangle.' neighbors = n_tmp else: neighbors = np.delete(n_tmp, np.where(n_tmp == bb[dmyi - 1])[0]) # print 'n_tmp = ', n_tmp # print 'neighbors = ', neighbors angles = np.mod(np.arctan2(xy[neighbors, 1] - xy[nextIND, 1], xy[neighbors, 0] - xy[nextIND, 0]).ravel() \ - np.arctan2(xy[bb[dmyi - 1], 1] - xy[nextIND, 1], xy[bb[dmyi - 1], 0] - xy[nextIND, 0]).ravel(), 2 * np.pi) # print 'angles = ', angles # print ' angles==min--> ', angles==min(angles) nextIND = neighbors[angles == min(angles)][0] # print 'nextIND = ', nextIND if check: # plt.plot(xy[:,0],xy[:,1],'k.') XY = np.vstack([xy[bb[dmyi], :], xy[nextIND, :]]) plt.plot(XY[:, 0], XY[:, 1], 'r-') # for i in range(len(xy)): # plt.text(xy[i,0]+0.2,xy[i,1],str(i)) plt.gca().set_aspect('equal') plt.pause(0.01) dmyi += 1 # Truncate the list of boundary indices boundary = bb[0:dmyi] # Since some points were removed from the boundary identification, translate # indices back to indices of original xy if translate_at_end: print 'extract_boundary: Translating boundary points back into original indices...' # print 'boundary = ', boundary # print 'translation = ', translation # print 'backtrans = ', backtrans boundary = backtrans[boundary] return boundary
[docs]def pts_in_polygon(xy, polygon): """Returns points in array xy that are located inside supplied polygon array. """ bpath = mplpath.Path(polygon) inside = bpath.contains_points(xy) xy_out = xy[inside, :] return xy_out
[docs]def inds_in_polygon(xy, polygon): """Returns points in array xy that are located inside supplied polygon array. """ bpath = mplpath.Path(polygon) inside = bpath.contains_points(xy) inds = np.where(inside)[0] return inds
[docs]def extract_phase(eigvector, point_arr=[]): """ Extract phase information from an eigenvector. Parameters ---------- eigvector : 2 x NP complex array First row is X component, second is Y. point_arr : M x 1 int array or empty for all points array of indices for which to order the output, in desired order. If empty, gets phase info for all elements/points. Returns ---------- phase : NP x 1 float array The phase of the array """ pa = point_arr if np.size(pa) == 0: pa = np.arange(len(evY)) evX = eigvector[2 * pa] evY = eigvector[2 * pa + 1] phase = np.arctan2(evY.real, evX.real) # print 'evY[0] =', evY[0] # print 'evX[0] =', evX[0] # print 'phase[0] = ', phase[0] return phase
# def argTRI_delaunay_cut_unnatural_boundary(xy,NL,KL,BL,TRI, thres): # '''Keep cutting skinny triangles on the boundary until no more skinny ones. # Cuts boundary tris of a triangulation until all have reasonable height/base values. # # Parameters # ---------- # xy : NP x 2 float array # The point set # BL : Nbonds x 2 int array # Bond list for the lattice (can include bonds that aren't triangulated) # TRI : Ntris x 3 int array # The triangulation of the lattice/mesh # thres : float # threshold value for height/base, below which to cut the boundary tri # # Returns # ---------- # keepTRI : Ntris x 1 bool array # whether to keep (True) or cut (False) each triangle based on how squished it is, # for trimming bad boundary tris after triangulation # ''' # NP = len(xy) # NN = np.shape(NL)[1] # print ' delaunay_cut_unnatural_boundary : extract boundary...' # boundary = extract_boundary(xy,NL,KL, BL) # Ncut = 1 # dmyi = 0 # while Ncut>0: # print 'cutting pass '+str(dmyi) # BL, TRItrim, Ncut = delaunay_cut_unnatural_boundary_singlepass(xy,BL,TRI,boundary,thres) # TRI = BL2TRI(BL) # NL, KL = BL2NLandKL(BL,NP=NP,NN=NN) # #print ' --> extract new boundary...' # boundary = extract_boundary(xy,NL,KL, BL) # dmyi += 1 # # return keepTRI
[docs]def delaunay_cut_unnatural_boundary(xy, NL, KL, BL, TRI, thres, check=False): """Keep cutting skinny triangles on the boundary until no more skinny ones. Cuts boundary tris of a triangulation until all have reasonable height/base values. Parameters ---------- xy : NP x 2 float array The point set NL : NP x NN int array (optional, speeds up calc if it is known there are no dangling bonds) Neighbor list. The ith row has neighbors of the ith particle, padded with zeros KL : NP x NN int array (optional, speeds up calc if it is known there are no dangling bonds) Connectivity list. The ith row has ones where ith particle is connected to NL[i,j] BL : Nbonds x 2 int array Bond list for the lattice (can include bonds that aren't triangulated) TRI : Ntris x 3 int array The triangulation of the lattice/mesh thres : float threshold value for base/height, below which to cut the boundary tri check: bool Display intermediate results Returns ---------- NL : NP x NN int array (optional, speeds up calc if it is known there are no dangling bonds) Neighbor list. The ith row has neighbors of the ith particle, padded with zeros KL : NP x NN int array (optional, speeds up calc if it is known there are no dangling bonds) Connectivity list. The ith row has ones where ith particle is connected to NL[i,j] BL : Nbonds x 2 int array Bond list for the lattice (can include bonds that aren't triangulated) TRI : (Ntris - Ncut) x 3 int array The new, trimmed triangulation """ # Computes--> # boundary : # points on boundary x 1 int array # The indices of the points that live on the boundary NP = len(xy) NN = np.shape(NL)[1] print ' delaunay_cut_unnatural_boundary : extract boundary...' boundary = extract_boundary(xy, NL, KL, BL, check=check) Ncut = 1 dmyi = 0 while Ncut > 0: print 'cutting pass ' + str(dmyi) BL, Ncut = delaunay_cut_unnatural_boundary_singlepass(xy, BL, TRI, boundary, thres, check=check) TRI = BL2TRI(BL, xy) NL, KL = BL2NLandKL(BL, NP=NP, NN=NN) # print ' --> extract new boundary...' boundary = extract_boundary(xy, NL, KL, BL) dmyi += 1 return NL, KL, BL, TRI
[docs]def delaunay_cut_unnatural_boundary_singlepass(xy, BL, TRI, boundary, thres=4.0, check=False): """ Algorithm: For each row of TRI containing at least one boundary pt, if contains 2 boundary pts, cut the base (edge) if base/height > threshold. If the tri has all three vertices on the boundary, keep it. Two boundary triangles may share an edge (bond), and removing both will leave a dangling point. To avoid this, check for edges shared between simplices. This allows bonds which are not part of the triangulation. Haven't inspected for periodicity issues. Parameters ---------- xy : NP x 2 float array The point set BL : Nbonds x 2 int array Bond list for the lattice (can include bonds that aren't triangulated) TRI : Ntris x 3 int array The triangulation of the lattice/mesh boundary : # points on boundary x 1 int array The indices of the points that live on the boundary thres : float threshold value for base/height, below which to cut the boundary tri check: bool Display intermediate results Returns ---------- TRItrim : (Ntris - Ncut) x 3 int array The new, trimmed triangulation Ncut : int The number of rows of TRI that have been cut (# boundary tris cut) """ # btri are indices of TRI rows that are boundary triangles btri = boundary_triangles(TRI, boundary) # CHECK if check: zfaces = np.zeros(len(TRI), dtype=float) zfaces[btri] = 1. # Color the boundary triangles in a plot plt.figure() plt.gca().set_aspect('equal') plt.tripcolor(xy[:, 0], xy[:, 1], TRI, facecolors=zfaces, edgecolors='k') plt.colorbar() for i in range(len(xy)): plt.text(xy[i, 0] - 0.2, xy[i, 1], str(i)) plt.title('tripcolor() showing boundary tris') plt.show() # preallocate for speed --> to cut from BL, TRI rows2cut = np.zeros(len(BL), dtype=bool) # rowsTRI2cut = np.zeros(len(TRI),dtype=bool) for ii in range(len(btri)): # Check if base/height >thres for this row row = TRI[btri[ii]] # print 'row = ', row, ' btri[ii] = ', btri[ii] onB_bool = np.array([row[i] in boundary for i in range(len(row))]) onB = np.where(onB_bool)[0] # Two possibilities: # (1) The tri has two vertices on the boundary --> keep it if aspect ratio is ok # (2) The tri has all three vertices on the boundary --> keep it # print 'onB = ', onB # print 'onB_bool.all() =', onB_bool.all() if not onB_bool.all(): # identify the point that lies across from the base of the scalene tri pt3 = xy[row[np.setdiff1d(np.arange(3), onB)], :][0] # identify the base points base = np.array([xy[row[onB[0]], :], xy[row[onB[1]], :]]) # identify the point closest to pt3 on lineseg p = closest_pt_along_line(pt3, base[0], base[1]) # calc base and height baseL = float(np.linalg.norm(base[0] - base[1])) height = float(np.linalg.norm(pt3 - p)) if (baseL / height) > thres: # if too skinny, kill the row of BL corresponding to the base BLp0 = min(row[onB]) BLp1 = max(row[onB]) # print 'row[onB] = ', row[onB] # print 'BL0 = ', BLp0 # print 'BL1 = ', BLp1 # Mark for BL removal indBL2cut = np.where((BL == (BLp0, BLp1)).all(axis=1))[0][0] rows2cut[indBL2cut] = True # Mark for TRI removal--> not natural since indices of TRI are changes # rowsTRI2cut[btri[ii]] = True # print 'boundary =', boundary # print 'onB =', onB # print 'xy ind = ', row[np.setdiff1d(np.arange(3),onB)] # print 'pt3 = ', pt3 # print 'base = ', base # print 'p = ', p # print 'baseL = ', baseL # print 'height = ', height # print 'baseL/height = ', baseL/height # check # keepTRI = np.ones(len(TRI),dtype=bool) # keepTRI[btri[rows2cut]] = False # TRItest = TRI[keepTRI] # plt.triplot(xy[:,0], xy[:,1], TRItest, 'bo-') # plt.plot([p[0],pt3[0]],[p[1],pt3[1]],'k.-') # plt.show() # Not natural since indices of TRI are changes! # keepTRI = np.ones(len(TRI),dtype=bool) # keepTRI[rowsTRI2cut] = False # TRItrim = TRI[keepTRI] keepBL = np.ones(len(BL), dtype=bool) keepBL[rows2cut] = False BLtrim = BL[keepBL] # check # print 'Checking in delaunay_cut_unnatural_boundary ...' # display_lattice_2D(xy,BLtrim) # plt.triplot(xy[:,0], xy[:,1], TRItrim, 'ro-') # plt.show() Ncut = len(np.where(rows2cut)[0]) return BLtrim, Ncut
[docs]def boundary_triangles(TRI, boundary): """Identify triangles of triangulation that live on the boundary (ie share one edge with no other simplices) """ # Look for triangles in TRI that contain 2 elements on the boundary # (ie they have a boundary edge in the triangle) inb0 = np.where(np.in1d(TRI[:, 0], boundary))[0] inb1 = np.where(np.in1d(TRI[:, 1], boundary))[0] inb2 = np.where(np.in1d(TRI[:, 2], boundary))[0] inb_all = np.hstack((inb0, inb1, inb2)).ravel() # print 'inb_all = ', inb_all # Look for indices that appear twice in cat( inb0,inb1,inb2). s = np.sort(inb_all, axis=None) btris = s[s[1:] == s[:-1]] # If any values are repeated in btri, that means all three vertices are boundary. # Keep these. Also, remove from the list any tris that share two points with one of these tris. # --> this is because this means an edge (not a boundary edge) connects two boundary particles, # and cuts off another particle. btri_repeats = btris[btris[1:] == btris[:-1]] # print 'TRI = ', TRI # print 'btris = ', btris # print 'btri_repeats = ', btri_repeats # btri = np.setdiff1d(btris,btri_repeats) btris = np.unique(btris) # If any btri triangles share an edge with a btri_repeats (they share 2 points), # kill the btri triangle. mask = np.ones(len(btris), dtype=bool) for ii in range(len(btris)): # if this one isn't itself a repeat, check against all brtri_repeats if not np.in1d(btris[ii], btri_repeats): tri0 = TRI[btris[ii]] for btr in btri_repeats: tri1 = TRI[btr] if len(np.intersect1d(tri0, tri1, assume_unique=True)) > 1: # print 'matching = ', np.intersect1d(tri0,tri1,assume_unique=True) mask[ii] = False btri = btris[mask] return btri
[docs]def extract_polygons_lattice(xy, BL, NL=[], KL=[], PVx=[], PVy=[], PVxydict={}, viewmethod=False, check=False): """ Extract polygons from a lattice of points. Note that dangling bonds are removed, but no points are removed. This allows correct indexing for PVxydict keys, if supplied. Parameters ---------- xy : NP x 2 float array points living on vertices of dual to triangulation BL : Nbonds x 2 int array Each row is a bond and contains indices of connected points NL : NP x NN int array (optional, speeds up calc if it is known there are no dangling bonds) Neighbor list. The ith row has neighbors of the ith particle, padded with zeros KL : NP x NN int array (optional, speeds up calc if it is known there are no dangling bonds) Connectivity list. The ith row has ones where ith particle is connected to NL[i,j] PVx : NP x NN float array (optional, for periodic lattices and speed) ijth element of PVx is the x-component of the vector taking NL[i,j] to its image as seen by particle i If PVx and PVy are specified, PVxydict need not be specified. PVy : NP x NN float array (optional, for periodic lattices and speed) ijth element of PVy is the y-component of the vector taking NL[i,j] to its image as seen by particle i If PVx and PVy are specified, PVxydict need not be specified. PVxydict : dict (optional, for periodic lattices) dictionary of periodic bonds (keys) to periodic vectors (values) viewmethod: bool View the results of many intermediate steps check: bool Check the initial and final result Returns ---------- polygons : list of lists of ints list of lists of indices of each polygon """ NP = len(xy) if KL == [] or NL == []: NL, KL = BL2NLandKL(BL, NP=NP, NN='min') if (BL < 0).any(): if len(PVxydict) > 0: PVx, PVy = PVxydict2PVxPVy(PVxydict, NL) else: raise RuntimeError('Must specify either PVxydict or KL and NL in extract_polygons_lattice()' + ' when periodic bonds exist!') elif (BL < 0).any(): if PVx == [] or PVy == []: if PVxydict == {}: raise RuntimeError('Must specify either PVxydict or PVx and PVy in extract_polygons_lattice()' + ' when periodic bonds exist!') else: PVx, PVy = PVxydict2PVxPVy(PVxydict, NL) NN = np.shape(KL)[1] # Remove dangling bonds # dangling bonds have one particle with only one neighbor finished_dangles = False while not finished_dangles: dangles = np.where([np.count_nonzero(row) == 1 for row in KL])[0] if len(dangles) > 0: # Check if need to build PVxy dictionary from PVx and PVy before changing NL and KL if (BL < 0).any() and len(PVxydict) == 0: PVxydict = PVxy2PVxydict(PVx, PVy, NL, KL=KL) # Make sorted bond list of dangling bonds dpair = np.sort(np.array([[d0, NL[d0, np.where(KL[d0] != 0)[0]]] for d0 in dangles]), axis=1) # Remove those bonds from BL BL = setdiff2d(BL, dpair.astype(BL.dtype)) # print 'dpair = ', dpair # print 'ending BL = ', BL NL, KL = BL2NLandKL(BL, NP=NP, NN=NN) # Now that NL and KL rebuilt (changed), (re)build PVx and PVy if periodic bcs if (BL < 0).any(): if len(PVxydict) > 0: PVx, PVy = PVxydict2PVxPVy(PVxydict, NL) else: finished_dangles = True if viewmethod or check: print 'Plotting result after chopped dangles, if applicable...' display_lattice_2D(xy, BL, NL=NL, KL=KL, PVx=PVx, PVy=PVy, PVxydict=PVxydict, title='Result after chopping dangling bonds', close=False) for i in range(len(xy)): plt.text(xy[i, 0] + 0.2, xy[i, 1], str(i)) plt.show() # bond markers for counterclockwise, clockwise used = np.zeros((len(BL), 2), dtype=bool) polygons = [] finished = False if viewmethod: f, (ax1, ax2) = plt.subplots(1, 2) # For periodicity, remember which bonds span periodic boundary periB = np.array([(row < 0).any() for row in BL]) if periB.any() and len(PVxydict) == 0 and (PVx == [] or PVy == []): raise RuntimeError('Periodic boundaries have been detected, but no periodic vectors supplied to ' + 'extract_polygons_lattice()') if not periB.any(): print 'no PBCs, calculating polygons...' while not finished: # Check if all bond markers are used in order A-->B # print 'Checking AB (A-->B): ' todoAB = np.where(~used[:, 0])[0] # print 'len(todoAB) = ', len(todoAB) # print 'used = ', used # print 'todoAB = ', todoAB if len(todoAB) > 0: bond = BL[todoAB[0]] # bb will be list of polygon indices # Start with orientation going from bond[0] to bond[1] nxt = bond[1] bb = [bond[0], nxt] dmyi = 1 ############### # check if viewmethod: ax1.plot(xy[:, 0], xy[:, 1], 'k.') ax1.annotate("", xy=(xy[bb[dmyi], 0], xy[bb[dmyi], 1]), xycoords='data', xytext=(xy[nxt, 0], xy[nxt, 1]), textcoords='data', arrowprops=dict(arrowstyle="->", color="r", shrinkA=5, shrinkB=5, patchA=None, patchB=None, connectionstyle="arc3,rad=0.2", ), ) for i in range(len(xy)): ax1.text(xy[i, 0] + 0.2, xy[i, 1], str(i)) ax2.imshow(used) ax1.set_aspect('equal') ############### # as long as we haven't completed the full outer polygon, add next index while nxt != bond[0]: n_tmp = NL[nxt, np.argwhere(KL[nxt]).ravel()] # Exclude previous boundary particle from the neighbors array, unless its the only one # (It cannot be the only one, if we removed dangling bonds) if len(n_tmp) == 1: '''The bond is a lone bond, not part of a triangle.''' neighbors = n_tmp else: neighbors = np.delete(n_tmp, np.where(n_tmp == bb[dmyi - 1])[0]) angles = np.mod(np.arctan2(xy[neighbors, 1] - xy[nxt, 1], xy[neighbors, 0] - xy[nxt, 0]).ravel() \ - np.arctan2(xy[bb[dmyi - 1], 1] - xy[nxt, 1], xy[bb[dmyi - 1], 0] - xy[nxt, 0]).ravel(), 2 * np.pi) nxt = neighbors[angles == max(angles)][0] bb.append(nxt) ############### # # Check # if viewmethod: # plt.annotate("", xy=(xy[bb[dmyi],0],xy[bb[dmyi],1] ), xycoords='data', # xytext=(xy[nxt,0], xy[nxt,1]), textcoords='data', # arrowprops=dict(arrowstyle="->", # color="r", # shrinkA=5, shrinkB=5, # patchA=None, # patchB=None, # connectionstyle="arc3,rad=0.2",), ) # ############### # Now mark the current bond as used thisbond = [bb[dmyi - 1], bb[dmyi]] # Get index of used matching thisbond mark_used = np.where((np.logical_or(BL == bb[dmyi - 1], BL == bb[dmyi])).all(axis=1)) # mark_used = np.where((BL == thisbond).all(axis=1)) if not used[mark_used, 0]: # print 'marking bond [', thisbond, '] as used' used[mark_used, 0] = True else: # Get index of used matching reversed thisbond (this list boolean is directional) # mark_used = np.where((BL == thisbond[::-1]).all(axis=1)) # Used this bond in reverse order used[mark_used, 1] = True # print 'used = ', used dmyi += 1 polygons.append(bb) ############### # Check new polygon if viewmethod: ax1.plot(xy[:, 0], xy[:, 1], 'k.') for i in range(len(xy)): ax1.text(xy[i, 0] + 0.2, xy[i, 1], str(i)) for dmyi in range(len(bb)): nxt = bb[np.mod(dmyi + 1, len(bb))] ax1.annotate("", xy=(xy[bb[dmyi], 0], xy[bb[dmyi], 1]), xycoords='data', xytext=(xy[nxt, 0], xy[nxt, 1]), textcoords='data', arrowprops=dict(arrowstyle="->", color="r", shrinkA=5, shrinkB=5, patchA=None, patchB=None, connectionstyle="arc3,rad=0.2", ), ) ax2.cla() ax2.imshow(used) plt.pause(0.00001) ############### else: # Check for remaining bonds unused in reverse order (B-->A) # print 'CHECKING REVERSE (B-->A): ' todoBA = np.where(~used[:, 1])[0] # print 'len(todoBA) = ', len(todoBA) if len(todoBA) > 0: bond = BL[todoBA[0]] ############### # # check # if viewmethod: # plt.annotate("", xy=(xy[bb[dmyi],0],xy[bb[dmyi],1] ), xycoords='data', # xytext=(xy[nxt,0], xy[nxt,1]), textcoords='data', # arrowprops=dict(arrowstyle="->", # color="b", # shrinkA=5, shrinkB=5, # patchA=None, # patchB=None, # connectionstyle="arc3,rad=0.6",), ) # ############### # bb will be list of polygon indices # Start with orientation going from bond[0] to bond[1] nxt = bond[0] bb = [bond[1], nxt] dmyi = 1 # as long as we haven't completed the full outer polygon, add nextIND while nxt != bond[1]: n_tmp = NL[nxt, np.argwhere(KL[nxt]).ravel()] # Exclude previous boundary particle from the neighbors array, unless its the only one # (It cannot be the only one, if we removed dangling bonds) if len(n_tmp) == 1: '''The bond is a lone bond, not part of a triangle.''' neighbors = n_tmp else: neighbors = np.delete(n_tmp, np.where(n_tmp == bb[dmyi - 1])[0]) angles = np.mod(np.arctan2(xy[neighbors, 1] - xy[nxt, 1], xy[neighbors, 0] - xy[nxt, 0]).ravel() \ - np.arctan2(xy[bb[dmyi - 1], 1] - xy[nxt, 1], xy[bb[dmyi - 1], 0] - xy[nxt, 0]).ravel(), 2 * np.pi) nxt = neighbors[angles == max(angles)][0] bb.append(nxt) ############### # Check # if viewmethod: # plt.annotate("", xy=(xy[bb[dmyi],0],xy[bb[dmyi],1] ), xycoords='data', # xytext=(xy[nxt,0], xy[nxt,1]), textcoords='data', # arrowprops=dict(arrowstyle="->", # color="b", # shrinkA=5, shrinkB=5, # patchA=None, # patchB=None, # connectionstyle="arc3,rad=0.6", #connectionstyle, # ), ) ############### # Now mark the current bond as used --> note the inversion of the bond order to match BL thisbond = [bb[dmyi], bb[dmyi - 1]] # Get index of used matching [bb[dmyi-1],nxt] mark_used = np.where((BL == thisbond).all(axis=1)) if len(mark_used) > 0: used[mark_used, 1] = True else: raise RuntimeError( 'Cannot mark polygon bond as used: this bond was already used in its attempted orientation. (All bonds in first column should already be marked as used.)') dmyi += 1 polygons.append(bb) # Check new polygon if viewmethod: ax1.plot(xy[:, 0], xy[:, 1], 'k.') for i in range(len(xy)): ax1.text(xy[i, 0] + 0.2, xy[i, 1], str(i)) for dmyi in range(len(bb)): nxt = bb[np.mod(dmyi + 1, len(bb))] ax1.annotate("", xy=(xy[bb[dmyi], 0], xy[bb[dmyi], 1]), xycoords='data', xytext=(xy[nxt, 0], xy[nxt, 1]), textcoords='data', arrowprops=dict(arrowstyle="->", color="b", shrinkA=5, shrinkB=5, patchA=None, patchB=None, connectionstyle="arc3,rad=0.6", ), ) ax2.cla() ax2.imshow(used) plt.pause(0.00001) ############### else: # All bonds have been accounted for finished = True else: print 'detected periodicity...' # get particles on the finite (non-periodic) system's boundary. This allows massive speedup. KLfin = np.zeros_like(KL) KLfin[KL > 0] = 1 # Create BLfin to pass to extract_boundary() prows = np.where(BL < 0)[0] nprows = np.setdiff1d(np.arange(len(BL)), prows) if check: print 'rows of BL that are periodic: ', prows print 'BL[prows] = ', BL[prows] BLfin = BL[nprows] finbd = extract_boundary(xy, NL, KLfin, BLfin, check=check) # If there were dangling points in the non-periodic representation, then we need to add those to finbd because # they will have periodic bonds attached to them. dangles = np.where(~KLfin.any(axis=1))[0] if dangles: print 'Found dangling points in the finite/non-periodic representation. Adding to finbd...' finbd = np.hstack((finbd, np.array(dangles))) if check: print 'finite boundary: finbd = ', finbd plt.clf() display_lattice_2D(xy, BL, NL=NL, KL=KLfin, PVx=PVx, PVy=PVy, PVxydict=PVxydict, title='Identified finite boundary', close=False) for i in range(len(xy)): plt.text(xy[i, 0] + 0.2, xy[i, 1], str(i)) plt.plot(xy[finbd, 0], xy[finbd, 1], 'ro') plt.show() first_check = True # Then erase periodicity in BL BL = np.abs(BL) while not finished: if len(polygons) % 20 == 0: print 'constructed ', len(polygons), ' polygons...' # Check if all bond markers are used in order A-->B # print 'Checking AB (A-->B): ' todoAB = np.where(~used[:, 0])[0] # print 'len(todoAB) = ', len(todoAB) # print 'used = ', used # print 'todoAB = ', todoAB if len(todoAB) > 0: bond = BL[todoAB[0]] # bb will be list of polygon indices # Start with orientation going from bond[0] to bond[1] nxt = bond[1] bb = [bond[0], nxt] dmyi = 1 # define 'previous angle' as backwards of current angle -- ie angle(prev-current_pos) # Must include effect of PV on this angle -- do in ref frame of nxt particle PVind = np.argwhere(NL[nxt] == bond[0])[0][0] addx = PVx[nxt, PVind] addy = PVy[nxt, PVind] xyb0 = xy[bond[0], :] + np.array([addx, addy]) prev_angle = np.arctan2(xyb0[1] - xy[nxt, 1], xyb0[0] - xy[nxt, 0]).ravel() ############### # check if viewmethod: if first_check: ax1.plot(xy[:, 0], xy[:, 1], 'k.') for i in range(len(xy)): ax1.text(xy[i, 0] + 0.2, xy[i, 1], str(i)) first_check = False ax1.annotate("", xy=(xy[bb[dmyi - 1], 0], xy[bb[dmyi - 1], 1]), xycoords='data', xytext=(xy[nxt, 0], xy[nxt, 1]), textcoords='data', arrowprops=dict(arrowstyle="->", color="r", shrinkA=5, shrinkB=5, patchA=None, patchB=None, connectionstyle="arc3,rad=0.2", ), ) ax2.imshow(used, aspect=1. / len(used), interpolation='none') ax1.set_aspect('equal') ############### # as long as we haven't completed the full outer polygon, add next index while nxt != bond[0]: # print nxt # o o neighbors # \ / # \ / # o nxt # / # / # o bb[dmyi-1] # n_tmp = NL[nxt, np.argwhere(KL[nxt]).ravel()] # Exclude previous boundary particle from the neighbors array, unless its the only one # (It cannot be the only one, if we removed dangling bonds) if len(n_tmp) == 1: '''The bond is a lone bond, not part of a triangle/polygon.''' neighbors = n_tmp else: neighbors = np.delete(n_tmp, np.where(n_tmp == bb[dmyi - 1])[0]) # check if neighbors CAN be connected across periodic bc-- ie if particle on finite boundary (finbd) if nxt in finbd: # Since on finite system boundary, particle could have periodic bonds # Find x values to add to neighbors, by first getting indices of row of PV (same as of NL) matching neighbors PVinds = [np.argwhere(NL[nxt] == nnn)[0][0] for nnn in neighbors] addx = PVx[nxt, PVinds] addy = PVy[nxt, PVinds] xynb = xy[neighbors, :] + np.dstack([addx, addy])[0] xynxt = xy[nxt, :] current_angles = np.arctan2(xynb[:, 1] - xynxt[1], xynb[:, 0] - xynxt[0]).ravel() angles = np.mod(current_angles - prev_angle, 2 * np.pi) if check: print '\n' print 'particle ', nxt, ' is on finbd' print 'nxt = ', nxt print 'neighbors = ', neighbors print 'xy[neighbors,:] =', xy[neighbors, :] print 'addxy = ', np.dstack([addx, addy])[0] print 'xynb = ', xynb print 'xynxt = ', xynxt print 'current_angles = ', current_angles print 'prev_angle = ', prev_angle print 'angles = ', angles print 'redefining nxt = ', neighbors[angles == max(angles)][0] # redefine previous angle as backwards of current angle -- ie angle(prev-current_pos) prev_angletmp = np.arctan2(xynxt[1] - xynb[:, 1], xynxt[0] - xynb[:, 0]).ravel() prev_angle = prev_angletmp[angles == max(angles)][0] # print 'prev_angletmp = ', prev_angletmp # print 'prev_angle = ', prev_angle # print 'NL[nxt] = ', NL[nxt] # print 'bb = ', bb # CHECK # ax1 = plt.gca() # ax1.plot(xy[:,0],xy[:,1],'k.') # for i in range(len(xy)): # ax1.text(xy[i,0]+0.2,xy[i,1],str(i)) # plt.show() else: current_angles = np.arctan2(xy[neighbors, 1] - xy[nxt, 1], xy[neighbors, 0] - xy[nxt, 0]).ravel() angles = np.mod(current_angles - prev_angle, 2 * np.pi) # redefine previous angle as backwards of current angle -- ie angle(prev-current_pos) # prev_angle = np.arctan2(xy[bb[dmyi-1],1] - xynxt[1], xy[bb[dmyi-1],0] - xynxt[0] ).ravel() xynxt = xy[nxt, :] xynb = xy[neighbors, :] prev_angletmp = np.arctan2(xynxt[1] - xy[neighbors, 1], xynxt[0] - xy[neighbors, 0]).ravel() prev_angle = prev_angletmp[angles == max(angles)][0] nxt = neighbors[angles == max(angles)][0] bb.append(nxt) ############### # # Check bond if viewmethod: # Check individually # ax1 = plt.gca() # ax1.plot(xy[:,0],xy[:,1],'k.') if first_check: for i in range(len(xy)): ax1.text(xy[i, 0] + 0.2, xy[i, 1], str(i)) plt.annotate("", xy=(xy[bb[dmyi], 0], xy[bb[dmyi], 1]), xycoords='data', xytext=(xy[nxt, 0], xy[nxt, 1]), textcoords='data', arrowprops=dict(arrowstyle="->", color="r", shrinkA=5, shrinkB=5, patchA=None, patchB=None, connectionstyle="arc3,rad=0.2", ), ) ############### # Now mark the current bond as used # thisbond = [bb[dmyi-1], bb[dmyi]] # Get index of used matching thisbond mark_used = np.where((np.logical_or(BL == bb[dmyi - 1], BL == bb[dmyi])).all(axis=1)) # mark_used = np.where((BL == thisbond).all(axis=1)) if not used[mark_used, 0]: # print 'marking bond [', thisbond, '] as used' used[mark_used, 0] = True else: # Get index of used matching reversed thisbond (this list boolean is directional) # mark_used = np.where((BL == thisbond[::-1]).all(axis=1)) # Used this bond in reverse order used[mark_used, 1] = True # print 'used = ', used dmyi += 1 if check: print 'bb = ', bb polygons.append(bb) ############### # Check new polygon if viewmethod: if first_check: ax1.plot(xy[:, 0], xy[:, 1], 'k.') for i in range(len(xy)): ax1.text(xy[i, 0] + 0.2, xy[i, 1], str(i)) for dmyi in range(len(bb)): nxt = bb[np.mod(dmyi + 1, len(bb))] ax1.annotate("", xy=(xy[bb[dmyi], 0], xy[bb[dmyi], 1]), xycoords='data', xytext=(xy[nxt, 0], xy[nxt, 1]), textcoords='data', arrowprops=dict(arrowstyle="->", color="r", shrinkA=5, shrinkB=5, patchA=None, patchB=None, connectionstyle="arc3,rad=0.2", ), ) ax2.cla() ax2.imshow(used, aspect=1. / len(used), interpolation='none') print 'polygons = ', polygons # plt.show() plt.pause(0.00001) ############### else: # Check for remaining bonds unused in reverse order (B-->A) # print 'CHECKING REVERSE (B-->A): ' todoBA = np.where(~used[:, 1])[0] # print 'len(todoBA) = ', len(todoBA) if len(todoBA) > 0: bond = BL[todoBA[0]] ############### # # check if viewmethod: plt.annotate("", xy=(xy[bb[dmyi], 0], xy[bb[dmyi], 1]), xycoords='data', xytext=(xy[nxt, 0], xy[nxt, 1]), textcoords='data', arrowprops=dict(arrowstyle="->", color="b", shrinkA=5, shrinkB=5, patchA=None, patchB=None, connectionstyle="arc3,rad=0.6", ), ) # ############### # bb will be list of polygon indices # Start with orientation going from bond[0] to bond[1] nxt = bond[0] bb = [bond[1], nxt] dmyi = 1 # define 'previous angle' as backwards of current angle -- ie angle(prev-current_pos) # Must include effect of PV on this angle -- do in ref frame of nxt particle PVind = np.argwhere(NL[nxt] == bond[1])[0][0] addx = PVx[nxt, PVind] addy = PVy[nxt, PVind] xyb0 = xy[bond[1], :] + np.array([addx, addy]) prev_angle = np.arctan2(xyb0[1] - xy[nxt, 1], xyb0[0] - xy[nxt, 0]) # .ravel() # print '\n---------\n' # print 'bb start = ', bb # print 'xy[nxt] = ', xy[nxt] # print 'addx = ', addx # print 'addy = ', addy # print 'xyb0 = ', xyb0 # print 'prev_angle = ', prev_angle/np.pi # print 'type(prev_angle) = ', type(prev_angle) # as long as we haven't completed the full outer polygon, add nextIND while nxt != bond[1]: n_tmp = NL[nxt, np.argwhere(KL[nxt]).ravel()] # Exclude previous boundary particle from the neighbors array, unless its the only one # (It cannot be the only one, if we removed dangling bonds) if len(n_tmp) == 1: '''The bond is a lone bond, not part of a triangle.''' neighbors = n_tmp else: neighbors = np.delete(n_tmp, np.where(n_tmp == bb[dmyi - 1])[0]) ######## # check if neighbors CAN be connected across periodic bc-- ie if particle on finite boundary (finbd) if nxt in finbd: # Since on finite system boundary, particle could have periodic bonds # Find x values to add to neighbors, by first getting indices of row of PV (same as of NL) matching neighbors # ALL CALCS in frame of reference of NXT particle PVinds = [np.argwhere(NL[nxt] == nnn)[0][0] for nnn in neighbors] addx = PVx[nxt, PVinds] addy = PVy[nxt, PVinds] xynb = xy[neighbors, :] + np.dstack([addx, addy])[0] xynxt = xy[nxt, :] # print '\n' # print 'nxt = ', nxt # print 'neighbors = ', neighbors # print 'xy[neighbors,:] =', xy[neighbors,:] # print 'addxy = ', np.dstack([addx, addy])[0] # print 'xynb = ', xynb # print 'xynxt = ', xynxt current_angles = np.arctan2(xynb[:, 1] - xynxt[1], xynb[:, 0] - xynxt[0]).ravel() angles = np.mod(current_angles - prev_angle, 2 * np.pi) selectIND = np.where(angles == max(angles))[0][0] # print 'selectIND = ', selectIND # print 'current_angles = ', current_angles/np.pi # print 'prev_angle = ', prev_angle/np.pi # print 'angles = ', angles/np.pi # redefine previous angle as backwards of current angle -- ie angle(nxt - neighbor ) prev_angletmp = np.arctan2(xynxt[1] - xynb[:, 1], xynxt[0] - xynb[:, 0]).ravel() prev_angle = prev_angletmp[selectIND] # print 'new prev_angle = ', prev_angle/np.pi # print 'NL[nxt] = ', NL[nxt] # print 'bb = ', bb # # CHECK # ax1 = plt.gca() # ax1.plot(xy[:,0],xy[:,1],'k.') # for i in range(len(xy)): # ax1.text(xy[i,0]+0.2,xy[i,1],str(i)) # plt.arrow(xynxt[0], xynxt[1], np.cos(angles[selectIND]), np.sin(angles[selectIND]),fc='r', ec='r') # plt.arrow(xynb[selectIND,0], xynb[selectIND,1], np.cos(prev_angle), np.sin(prev_angle),fc='b', ec='b') # plt.show() else: current_angles = np.arctan2(xy[neighbors, 1] - xy[nxt, 1], xy[neighbors, 0] - xy[nxt, 0]).ravel() angles = np.mod(current_angles - prev_angle, 2 * np.pi) # redefine previous angle as backwards of current angle -- ie angle(prev-current_pos) xynxt = xy[nxt, :] xynb = xy[neighbors, :] prev_angletmp = np.arctan2(xynxt[1] - xynb[:, 1], xynxt[0] - xynb[:, 0]).ravel() selectIND = np.where(angles == max(angles))[0][0] # print '\n' # print 'nxt = ', nxt # print 'bb = ', bb # print 'neighbors = ', neighbors # print 'current_angles = ', current_angles/np.pi # print 'prev_angle = ', prev_angle/np.pi # print 'angles = ', angles/np.pi # print 'selectIND = ', selectIND # print('xynxt[1] - xynb[:,1], xynxt[0] - xynb[:,0] = ', xynxt[1] - xynb[:,1], # xynxt[0] - xynb[:,0]) # print('np.arctan2(xynxt[1] - xynb[:,1], xynxt[0] - xynb[:,0]) = ', # np.arctan2(xynxt[1] - xynb[:,1], xynxt[0] - xynb[:,0])) # print 'prev_angletmp = ', prev_angletmp/np.pi prev_angle = prev_angletmp[selectIND] # print 'new prev_angle = ', prev_angle/np.pi ############### nxt = neighbors[angles == max(angles)][0] bb.append(nxt) ############### # Check if viewmethod: # If checking individual bonds # ax1 = plt.gca() # ax1.plot(xy[:,0],xy[:,1],'k.') # for i in range(len(xy)): # ax1.text(xy[i,0]+0.2,xy[i,1],str(i)) plt.annotate("", xy=(xy[bb[dmyi], 0], xy[bb[dmyi], 1]), xycoords='data', xytext=(xy[nxt, 0], xy[nxt, 1]), textcoords='data', arrowprops=dict(arrowstyle="->", color="b", shrinkA=5, shrinkB=5, patchA=None, patchB=None, connectionstyle="arc3,rad=0.6", ), ) # plt.show() ############### # Now mark the current bond as used --> note the inversion of the bond order to match BL thisbond = [bb[dmyi], bb[dmyi - 1]] # Get index of used matching [bb[dmyi-1],nxt] mark_used = np.where((BL == thisbond).all(axis=1)) if len(mark_used) > 0: used[mark_used, 1] = True else: messg = 'Cannot mark polygon bond as used: this bond was already used in its attempted' + \ ' orientation. (All bonds in first column should already be marked as used.)' raise RuntimeError(messg) dmyi += 1 polygons.append(bb) # print 'added polygon = ', bb # Check new polygon if viewmethod: if first_check: ax1.plot(xy[:, 0], xy[:, 1], 'k.') for i in range(len(xy)): ax1.text(xy[i, 0] + 0.2, xy[i, 1], str(i)) for dmyi in range(len(bb)): nxt = bb[np.mod(dmyi + 1, len(bb))] ax1.annotate("", xy=(xy[bb[dmyi], 0], xy[bb[dmyi], 1]), xycoords='data', xytext=(xy[nxt, 0], xy[nxt, 1]), textcoords='data', arrowprops=dict(arrowstyle="->", color="b", shrinkA=5, shrinkB=5, patchA=None, patchB=None, connectionstyle="arc3,rad=0.6", ), ) ax2.cla() ax2.imshow(used) # plt.show() plt.pause(0.0001) ############### else: # All bonds have been accounted for print 'all finished with finding polygons...' finished = True # check if viewmethod: plt.show() # Check for duplicates (up to cyclic permutations and inversions) in polygons # Note that we need to ignore the last element of each polygon (which is also starting pt) keep = np.ones(len(polygons), dtype=bool) for ii in range(len(polygons)): polyg = polygons[ii] for p2 in polygons[ii + 1:]: if is_cyclic_permutation(polyg[:-1], p2[:-1]): keep[ii] = False polygons = [polygons[i] for i in np.where(keep)[0]] # Remove duplicates via inversion (maybe not necessary?) # Remove the polygon which is the entire lattice boundary, except dangling bonds if not periB.any(): print 'Removing entire lattice boundary from list of polygons...' boundary = extract_boundary(xy, NL, KL, BL) # print 'boundary = ', boundary keep = np.ones(len(polygons), dtype=bool) for ii in range(len(polygons)): polyg = polygons[ii] if is_cyclic_permutation(polyg[:-1], boundary.tolist()): keep[ii] = False elif is_cyclic_permutation(polyg[:-1], boundary[::-1].tolist()): keep[ii] = False polygons = [polygons[i] for i in np.where(keep)[0]] # Check order of each polygon so that it is oriented counterclockwise # for polys in polygons: # angle_poly = 0 # # Make sure that oriented counterclockwise # print 'polys = ', polys # for i in range(len(polys)): # p0 = polys[ np.mod(i-1, len(polys)-1)] # p1 = polys[i] # p2 = polys[ np.mod(i+1,len(polys)-1) ] # print 'p0,p1,p2 = ', p0, p1, p2 # angle_tmp = np.mod(np.arctan2(xy[p2,1]-xy[p1,1], xy[p2,0]-xy[p1,0]) - np.arctan2( xy[p1,1]-xy[p0,1], # xy[p1,0]-xy[p0,0] ), 2*np.pi) # print 'angle_tmp = ', angle_tmp # angle_poly += angle_tmp # # print 'angle = ', angle_poly/6. if check: polygons2PPC(xy, polygons, check=True) return polygons
[docs]def pairwise(iterable): """Convert list into tuples with adjacent items tupled, like: s -> (s0,s1), (s1,s2), (s2, s3), ... Parameters ---------- iterable : list or other iterable the list to tuple Returns ------- out : list of tuples adjacent items tupled """ a, b = tee(iterable) next(b, None) return zip(a, b)
[docs]def periodic_polygon_indices2xy(poly, xy, BLdbl, PVxydict): """Convert a possibly-periodic polygon object that lists indices of vertices/points into an array of vertex coordinates. This is nontrivial only because of the existence of periodic boundary conditions. This function returns the polygon as it would appear to the zero-index particle (the first particle indexed in the variable 'poly'. Parameters ---------- poly : list of ints The indices of xy, indexing the polygon in question. The first index may or may not be repeated as the last --- it seems to work either way xy : #pts x 2 float array the coordinates of the vertices of all polygons in the network BLdbl : #bonds x 2 signed int array Bond list reapeated twice, with the second time being a copy of the first, but flipped (the convention of doubling BL is a matter of speedup). ie BLdbl = np.vstack((BL, np.fliplr(BL))) PVxydict : dict dictionary of periodic bonds (keys) to periodic vectors (values) If key = (i,j) and val = np.array([ 5.0,2.0]), then particle i sees particle j at xy[j]+val --> transforms into: ijth element of PVx is the x-component of the vector taking NL[i,j] to its image as seen by particle i Returns ------- xypoly : list of coordinates (each a list of 2 floats) The polygon as it appears to the first particle/vertex in the input list poly periodicpoly : bool Whether the polygon traverses a periodic boundary """ periodicpoly = False tups = pairwise(poly) xypoly = [] pervec = np.array([0., 0.]) # Add first point to coordinate list xypoly.append((xy[tups[0][0], :] + pervec).tolist()) for tup in tups: # Check if the matching row of BL is all positive --> if so, then not periodic bond # NOTE: If tup is positive, and bond is periodic, then will not register a match! match = (BLdbl[:, 0] == tup[0]) & (BLdbl[:, 1] == tup[1]) if match.any() and (BLdbl[match, :] > -0.5).all(): xypoly.append((xy[tup[1], :] + pervec).tolist()) else: # # Check if the matching row of BL flippedlr is all positive --> if so, then not periodic bond # match2 = (BL[:, 0] == tup[1]) & (BL[:, 1] == tup[0]) # if match2.any() and (BL[match2, :] > -0.5).all(): # xypoly.append((xy[tup[0], :] + pervec).tolist()) # xypoly.append((xy[tup[1], :] + pervec).tolist()) # else: # Declare that this polygon exists on at least two sides periodicpoly = True # Add periodic vector (PVx, PVy) to forming polygon try: pervec += PVxydict[tup] except KeyError: pervec += -PVxydict[(tup[1], tup[0])] xypoly.append((xy[tup[1], :] + pervec).tolist()) return xypoly, periodicpoly
[docs]def polygons2PPC(xy, polygons, BL=None, PVxydict=None, check=False): """Create list of polygon patches from polygons indexing xy. If the network is periodic, BL, PVx, PVy are required. Parameters ---------- xy : NP x 2 float array coordinates of particles polygons: list of lists of ints list of polygons, each of which are a closed list of indices of xy check: bool whether to plot the polygon patch collection Returns ------- PPC: list of patches list for a PatchCollection object """ # Prepare a polygon patch collection plot if PVxydict is not None and PVxydict != {}: BLdbl = np.vstack((BL, np.fliplr(BL))) PPC = [] for poly in polygons: if PVxydict is not None and PVxydict != {}: xypoly, periodicpoly = periodic_polygon_indices2xy(poly, xy, BLdbl, PVxydict) # Add to list of polygon path patches pp = Path(np.array(xypoly), closed=True) ppp = patches.PathPatch(pp, lw=2) PPC.append(ppp) # If polygon was periodic, get other permutations of the polygon if periodicpoly: # print 'Dealing with periodic polygon here...' # make sure that polygon doesn't have repeated index # print 'poly = ', poly if poly[-1] == poly[0]: poly = poly[0:len(poly) - 1] oldpolys = [xypoly[0:len(xypoly) - 1]] for ii in range(len(poly)): # permute polygon, check if it is a cyclic permutation for any previously-plotted polygons poly = np.roll(poly, 1) # print 'rolled poly = ', poly newxyp, trash = periodic_polygon_indices2xy(poly, xy, BLdbl, PVxydict) # print 'oldxyp[:, 0] = ', np.array(oldpolys[0])[:, 0] # print 'newxyp[:, 0] = ', np.array(newxyp)[:, 0] xcyclic = np.array([is_cyclic_permutation(np.array(oldp)[:, 0].tolist(), np.array(newxyp)[:, 0].tolist()) for oldp in oldpolys]) ycyclic = np.array([is_cyclic_permutation(np.array(oldp)[:, 1].tolist(), np.array(newxyp)[:, 1].tolist()) for oldp in oldpolys]) if not xcyclic.any() or not ycyclic.any(): # print '\n\n\n\n\n adding new periodic polygon! \n\n\n\n' pp = Path(np.array(np.vstack((np.array(newxyp), np.array(newxyp)[0, :]))), closed=True) ppp = patches.PathPatch(pp, lw=2) PPC.append(ppp) oldpolys.append(newxyp) else: pp = Path(xy[poly], closed=True) ppp = patches.PathPatch(pp, lw=2) PPC.append(ppp) if check: fig = plt.figure() ax = fig.add_subplot(111) p = PatchCollection(PPC, cmap=cm.jet, alpha=0.5) colors = 100 * np.random.rand(len(PPC)) p.set_array(np.array(colors)) ax.add_collection(p) xlim = max(abs(xy[:, 0])) ylim = max(abs(xy[:, 1])) ax.set_xlim(-xlim, xlim) ax.set_ylim(-ylim, ylim) plt.show() plt.clf() return PPC
# def polygons2periodic_NLNNN_and_KLNNN(polygons,KL): # pass
[docs]def is_cyclic_permutation(A, B): """Check if list A is a cyclic permutation of list B. Parameters ---------- A : list B : list Returns ---------- bool Whether A is a cylic permutation of B """ # Check if same length if len(A) != len(B): return False # Check that contain the same elements if set(A) == set(B): longlist = A + A if contains_sublist(longlist, B): return True else: return False else: return False
[docs]def is_cyclic_permutation_numpy(A, B): """Check if 1d numpy array A is a cyclic permutation of 1d numpy array B. HAVENT TESTED THIS BUT SHOULD WORK? Parameters ---------- A : 1d numpy array B : 1d numpy array Returns ---------- bool Whether A is a cylic permutation of B """ # Check if same length if len(A) != len(B): return False # Check that contain the same elements if set(A) == set(B): longlist = np.hstack((A, A)) n = len(A) if any((A == longlist[i:i + n]).all() for i in xrange(len(longlist) - n + 1)): return True else: return False else: return False
[docs]def contains_sublist(lst, sublst): """Check if a list contains a sublist (same order) Parameters ---------- lst, sublst : lists The list and sublist to test Returns ---------- bool Whether sublst is a sublist of lst. """ n = len(sublst) return any((sublst == lst[i:i + n]) for i in xrange(len(lst) - n + 1))
def trim_cols_NLandKL(NL, KL): # Now trim the # columns down--> any column with all zeros gets cut done_cutting = False while not done_cutting: # print 'np.where(KL[:,-1]) =', np.where(KL[:,-1]) if np.where(KL[:, -1])[0].size > 0: done_cutting = True # print 'Chopped last column, now shape = ', np.shape(KL) else: KLnew = copy.deepcopy(KL[:, 0:-1]) KL = KLnew NL = NL[:, 0:np.shape(KL)[1]] return NL, KL
[docs]def azimuthalAverage(image, center=None): """ Calculate the azimuthally averaged radial profile, by Jessica R. Lu. Parameters ---------- image : N x M numpy array The 2D image center : 1 x 2 numpy float (or int) array The [x,y] pixel coordinates used as the center. The default is None, which then uses the center of the image (including fracitonal pixels). Returns ----------- radial_prof : 1 x N numpy float array The values of the image, ordered by radial distance """ # Calculate the indices from the image y, x = np.indices(image.shape) if not center: center = np.array([(x.max() - x.min()) / 2.0, (x.max() - x.min()) / 2.0]) r = np.hypot(x - center[0], y - center[1]) # Get sorted radii ind = np.argsort(r.flat) r_sorted = r.flat[ind] i_sorted = image.flat[ind] # Get the integer part of the radii (bin size = 1) r_int = r_sorted.astype(int) # Find all pixels that fall within each radial bin. deltar = r_int[1:] - r_int[:-1] # Assumes all radii represented rind = np.where(deltar)[0] # location of changed radius nr = rind[1:] - rind[:-1] # number of radius bin # Cumulative sum to figure out sums for each radius bin csim = np.cumsum(i_sorted, dtype=float) tbin = csim[rind[1:]] - csim[rind[:-1]] radial_prof = tbin / nr return radial_prof
[docs]def remove_pts(keep, xy, BL, NN='min', check=False): """ Remove particles not indexed by keep from xy, BL, then rebuild NL, and KL. Zeros out inds where they appear in NL and KL and reorders rows to put zeros last. Parameters ---------- keep : NP_0 x 1 int array or bool array indices (of xy, of particles) to keep xy : NP_0 x 2 float array points living on vertices of dual to triangulation BL : Nbonds_0 x 2 int array or empty list Each row is a bond and contains indices of connected points NN : int or string 'min' Number of nearest neighbors in each row of KL, NL Returns ---------- xy : NP x 2 float array points living on vertices of dual to triangulation NL : NP x NN int array Neighbor list KL : NP x NN int array Connectivity list BL : Nbonds x 2 int array Each row is a bond and contains indices of connected points """ NP = len(xy) # print 'NP = ', NP # print 'len(keep) = ', len(keep) if check: if (BL > -0.5).all(): PVx = [] PVy = [] PVxydict = {} movie_plot_2D(xy, BL, bs=0.0 * BL[:, 0], PVx=PVx, PVy=PVy, PVxydict=PVxydict, colormap='BlueBlackRed', title='Input to remove points', show=True) # ensure that keep is int array of indices, not bool if keep.dtype == 'bool': print 'converting bool keep to int array...' keep = np.where(keep)[0] else: keep = np.sort(keep) # print 'keep = ', keep remove = np.setdiff1d(np.arange(NP), keep) # print 'keep = ', keep xyout = xy[keep, :] # print 'BL = ', BL if BL is not None and BL != []: # Make BLout # Find rows of BL for which both elems are in keep inBL0 = np.in1d(np.abs(BL[:, 0]), keep) inBL1 = np.in1d(np.abs(BL[:, 1]), keep) keepBL = np.logical_and(inBL0, inBL1) BLt = BL[keepBL, :] if check: print 'Removed bonds with removed particle as endpt:' print 'BLt = ', BLt # Make xyout # Reorder BLout to match new coords by making map from old to new # (Lower elements of NL by #particles removed) BL_r = copy.deepcopy(BLt) # BL to reorder if (BL < 0).any(): for ind in remove: BL_r[np.abs(BLt) > ind] = np.sign(BL_r[np.abs(BLt) > ind]) * (np.abs(BL_r[np.abs(BLt) > ind]) - 1) else: for ind in remove: BL_r[BLt > ind] = (BL_r[BLt > ind] - 1) # print 'max(BL_r) = ', max(BL_r.ravel()) # print 'BL = ', BL_r print '\nRemoved ', len(remove), ' particles...' BLout = np.sort(BL_r, axis=1) BLtrim = unique_rows(BLout) # print 'BLout = ', BLout NL, KL = BL2NLandKL(BLtrim, NN=NN) else: NL = [] KL = [] BLtrim = [] if check: display_lattice_2D(xyout, BLtrim, colorz=True, title='Network after removing points') return xyout, NL, KL, BLtrim
[docs]def compute_bulk_z(xy, NL, KL, BL): """Compute the average coordination number of the bulk particles in a lattice/network. Parameters ---------- xy : NP x dim array positions of particles NL : NL x NN array neighbor list KL : NL x NN array connectivity list Returns ---------- z : float average coordination number of the bulk particles in the network """ NP = len(xy) boundary = extract_boundary(xy, NL, KL, BL) bulk = np.setdiff1d(np.arange(NP), boundary) # Compute the starting z in the bulk countKL = [KL[jj] for jj in bulk] # print 'found = ', np.count_nonzero(countKL), ' connections for ', NP_bulk, ' bulk particles...' z = float(np.count_nonzero(countKL)) / float(len(bulk)) return z
[docs]def cut_bonds_z_random(xy, NL, KL, BL, target_z, min_coord=2, bulk_determination='Triangulation', check=False): """Cut bonds in network so that average bulk coordination is z +/- 1 bond/system size. Note that 'boundary' is not a unique array if there are dangling bonds. Parameters ---------- xy : NP x dim array positions of particles NL : NL x NN array neighbor list KL : NL x NN array connectivity list BL : array of dimension #bonds x 2 Each row is a bond and contains indices of connected points target_z : float target average bulk coordination, to be approximately enforced by cutting bonds bulk_determination : string ('Triangulation' 'Cutter' 'Endpts') How to determine which bonds are in the bulk and which are on the boundary Returns ----------- NLout : NL x NN int array neighbor list KLout : NL x NN int array connectivity list BLout : #bonds x 2 int array Each row is a bond and contains indices of connected points """ print ' Cutting bonds z...' NP = len(xy) NN = np.shape(NL)[1] # Identify boundary pts, bulk pts print ' cut_bonds_z : extract boundary...' boundary = extract_boundary(xy, NL, KL, BL) # print 'boundary = ', boundary bulk = np.setdiff1d(np.arange(NP), boundary) NP_bulk = len(bulk) NP_bound = len(np.unique(boundary)) print 'NP_bound = ', NP_bound print 'NP_bulk = ', NP_bulk if bulk_determination == 'Triangulation': # Form indices of BL in bulk. Bulk bonds appear in two simplices. # CHANGE THIS TO TEST IF BOND TWO SIMPLICES TRI = BL2TRI(BL, xy) Binds_list = [] for ii in range(len(BL)): row = BL[ii] # get rows of TRI where each elem of row lives is_a = np.where(TRI == row[0])[0] is_b = np.where(TRI == row[1])[0] # The intersection of those rows gives where both live simplices = np.intersect1d(is_a, is_b) # print 'simplices = ', simplices # print 'np.size(simplices) = ', np.size(simplices) # If more than one simplex, bulk bond if np.size(simplices) < 2: # add to boundary list Binds_list.append(ii) # print ' --> Binds = ', Binds_list Binds = np.array(Binds_list).ravel() # Get the BL indices of bulk bonds --> (binds) binds = np.setdiff1d(np.arange(len(BL)), Binds) elif bulk_determination == 'Endpts': # Define bulk bonds as connecting at least one bulk particle is_a = np.in1d(BL[:, 0], bulk) is_b = np.in1d(BL[:, 1], bulk) binds = np.where(np.logical_or(is_a, is_b))[0] Binds = np.setdiff1d(np.arange(len(BL)), binds) else: raise RuntimeError('ERROR: argument <bulk_determination> did not match known method!') # print 'binds = ', binds # print 'Binds = ', Binds print 'len(binds) = ', len(binds) print 'len(Binds) = ', len(Binds) # Check if check: # plt.triplot(xy[:,0], xy[:,1], TRI, 'bo-') for bii in binds: XX = xy[BL[bii], 0] YY = xy[BL[bii], 1] plt.plot(XX, YY, 'b-') for Bii in Binds: XX = xy[BL[Bii], 0] YY = xy[BL[Bii], 1] plt.plot(XX, YY, 'r-') # for i in range(len(xy)): # plt.text(xy[i,0]+0.2,xy[i,1],str(i)) plt.gca().set_aspect('equal') plt.show() # Compute the starting z in the bulk countKL = [KL[jj] for jj in bulk] # print 'found = ', np.count_nonzero(countKL), ' connections for ', NP_bulk, ' bulk particles...' z_start = float(np.count_nonzero(countKL)) / float(NP_bulk) print 'z_start = ', z_start print 'target_z = ', target_z # number of bonds to cut in the bulk # Be sure to divide the number of bonds by 2, since each bond double counts nbulk2cut = int(max([0, round((z_start - target_z) * 0.5 * float(NP_bulk))])) print 'nbulk2cut = ', nbulk2cut # number of bonds to cut in the boundary = nbulk2cut * (# boundary bonds)/(#bulk bonds) nB2cut = int(round(nbulk2cut * float(len(Binds)) / float(len(binds)))) print 'nB2cut = ', nB2cut # CUT RANDOM BONDS ############################################ ## DO BOUNDARY FIRST --> to avoid dangling particles # Choose nB2cut randomly from bulk # Shuffle bulk in-place np.random.shuffle(Binds) # Now work slowly towards selecting nbulk2cut: of the bonds, # but ensure that never leave a particle dangling without bonds done_cutting = False dmyi = 0 # Set up mask for BL mask = np.ones(len(BL), dtype=bool) ################################# # # Check : # plt.figure() # plt.gca().set_aspect('equal') # for ii in range(len(BL)): # XX = xy[BL[ii],0] # YY = xy[BL[ii],1] # plt.plot(XX, YY, 'b-') # plt.text(np.mean(XX), np.mean(YY), str(ii)) # plt.show() ################################# while not done_cutting: if len(np.where(mask == False)[0]) == nB2cut: done_cutting = True else: if np.mod(dmyi, 200) == 1: print 'cutting boundary bond: pass ', dmyi, ' (need to cut', nB2cut, ')' # consider adding dmyi element of bind to cut (make a test list) test = copy.deepcopy(mask) test[Binds[dmyi]] = False BLtmp = BL[test] # Check that BL leads to no dangling particles KLtmp = BL2KL(BLtmp, NL) # if all the rows in KLtmp have at least one nonzero bond, add dmyi to cut # print 'KLtmp.any(axis=1) = ', KLtmp.any(axis=1) if (np.where(~KLtmp.any(axis=1))[0]).size > 0: dmyi += 1 else: mask[Binds[dmyi]] = False dmyi += 1 ############################################ # Choose nbulk2cut randomly from bulk # Shuffle bulk in-place np.random.shuffle(binds) # print 'binds = ', binds # Now work slowly towards selecting nbulk2cut: of the bonds, # but ensure that never leave a particle dangling without bonds done_cutting = False dmyi = 0 while not done_cutting: if len(np.where(mask == False)[0]) == nB2cut + nbulk2cut: done_cutting = True else: if np.mod(dmyi, 200) == 1: print 'cutting bulk bond: pass ', dmyi, ' (need to cut', nbulk2cut, ')' # consider adding dmyi element of bind to cut (make a test list) test = copy.deepcopy(mask) test[binds[dmyi]] = False BLtmp = BL[test] # Check that BL leads to no dangling particles KLtmp = BL2KL(BLtmp, NL) # print 'KL = ', KLtmp # print 'np.where(~KLtmp.any(axis=1))[0] = ', np.where(~KLtmp.any(axis=1))[0] # if all the rows in KLtmp have at least one nonzero bond, add dmyi to cut if (np.where(~KLtmp.any(axis=1))[0]).size > min_coord - 1: dmyi += 1 else: mask[binds[dmyi]] = False dmyi += 1 # drop the nbulk2cut + nB2cut rows from total Bond List BL = BL[mask] # print 'BLout = ', BLout NL, KL = BL2NLandKL(BL, NN=NN) if check: display_lattice_2D(xy, BL) print '\nReturning lattice with ', len(BL), ' bonds for ', NP, ' particles...' print 'KL[bulk] = ', KL[bulk] return NL, KL, BL
[docs]def cut_bonds_z_highest(xy, NL, KL, BL, target_z, check=False): """Cut bonds in network so that average bulk coordination is z +/- 1 bond/system size, using iterative procedure based on connectivity. Note that 'boundary' is not a unique array if there are dangling bonds. The boundary is not treated at all, since it is very hard to treat it correctly. Therefore, one MUST crop out the desired region after lattice creation. Parameters ---------- xy : NP x dim array positions of particles NL : NL x NN array neighbor list KL : NL x NN array connectivity list BL : array of dimension #bonds x 2 Each row is a bond and contains indices of connected points target_z : float target average bulk coordination, to be approximately enforced by cutting bonds Returns ----------- NL : NL x NN int array neighbor list KL : NL x NN int array connectivity list BL : #bonds x 2 int array Each row is a bond and contains indices of connected points """ print ' Cutting bonds z...' NP = len(xy) NN = np.shape(NL)[1] # Identify boundary pts, bulk pts print ' cut_bonds_z : extract boundary...' boundary = extract_boundary(xy, NL, KL, BL) # print 'boundary = ', boundary bulk = np.setdiff1d(np.arange(NP), boundary) NP_bulk = len(bulk) NP_bound = len(np.unique(boundary)) print 'NP_bound = ', NP_bound print 'NP_bulk = ', NP_bulk # Define bulk bonds as connecting at least one bulk particle is_a = np.in1d(BL[:, 0], bulk) is_b = np.in1d(BL[:, 1], bulk) binds = np.where(np.logical_or(is_a, is_b))[0] Binds = np.setdiff1d(np.arange(len(BL)), binds) BLbulk = BL[binds] BLboun = BL[Binds] # bBinds bonds connect bulk to boundary # Treat these as is connecting bulk(z) to bulk(z) bBinds = np.where(np.logical_xor(is_a, is_b))[0] BLbB = BL[bBinds] print 'len(binds) = ', len(binds) print 'len(Binds) = ', len(Binds) # Check if check: # plt.triplot(xy[:,0], xy[:,1], TRI, 'bo-') for bii in binds: XX = xy[BL[bii], 0] YY = xy[BL[bii], 1] plt.plot(XX, YY, 'b-') for Bii in Binds: XX = xy[BL[Bii], 0] YY = xy[BL[Bii], 1] plt.plot(XX, YY, 'r-') # for i in range(len(xy)): # plt.text(xy[i,0]+0.2,xy[i,1],str(i)) plt.gca().set_aspect('equal') plt.show() # number of bonds to cut in the bulk # Be sure to divide the number of bonds by 2, since each bond double counts # Can write in terms of bonds? 2have = zt # nbulk2cut = int(max([0,round((z_start - target_z)*0.5*float(NP_bulk))])) # nbulk2have = len(binds) - nbulk2cut # print 'nboun2have = ', nboun2have # print 'nbulk2have = ', nbulk2have # CUT BONDS FROM HIGHEST Z NODES (sum of endpts) # Unfortunately, this has to be done iteratively. # Algorithm: find zvals of all bonds. For all bonds with zval = max(zval), # cut all the bonds that don't share endpts with any of the other bonds. # Find these by going through in-place-randomized B2cut and cross off if later bonds share indices. # Let boundary bonds be cut, or not, and pay no attention to them, since lattice will be cropped. # First cut most coordinated, whether on bulk or boundary, but keep track of which. # Get bonds with highest z pairs of nodes NN = np.shape(KL)[1] zz = np.sum(KL, axis=1) # print 'zz = ', zz zbulk = float(np.sum(zz[bulk])) / float(len(bulk)) print 'zbulk so far = ', zbulk # As long as we haven't cut enough bonds, cut some more while zbulk > target_z: print 'zbulk = ', zbulk zb = zz[BL[:, 0]] + zz[BL[:, 1]] zcut = np.where(zb == max(zb))[0] np.random.shuffle(zcut) B2cut = BL[zcut] # print 'B2cut = ', B2cut # Check --> show bond numbers and bond to cut if check: display_lattice_2D(xy, BL, close=False) # for ii in range(len(BL)): # plt.text((xy[BL[ii,0],0]+xy[BL[ii,1],0])*0.5,(xy[BL[ii,0],1]+xy[BL[ii,1],1])*0.5,str(ii)) # plt.text((xy[BL[ii,0],0]+xy[BL[ii,1],0])*0.5,(xy[BL[ii,0],1]+xy[BL[ii,1],1])*0.5,str(zb[ii])) for row in B2cut: plt.plot([xy[row[0], 0], xy[row[1], 0]], [xy[row[0], 1], xy[row[1], 1]], 'r-') plt.title('Initial counting marks these') plt.pause(0.01) plt.clf() # print 'B2cut = ', B2cut # Cross off if later bonds share indices keep = np.ones(len(B2cut), dtype=bool) for ii in range(len(B2cut)): row = B2cut[ii] if row[0] in B2cut[ii + 1:, :].ravel(): # print 'found ', row[0], 'in rest of array ' # print ' --> len BL[ii+1:,:] = ', len(B2cut[ii+1:,:] ) keep[ii] = False elif row[1] in B2cut[ii + 1:, :].ravel(): keep[ii] = False # print 'keep = ', keep # print 'keep.any() = ', keep.any() if keep.any(): B2cut = B2cut[keep] else: print 'The highest nodes are all connected to at least one other. Killing one bond...' B2cut = B2cut[0:1] # Only interested in the bulk bonds for measurement, but cutting boundary # bonds will get us out of a situation where bulk is less coordinated than # boundary so don't do --> B2cut = intersect2d(B2cut,BLbulk) N2cut = len(B2cut) # See what would happen if we cut all of these BLt = setdiff2d(BL, B2cut) NLt, KLt = BL2NLandKL(BLt, NP=NP, NN=NN) zzt = np.sum(KLt, axis=1) zbulk = np.float(np.sum(zzt[bulk])) / float(len(bulk)) # If we can cut all of these, do that. Otherwise, cut only as many as needed after shuffling. if len(np.where(zzt == 0)[0]) > 0: print 'There are dangling points. Removing bonds2cut that would make these...' # There are dangling points. # Remove the bonds that make zzt elems zero from the bonds to cut list # and recalculate. dangle_pts = np.where(zzt == 0)[0] # protect dangle points --> there is only one bond to find since we have run a "keep" search on B2cut inb0 = np.where(np.in1d(B2cut[:, 0], dangle_pts))[0] inb1 = np.where(np.in1d(B2cut[:, 1], dangle_pts))[0] keep = np.setdiff1d(np.arange(len(B2cut)), inb0) keep = np.setdiff1d(keep, inb1) print 'Protecting dangling bond: keep for dangle =', keep # Check --> show bond numbers and bond to cut and protect (dangles) if check: display_lattice_2D(xy, BL, close=False) for ii in range(len(BL)): # plt.text((xy[BL[ii,0],0]+xy[BL[ii,1],0])*0.5,(xy[BL[ii,0],1]+xy[BL[ii,1],1])*0.5,str(ii)) plt.text((xy[BL[ii, 0], 0] + xy[BL[ii, 1], 0]) * 0.5, (xy[BL[ii, 0], 1] + xy[BL[ii, 1], 1]) * 0.5, str(zb[ii])) for row in B2cut: plt.plot([xy[row[0], 0], xy[row[1], 0]], [xy[row[0], 1], xy[row[1], 1]], 'r-') plt.plot([xy[B2cut[keep, 0], 0], xy[B2cut[keep, 1], 0]], [xy[B2cut[keep, 0], 1], xy[B2cut[keep, 1], 1]], 'b-', lw=5) plt.show() plt.clf() B2cut = B2cut[keep] N2cut = len(B2cut) BLt = setdiff2d(BL, B2cut) NLt, KLt = BL2NLandKL(BLt, NP=NP, NN=NN) zzt = np.sum(KLt, axis=1) zbulk = np.float(np.sum(zzt[bulk])) / float(len(bulk)) # If we end up in a place where these are the only bonds to cut, raise exception # --> means target_z is just too low for our given lattice. if np.size(B2cut) == 0: raise RuntimeError('target_z is too low for the given lattice! Cutting bonds led to dangling points.') if zbulk > target_z: print 'Still above: zbulk = ', zbulk # Check --> show bond numbers and bond to cut if check: display_lattice_2D(xy, BL, close=False) # for ii in range(len(BL)): # plt.text((xy[BL[ii,0],0]+xy[BL[ii,1],0])*0.5,(xy[BL[ii,0],1]+xy[BL[ii,1],1])*0.5,str(ii)) # plt.text((xy[BL[ii,0],0]+xy[BL[ii,1],0])*0.5,(xy[BL[ii,0],1]+xy[BL[ii,1],1])*0.5,str(zb[ii])) for row in B2cut: plt.plot([xy[row[0], 0], xy[row[1], 0]], [xy[row[0], 1], xy[row[1], 1]], 'r-') plt.pause(0.01) plt.clf() # move pointers BL, BLt = BLt, BL NL, NLt = NLt, NL KL, KLt = KLt, KL zz, zzt = zzt, zz else: print 'Approaching z = ', target_z, ' tuning one bond at a time...' # Cut a bond unless there is only one to cut # (in which case we are within threshold) if N2cut == 1: zbulk = 0. # move pointers BL, BLt = BLt, BL NL, NLt = NLt, NL KL, KLt = KLt, KL zz, zzt = zzt, zz else: # Check --> show bond numbers and bond to cut if check: display_lattice_2D(xy, BL, close=False) for ii in range(len(BL)): # plt.text((xy[BL[ii,0],0]+xy[BL[ii,1],0])*0.5,(xy[BL[ii,0],1]+xy[BL[ii,1],1])*0.5,str(ii)) plt.text((xy[BL[ii, 0], 0] + xy[BL[ii, 1], 0]) * 0.5, (xy[BL[ii, 0], 1] + xy[BL[ii, 1], 1]) * 0.5, str(zb[ii])) for row in B2cut: plt.plot([xy[row[0], 0], xy[row[1], 0]], [xy[row[0], 1], xy[row[1], 1]], 'r-') plt.pause(0.01) plt.clf() BL = setdiff2d(BL, B2cut[0:1]) NL, KL = BL2NLandKL(BL, NP=NP, NN=NN) zz = np.sum(KLt, axis=1) print 'zz = ', zz zbulk = np.float(np.sum(zz[bulk])) / float(len(bulk)) # IGNORE BOUNDARY: MUST CUT OUT DESIRED REGION. OTHERWISE, IT'S JUST TOO HARD TO MAKE IT RIGHT. # Only interested in the boundary bonds now # number of bonds to cut in the boundary = nbulkcut * (# boundary bonds)/(#bulk bonds) # nB2cut = int(round(nbulk2cut * float(len(Binds))/float(len(binds)))) # nboun2have = len(Binds) - nB2cut # # while nboun > nboun2have: # zz = np.sum(KL, axis=1) # zb = zz[BL[:,0]] + zz[BL[:,1]] # zcut = np.where(zb== max(zb))[0] # np.random.shuffle(zcut) # B2cut = BL[zcut] # # Only interested in the boundary bonds now # B2cut = intersect2d(B2cut,BLboun) # # Cross off if later bonds share indices # keep = np.ones(len(B2cut),dtype = bool) # for ii in range(len(B2cut)): # row = B2cut[ii] # if row[0] in BL[ii+1,:].ravel(): # keep[ii] = False # B2cut = B2cut[keep] # # Cut only as many as needed # nboun2cut = min([nboun - nboun2have, len(B2cut)]) # BL = setdiff2d(BL,B2cut[0:nboun2cut]) # nboun = len(intersect2d(BL,BLboun)) # print 'nbound so far =', nboun # NL, KL = BL2NLandKL(BL,NP=NP,NN=NN) zz = np.sum(KL, axis=1) zbulk = np.float(np.sum(zz[bulk])) / float(len(bulk)) print 'Tuned to zbulk = ', zbulk if check: display_lattice_2D(xy, BL, close=False) plt.show() print '\nReturning lattice with ', len(BL), ' bonds for ', NP, ' particles...' return NL, KL, BL
[docs]def setdiff2d(A, B): """Return row elements in A not in B. Used to be called remove_bonds_BL --> Remove bonds from bond list. Parameters ---------- A : N1 x M array Array to take rows of not in B (could be BL, for ex) B : N2 x M Array whose rows to compare to those of A Returns ---------- BLout : (usually N1-N2) x M array Rows in A that are not in B. If there are repeats in B, then length will differ from N1-N2. """ a1_rows = A.view([('', A.dtype)] * A.shape[1]) # print 'A.dtype = ', A.dtype # print 'B.dtype = ', B.dtype a2_rows = B.view([('', B.dtype)] * B.shape[1]) # Now trim those bonds from BL C = np.setdiff1d(a1_rows, a2_rows).view(A.dtype).reshape(-1, A.shape[1]) return C
[docs]def intersect2d(A, B): """Return row elements in A not in B. Used to be called remove_bonds_BL --> Remove bonds from bond list. Parameters ---------- A : N1 x M array Array to take rows of not in B (could be BL, for ex) B : N2 x M Array whose rows to compare to those of A Returns ---------- BLout : (usually N1-N2) x M array Rows in A that are not in B. If there are repeats in B, then length will differ from N1-N2. """ # print 'A = ', A # print 'B = ', B a1_rows = A.view([('', A.dtype)] * A.shape[1]) a2_rows = B.view([('', B.dtype)] * B.shape[1]) # Now trim those bonds from BL C = np.intersect1d(a1_rows, a2_rows).view(A.dtype).reshape(-1, A.shape[1]) return C
[docs]def cut_bonds_strain(xy, NL, KL, BM0, bstrain): """Cut bonds from KL (set elems of KL to zero) based on the bond strains. This is not finished since for now seems ok to convert to BL --> cut --> NL,KL Parameters ---------- xy : NP x dim array positions of particles NL : NL x NN array neighbor list KL : NL x NN array connectivity list BM0 : NL x NN array bond rest lengths Return ---------- KL, BLtrim, bL0trim """ NP, NN = np.shape(NL) BL = NL2BL(NL, KL) bL0 = BM2bL(NL, BM0, BL) BLtrim, bL0trim = cut_bonds_strain_BL(BL, xy, bL0, bstrain) KL = BL2KL(BLtrim, NL) # i2cut = (np.sqrt((xy[BL[:,0],0]-xy[BL[:,1],0])**2+(xy[BL[:,0],1]-xy[BL[:,1],1])**2) - bL0) < bstrain*bL0 return KL, BLtrim, bL0trim
[docs]def cut_bonds(BL, xy, thres): """Cuts bonds with LENGTHS greater than cutoff value. Parameters ---------- BL : array of dimension #bonds x 2 Each row is a bond and contains indices of connected points xy : array of dimension nx2 2D lattice of points (positions x,y) thres : float cutoff length between points Returns ---------- BLtrim : array of dimension #bonds x 2 Each row is a bond and contains indices of connected points, contains no bonds longer than thres""" i2cut = (xy[BL[:, 0], 0] - xy[BL[:, 1], 0]) ** 2 + (xy[BL[:, 0], 1] - xy[BL[:, 1], 1]) ** 2 < thres ** 2 BLtrim = BL[i2cut] return BLtrim
[docs]def cut_bonds_strain_BL(BL, xy, bL0, bstrain): """Cuts bonds with STRAIN greater than cutoff value based on BL, and return trimmed BL. Parameters ---------- BL : array of dimension #bonds x 2 Each row is a bond and contains indices of connected points xy : array of dimension nx2 2D lattice of points (positions x,y) bstrain : float breaking strain -- bonds strained above this amount are cut Returns ---------- BLtrim : array of dimension #bonds x 2 Each row is a bond and contains indices of connected points, contains no bonds longer than thres bL0trim : array of dimension #bonds x 1 unstrained bond length list: Each element is the length of the reference (unstrained) bond, in same order as rows of BL """ i2cut = (np.sqrt( (xy[BL[:, 0], 0] - xy[BL[:, 1], 0]) ** 2 + (xy[BL[:, 0], 1] - xy[BL[:, 1], 1]) ** 2) - bL0) < bstrain * bL0 bL0trim = bL0[i2cut] BLtrim = BL[i2cut] return BLtrim, bL0trim
[docs]def tensor_polar2cartesian2D(Mrr, Mrt, Mtr, Mtt, x, y): """converts a Polar tensor into a Cartesian one Parameters ---------- Mrr, Mtt, Mrt, Mtr : N x 1 arrays radial, azimuthal, and shear components of the tensor M x : N x 1 array the x positions of the points on which M is defined y : N x 1 array the y positions of the points on which M is defined Returns ---------- Mxx,Mxy,Myx,Myy : N x 1 arrays the cartesian components """ A = Mrr; B = Mrt; C = Mtr; D = Mtt; theta = np.arctan2(y, x); ct = np.cos(theta); st = np.sin(theta); Mxx = ct * (A * ct - B * st) - st * (C * ct - D * st); Mxy = ct * (B * ct + A * st) - st * (D * ct + C * st); Myx = st * (A * ct - B * st) + ct * (C * ct - D * st); Myy = st * (B * ct + A * st) + ct * (D * ct + C * st); return Mxx, Mxy, Myx, Myy
[docs]def tensor_cartesian2polar2D(Mxx, Myx, Mxy, Myy, x, y): """converts a Cartesian tensor into a Polar one Parameters ---------- Mxx,Mxy,Myx,Myy : N x 1 arrays cartesian components of the tensor M x : N x 1 array the x positions of the points on which M is defined y : N x 1 array the y positions of the points on which M is defined Returns ---------- Mrr, Mrt, Mtr, Mtt : N x 1 arrays radial, shear, and azimuthal components of the tensor M """ A = Mxx; B = Mxy; C = Myx; D = Myy; theta = np.arctan2(y, x); ct = np.cos(theta); st = np.sin(theta); Mrr = A * ct ^ 2 + (B + C) * ct * st + D * st ^ 2; Mrt = B * ct ^ 2 + (-A + D) * ct * st - C * st ^ 2; Mtr = C * ct ^ 2 + (-A + D) * ct * st - B * st ^ 2; Mtt = D * ct ^ 2 - (B + C) * ct * st + A * st ^ 2; return Mrr, Mrt, Mtr, Mtt
[docs]def tensor_polar2cartesian_tractions(Mrr, Mtt, beta): """ Given a stress tensor with locally diagonal values Mrr, Mtt, pick out tractions along x and y directions, where x axis is oriented an angle beta from the radial. Parameters ---------- Mrr,Mtt : N x 1 arrays polar components of the tensor M beta : float angle of x axis wrt radial axis (phi =0) Returns ---------- px : N x 1 float array the traction in the x direction py : N x 1 float array the traction in the y direction """ py = Mrr * np.sin(beta) ** 2 + Mtt * np.cos(beta) ** 2 px = (Mtt - Mrr) * np.sin(beta) * np.cos(beta) return px, py
[docs]def rotate_tensor_cartesian(Mxx, Mxy, Myy, beta): """Rotate a symmetric tensor by an angle beta in the xy plane. see http://www.creatis.insa-lyon.fr/~dsarrut/bib/Archive/others/phys/www.jwave.vt.edu/crcd/batra/lectures/esmmse5984/node38.html or elasticity.tex for more notes Parameters ---------- Mxx,Mxy,Myy : N x 1 arrays cartesian components of the tensor M in xy coord sys beta : float angle of x' wrt x (counterclockwise rotation) Returns ---------- Mxxprime, Mxyprime, Mxyprime : N x 1 float arrays The stress in the rotated xy' coordinates """ Mxxprime = Mxx * np.cos(beta) ** 2 + Myy * np.sin(beta) ** 2 + 2. * Mxy * np.sin(beta) * np.cos(beta) Mxyprime = Mxy * (np.cos(beta) ** 2 - np.sin(beta) ** 2) + (Myy - Mxx) * np.sin(theta) * np.cos(theta) Myyprime = Mxx * np.sin(beta) ** 2 + Myy * np.cos(theta) ** 2 - 2. * Mxy * np.sin(theta) * np.cos(theta) return Mxxprime, Mxyprime, Myyprime
[docs]def rotate_vectors_2D(XY, theta): """Given a list of vectors, rotate them actively counterclockwise in the xy plane by theta. Parameters ---------- XY : NP x 2 array Each row is a 2D x,y vector to rotate counterclockwise theta : float Rotation angle Returns ---------- XYrot : NP x 2 array Each row is the rotated row vector of XY """ R = np.array([[np.cos(theta), -np.sin(theta)], [np.sin(theta), np.cos(theta)]]) XYrot = np.dot(R, XY.transpose()).transpose() return XYrot
[docs]def rotation_matrix_3D(axis, theta): """ Return the rotation matrix associated with counterclockwise rotation about the given axis by theta radians. """ import math axis = np.asarray(axis) theta = np.asarray(theta) axis = axis / math.sqrt(np.dot(axis, axis)) a = math.cos(theta / 2.0) b, c, d = -axis * math.sin(theta / 2.0) aa, bb, cc, dd = a * a, b * b, c * c, d * d bc, ad, ac, ab, bd, cd = b * c, a * d, a * c, a * b, b * d, c * d return np.array([[aa + bb - cc - dd, 2 * (bc + ad), 2 * (bd - ac)], [2 * (bc - ad), aa + cc - bb - dd, 2 * (cd + ab)], [2 * (bd + ac), 2 * (cd - ab), aa + dd - bb - cc]])
[docs]def bonds_are_in_region(NL, KL, reg1, reg2): """Discern if a bond is connecting particles in the same region (reg1), or else: connecting reg1 to reg2 or reg2 to reg2. Returns KL-like and kL-like objects with 1 for reg1-reg1 connections and 0 for else. Parameters ---------- NL : NP x NN array Each row corresponds to a lattice site. The entries give indices of the neighboring sites KL : NP x NN array Connectivity array. 1 for true connection, 0 for no connection, -1 for periodic connection reg1 : (fraction)*NP x 1 array Indices of the portion of the lattice in question reg2 : (1-fraction)*NP x 1 array Indices of the rest of the lattice Returns -------- KL_reg1 : NP x NN array Region 1 connectivity array. Same as input KL, but with reg1-reg2 and reg2-reg2 connections zeroed out. """ # Kill all KL rows of reg2 particles KL[reg2, :] = 0 # Find where reg1 row of NL contains element from reg2 # The side searched for elems of row must not contain index 0. # print 'NL = ', NL for ii in reg1: row = NL[ii] # print 'row = ', row if np.in1d(row, reg2).any(): tuned = np.in1d(row, reg2) # print 'tuned = ', tuned # There is at least one element in row that is on the right side of the sample # Change spring const of that bond. # Current particle is indexed as reg1[ii] KL[ii, tuned] = 0 # print 'OmK[reg1[ii],tuned] = ', OmK[ii,tuned] KL_reg1 = KL return KL_reg1
[docs]def tune_springs_connecting_regions(NL, OmK, reg1, reg2, factor): """ Adjust (by a factor) the values of a connection matrix (like OmK or KL) that link sites on opposing regions of a lattice. This only works for two regions, but >2 regions can be built through iteration of this function. Note: reg1 MUST contain the zeroth particle. Parameters ---------- NL : NP x NN array Each row corresponds to a lattice site. The entries give indices of the neighboring sites reg1 : (fraction)*NP x 1 array Indices of the portion of the lattice containing the zeroth site reg2 : (1-fraction)*NP x 1 array Indices of the other portion of the lattice factor : float Factor by which to adjust/tune the values of the connection matrix Returns -------- """ # Find where reg1 row of NL contains element from reg2 # The side searched for elems of row must not contain index 0. # print 'NL = ', NL for ii in reg1: row = NL[ii] # print 'row = ', row if np.in1d(row, reg2).any(): tuned = np.in1d(row, reg2) # print 'tuned = ', tuned # There is at least one element in row that is on the right side of the sample # Change spring const of that bond. # Current particle is indexed as reg1[ii] OmK[ii, tuned] *= factor # print 'OmK[reg1[ii],tuned] = ', OmK[ii,tuned] # Change spring const of the same bond in the neighbor's row for other in row[tuned]: # Find ii in other's row # print 'looking for ', other, ' in ', row[tuned] # print 'np.where(NL[other]==ii) = ', np.where(NL[other]==ii) col2tune = np.where(NL[other] == ii)[0][0] # print 'col2tune = ', col2tune OmK[other, col2tune] *= factor return OmK
[docs]def row_is_in_array(row, array): """Check if row ([x,y]) is an element of an array ([[x1,y1],[x2,y2],...]) """ return any((array[:] == row).all(1))
[docs]def perp(a): """Get vector perpendicular to input vector a""" b = np.empty_like(a) b[0] = -a[1] b[1] = a[0] return b
[docs]def intersection_lines(a1, a2, b1, b2): """Find line intersection using two points on each line see Computer Graphics by F.S. Hill Parameters ---------- a1 : 1 x 2 float array endpoint 1 for the first lineseg a2 : 1 x 2 float array endpoint 2 for the first lineseg b1 : 1 x 2 float array endpoint 1 for the second lineseg b2 : 1 x 2 float array endpoint 2 for the second lineseg Returns ---------- intersect : 1 x 2 float array The intersection of the two lines defined by pts (a1,a2) and (b1,b2) """ da = a2 - a1 db = b2 - b1 dp = a1 - b1 dap = perp(da) denom = np.dot(dap, db) num = np.dot(dap, dp) intersect = (num / denom.astype(float)) * db + b1 return intersect
[docs]def intersection_linesegs(a1, a2, b1, b2, thres=1e-6): """Find line segment intersection using endpts of each lineseg. see Computer Graphics by F.S. Hill Parameters ---------- a1 : 1 x 2 float array endpoint 1 for the first lineseg a2 : 1 x 2 float array endpoint 2 for the first lineseg b1 : 1 x 2 float array endpoint 1 for the second lineseg b2 : 1 x 2 float array endpoint 2 for the second lineseg thres : float minimum distance for admitting intersection Returns ---------- intersect : 1 x 2 float array The intersection of the two linesegments (a1,a2) and (b1,b2) """ da = a2 - a1 db = b2 - b1 dp = a1 - b1 dap = perp(da) denom = np.dot(dap, db) num = np.dot(dap, dp) intersect = (num / denom.astype(float)) * db + b1 ptok = point_is_on_linesegment_2D_singlept(intersect, a1, a2, thres=thres) if not ptok: ptok = point_is_on_linesegment_2D_singlept(intersect, b1, b2, thres=thres) if ptok: return intersect else: return None
[docs]def find_intersections(A, B): """ Get the intersections between line segments A and line segments B, without a nested for loop over linesegs. http://stackoverflow.com/questions/3252194/numpy-and-line-intersections Parameters ---------- Returns ---------- x, y : coords of intersections """ # min, max and all for arrays amin = lambda x1, x2: np.where(x1 < x2, x1, x2) amax = lambda x1, x2: np.where(x1 > x2, x1, x2) aall = lambda abools: np.dstack(abools).all(axis=2) slope = lambda line: (lambda d: d[:, 1] / d[:, 0])(np.diff(line, axis=0)) x11, x21 = np.meshgrid(A[:-1, 0], B[:-1, 0]) x12, x22 = np.meshgrid(A[1:, 0], B[1:, 0]) y11, y21 = np.meshgrid(A[:-1, 1], B[:-1, 1]) y12, y22 = np.meshgrid(A[1:, 1], B[1:, 1]) m1, m2 = np.meshgrid(slope(A), slope(B)) m1inv, m2inv = 1 / m1, 1 / m2 yi = (m1 * (x21 - x11 - m2inv * y21) + y11) / (1 - m1 * m2inv) xi = (yi - y21) * m2inv + x21 xconds = (amin(x11, x12) < xi, xi <= amax(x11, x12), amin(x21, x22) < xi, xi <= amax(x21, x22)) yconds = (amin(y11, y12) < yi, yi <= amax(y11, y12), amin(y21, y22) < yi, yi <= amax(y21, y22)) return xi[aall(xconds)], yi[aall(yconds)]
[docs]def point_is_on_linesegment_2D_singlept(p, a, b, thres=1e-5): """Check if point is on line segment (or vertical line is on plane in 3D, since 3rd dim ignored). Parameters ---------- p : array or list of length >=2 The point in 2D a : array or list of length >=2 One end of the line segment b : array or list of length >=2 The other end of the line segment thres : float How close must the point be to the line segment to be considered to be on it Returns ---------- Boolean : whether the pt is on the line segment """ crossproduct = (p[1] - a[1]) * (b[0] - a[0]) - (p[0] - a[0]) * (b[1] - a[1]) if abs(crossproduct) > thres: return False # (or != 0 if using integers) dotproduct = (p[0] - a[0]) * (b[0] - a[0]) + (p[1] - a[1]) * (b[1] - a[1]) if dotproduct < 0: return False squaredlengthba = (b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1]) if dotproduct > squaredlengthba: return False return True
[docs]def point_is_on_linesegment_2D(p, a, b, thres=1e-5): """Check if point is on line segment (or vertical line is on plane in 3D, since 3rd dim ignored). Parameters ---------- p : array of dimension #points x >=2 The points in 2D (or 3D with 3rd dim ignored) a : array or list of dimension 1 x >=2 One end of the line segment b : array or list of dimension 1 x >=2 The other end of the line segment thres : float How close must the point be to the line segment to be considered to be on it Returns ---------- Boolean array : whether the pts are on the line segment """ crossproduct = (p[:, 1] - a[1]) * (b[0] - a[0]) - (p[:, 0] - a[0]) * (b[1] - a[1]) dotproduct = (p[:, 0] - a[0]) * (b[0] - a[0]) + (p[:, 1] - a[1]) * (b[1] - a[1]) squaredlengthba = (b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1]) on_seg = np.logical_and(np.logical_and(abs(crossproduct) < thres, dotproduct > 0), dotproduct < squaredlengthba) return on_seg
[docs]def closest_pt_along_line(pt, endpt1, endpt2): """Get point along a line defined by two points (endpts), closest to a point not on the line Parameters ---------- pt : array of length 2 point near which to find nearest point endpt1, endpt2 : arrays of length 2 x,y positions of points on line as array([[x0,y0],[x1,y1]]) Returns ---------- proj : array of length 2 the point nearest to pt along line """ # .pt /endpt2 # / # 7/proj # // # //endpt1 # # v is vec along line seg a = endpt2[0] - endpt1[0] b = endpt2[1] - endpt1[1] x = pt[0] - endpt1[0] y = pt[1] - endpt1[1] # the projection of the vector to pt along v (no numpy) p = np.array( [a * (a * x + b * y) / (a ** 2 + b ** 2) + endpt1[0], b * (a * x + b * y) / (a ** 2 + b ** 2) + endpt1[1]]) # print 'p (in closest_pt_along...) =', p return p
[docs]def closest_pts_along_line(pts, endpt1, endpt2): """For each coordinate in pts, get point along a line defined by two points (endpts) which is closest to that coordinate. Returns p as numpy array of points along the line. Right now just works in 2D. Parameters ---------- pts : NP x 2 float array point near which to find nearest point endpt1, endpt2 : 2 x 1 float arrays x,y positions of points on line as array([[x0,y0],[x1,y1]]) Returns ---------- proj : array of length 2 the points along line that are nearest to each coordinate in pts """ # if 2D # v is vec along line seg aa = endpt2[0] - endpt1[0] bb = endpt2[1] - endpt1[1] xx = pts[:, 0] - endpt1[0] yy = pts[:, 1] - endpt1[1] # the projection of the vector to pt along v projv = np.dstack((aa * (aa * xx + bb * yy) / (aa ** 2 + bb ** 2), bb * (aa * xx + bb * yy) / (aa ** 2 + bb ** 2)))[ 0] # add the endpt whose position was subtracted pp = projv + endpt1 * np.ones(projv.shape) # else if 3D: # todo : generalize to 3d return pp
[docs]def closest_pt_on_lineseg(pt, endpts): """Get point on line segment closest to a point not on the line; could be an endpt if lineseg is distant from pt. Parameters ---------- pt : array of length 2 point near which to find near point endpts : array of dimension 2x2 x,y positions of endpts of line segment as array([[x0,y0],[x1,y1]]) Returns ---------- proj : array of length 2 the point nearest to pt on lineseg """ p = closest_pt_along_line(pt, endpts) d0 = np.sqrt((proj[1] - pt[1]) ** 2 + (proj[0] - pt[0]) ** 2) d1 = np.sqrt((endpts[0, 1] - pt[1]) ** 2 + (endpts[0, 0] - pt[0]) ** 2) d2 = np.sqrt((endpts[1, 1] - pt[1]) ** 2 + (endpts[1, 0] - pt[0]) ** 2) if d0 <= d1: if d0 <= d2: return p else: return endpts[1, :] else: if d1 < d2: return endpts[0, :] else: return endpts[1, :]
[docs]def closest_pts_on_lineseg(pts, endpt1, endpt2): """Get points on line segment closest to an array of points not on the line; the closest point can be an endpt, for example if the lineseg is distant from pt. Works in any dimension now, if closest_pts_along_line works in any dimension. Parameters ---------- pts : N x dim float array points near which to find near point endpt1 : dim x 1 float array position of first endpt of line segment as array([x0, x1, ... xdim]) endpt2 : dim x 1 float array position of second endpt of line segment as array([x0, x1, ... xdim]) Returns ---------- p : N x dim float array the point nearest to pt on lineseg d : float distance from pt to p """ # create output vectors pout = np.zeros_like(pts) dout = np.zeros_like(pts[:, 0]) # Find nearest p along line formed by endpts p = closest_pts_along_line(pts, endpt1, endpt2) d0 = np.linalg.norm(p - pts, axis=1) # is p ok?-- are they the line segment? or is out of bounds? pok = line_pts_are_on_lineseg(p, endpt1, endpt2) # Assign those pts and distances for pok indices pout[pok, :] = p[pok, :] dout[pok] = d0[pok] # For p not on the segment, pick closer endpt d1 = np.linalg.norm(endpt1 - pts, axis=1) d2 = np.linalg.norm(endpt2 - pts, axis=1) nd1 = d1 < d2 # nearer to d1 ntd1 = np.logical_and(~pok, nd1) # nearest to d1 ntd2 = np.logical_and(~pok, ~nd1) # nearest to d2 pout[ntd1, :] = endpt1 dout[ntd1] = d1[ntd1] pout[ntd1, :] = endpt2 dout[ntd2] = d2[ntd2] return pout, dout
[docs]def closest_pts_on_lineseg_2D(pts, endpt1, endpt2): """Get points on line segment closest to an array of points not on the line; the closest point can be an endpt, for example if the lineseg is distant from pt. Parameters ---------- pts : array N x 2 points near which to find near point endpt1 : 2x2 float array x,y positions of endpts of line segment as array([[x0,y0],[x1,y1]]) endpt2 : 2x2 float array x,y positions of endpts of line segment as array([[x0,y0],[x1,y1]]) Returns ---------- p : array of length 2 the point nearest to pt on lineseg d : float distance from pt to p """ # create output vectors pout = np.zeros_like(pts) dout = np.zeros_like(pts[:, 0]) # Find nearest p along line formed by endpts p = closest_pts_along_line(pts, endpt1, endpt2) d0 = np.sqrt((p[:, 1] - pts[:, 1]) ** 2 + (p[:, 0] - pts[:, 0]) ** 2) # is p ok?-- are they the line segment? or is out of bounds? pok = line_pts_are_on_lineseg(p, endpt1, endpt2) # Assign those pts and distances for pok indices pout[pok, :] = p[pok, :] dout[pok] = d0[pok] # For p not on the segment, pick closer endpt d1 = (endpt1[1] - pts[:, 1]) ** 2 + (endpt1[0] - pts[:, 0]) ** 2 d2 = (endpt2[1] - pts[:, 1]) ** 2 + (endpt2[0] - pts[:, 0]) ** 2 nd1 = d1 < d2 # nearer to d1 ntd1 = np.logical_and(~pok, nd1) # nearest to d1 ntd2 = np.logical_and(~pok, ~nd1) # nearest to d2 pout[ntd1, :] = endpt1 dout[ntd1] = np.sqrt(d1[ntd1]) pout[ntd1, :] = endpt2 dout[ntd2] = np.sqrt(d2[ntd2]) return pout, dout
[docs]def line_pts_are_on_lineseg(p, a, b): """Check if an array of n-dimensional points (p) which lie along a line is between two other points (a,b) on that line (ie, is on a line segment) Parameters ---------- p : array of dim N x 2 points for which to evaluate if they are on segment a,b : dim x 1 float arrays or lists positions of line segment endpts Returns ---------- True or False: whether pt is between endpts """ # dot product must be positive and less than |b-a|^2 if np.shape(p)[1] == 2: dotproduct = (p[:, 0] - a[0]) * (b[0] - a[0]) + (p[:, 1] - a[1]) * (b[1] - a[1]) squaredlengthba = (b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1]) else: # Do n-dimensional dot product: dotproduct = np.zeros_like(p[:, 0]) for dim in range(np.shape(p)[1]): dotproduct += (p[:, dim] - a[dim]) * (b[dim] - a[dim]) squaredlengthba = np.linalg.norm(b - a, axis=1) ** 2 return np.logical_and(dotproduct > 0, dotproduct < squaredlengthba)
[docs]def line_pts_are_on_lineseg_2D(p, a, b): """Check if an array of points (p) which lie along a line is between two other points (a,b) on that line (ie, is on a line segment) Parameters ---------- p : array of dim N x 2 points for which to evaluate if they are on segment a,b : arrays or lists of length 2 x,y positions of line segment endpts Returns ---------- True or False: whether pt is between endpts """ # dot product must be positive and less than |b-a|^2 dotproduct = (p[:, 0] - a[0]) * (b[0] - a[0]) + (p[:, 1] - a[1]) * (b[1] - a[1]) squaredlengthba = (b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1]) return np.logical_and(dotproduct > 0, dotproduct < squaredlengthba)
[docs]def pts_are_near_lineseg(x, endpt1, endpt2, W): """Determine if pts in array x are within W of line segment Parameters ---------- x : NP x 2 float array the points to consider proximity to a lineseg endpt1 : 2x2 float array the first endpoint of the linesegment endpt2 : 2x2 float array the second endpoint of the linesegment W : float threshold distance from lineseg to consider a point 'close' Returns ------- NP x 1 boolean array Whether the points are closer than W from the linesegment """ # check if point is anywhere near line before doing calcs p, dist = closest_pts_on_lineseg(x, endpt1, endpt2) return dist <= W
[docs]def is_number(s): """Check if a string can be represented as a number; works for floats""" try: float(s) return True except ValueError: return False
def round_thres(a, MinClip): return round(float(a) / MinClip) * MinClip def round_thres_numpy(a, MinClip): return np.round(np.array(a, dtype=float) / MinClip) * MinClip ########################################## # String Formatting ##########################################
[docs]def string_to_array(string, delimiter='/', dtype='auto'): """Convert string to numpy array See also string_sequence_to_numpy_array() """ slist = string.split(delimiter) if dtype != 'auto': arr = np.array(slist, dtype=dtype) else: try: arr = np.array(slist, dtype=float) except: try: arr = np.array(slist, dtype=int) except: arr = np.array(slist, dtype=str) return arr
[docs]def string_sequence_to_numpy_array(vals, dtype=str): """Convert a string of numbers like (#/#/#) or (#:#:#) or (#:#) or (#) to a numpy array. Parameters ---------- vals : string List of values, can take formats: 'check_string_for_empty', #/#/#, #:#:#, #:#, or # dtype: Returns ---------- out : numpy array Values input as string, returned as array """ # ensure input is string if not isinstance(vals, str): vals = str(vals) vals = vals.replace('n', '-') # Convert to an array of strings, or to an array of dtype if vals == 'check_string_for_empty': out = np.array([]) elif '/' not in vals and ':' not in vals: out = np.array([vals], dtype=dtype) elif ':' in vals: values = vals.split(':') if '.' in values[0] or '.' in values[1]: start = float(values[0]) if len(values) == 3: step = float(values[1]) end = float(values[2]) elif len(values) == 2: step = float(1) end = float(values[1]) else: raise RuntimeError('If : is used, vals must be ##:## or ##:##:##') else: start = int(values[0]) if len(values) == 3: step = int(values[1]) end = int(values[2]) elif len(values) == 2: step = 1 end = int(values[1]) else: raise RuntimeError('If : is used, vals must be ##:## or ##:##:##') vals_nums = np.arange(start, end, step) out = np.array([vali for vali in vals_nums], dtype=dtype) else: out = np.array([vals.split('/')[i] for i in range(len(vals.split('/')))], dtype=dtype) return out
[docs]def float2pstr(floatv, ndigits=2): """Format a float as a string, replacing decimal points with p""" return ('{0:0.' + str(int(ndigits)) + 'f}').format(floatv).replace('.', 'p').replace('-', 'n')
[docs]def prepstr(string): """Format a string for using as part of a directory string""" return string.replace('.', 'p').replace('-', 'n')
################################################### # Below is the entire Lattice Creation section ###################################################
[docs]def delaunay_lattice_from_pts(xy, trimbound=True, target_z=-1, max_bond_length=-1, thres=4.0, zmethod='random', minimum_bonds=-1, check=False): """ Convert 2D pt set to lattice (xy, NL, KL, BL, BM) via traingulation. The order of operations is very important: A) Trim boundary triangles (needs to be a triangulation to work). B) Cut long bonds. C) Tune average coordination. The nontrivial part of this program kills edges which are unnatural according to the following definition: 1. The outside edge is the hypotenuse of the triangle 2. Call the hypotenuse the "base" and determine the "height" of the triangle. Then if base/height > x (where x is some criteria value, say 4), delete that edge/triangle (depending on data structure/object definitions). Parameters ---------- xy : #pts to be triangulated x 2 array xy points from which to construct lattice trimbound : bool (optional) Whether to trim the boundary triangles based on aspect ratio target_z: float Average coordinate to which the function will tune the network, if specified to be > 0. max_bond_length : float cut bonds longer than this value, if specified to be > 0 thres : float cut boundary triangles with height/base longer than this zmethod : string ('random' 'highest') Whether to cut randomly or cut bonds from nodes with highest z, if target_z > 0 minimum_bonds: int or None (default=-1) If minimum_bonds>0, removes all points with fewer than minimum_bonds bonds check: bool View intermediate results Returns ---------- xy : NP x 2 float array triangulated points NL : NP x NN int array Neighbor list KL : NP x NN int array Connectivity list BL : Nbonds x 2 int array Bond list BM : NP x NN float array unstretched (reference) bond length matrix """ NP = len(xy) tri = Delaunay(xy) TRI = tri.vertices # check # plt.triplot(xy[:,0], xy[:,1], TRI, 'go-') # plt.show() BL = TRI2BL(TRI) NL, KL = BL2NLandKL(BL, NP=NP, NN='min') if trimbound: # Cut unnatural edge bonds (ones that are long and skinny) NL, KL, BL, TRI = delaunay_cut_unnatural_boundary(xy, NL, KL, BL, TRI, thres) # check if check: plt.clf() plt.triplot(xy[:, 0], xy[:, 1], TRI, 'go-') plt.show() # Cut bonds longer than max allowed length if max_bond_length > 0: print 'Cutting bonds longer than max_bond_length...' BL = cut_bonds(BL, xy, max_bond_length) if check: display_lattice_2D(xy, BL, title='In delaunay_lattice_from_pts(), removed long bonds.') NL, KL = BL2NLandKL(BL, NN='min') if minimum_bonds > 0: # Remove any points with no bonds print 'Removing points without any bonds...' if minimum_bonds == 1: keep = KL.any(axis=1) else: keep = np.sum(KL, axis=1) > minimum_bonds # keep = np.array([np.count_nonzero(KL[i]) > minimum_bonds for i in range(len(KL))]) xy, NL, KL, BL = remove_pts(keep, xy, BL, NN='min') if check: display_lattice_2D(xy, BL, NL=NL, KL=KL, title='In delaunay_lattice_from_pts(), removed pts without bonds.') # Cut bonds to tune average coordination if target_z > 0: print 'Cutting bonds to tune average coordination...' if zmethod == 'random': NL, KL, BL = cut_bonds_z_random(xy, NL, KL, BL, target_z) elif zmethod == 'highest': NL, KL, BL = cut_bonds_z_highest(xy, NL, KL, BL, target_z) print 'Constructing BM...' BM = NL2BM(xy, NL, KL) if check: display_lattice_2D(xy, BL, NL=NL, KL=KL, title='Checking output lattice in delaunay_lattice_from_pts()') # vc = cc[:,tri.neighbors] # # kill edges at infinity, plotting those would need more work... # vc[:,tri.neighbors == -1] = np.nan # # lines = [] # lines.extend(zip(cc.T, vc[:,:,0].T)) # lines.extend(zip(cc.T, vc[:,:,1].T)) # lines.extend(zip(cc.T, vc[:,:,2].T)) return xy, NL, KL, BL, BM
[docs]def voronoi_lattice_from_pts(points, polygon=None, NN=3, kill_outliers=True, check=False): """Convert 2D pt set to dual lattice (xy, NL, KL, BL) via Voronoi construction Parameters ---------- points : #triangulated pts x 2 array xy points from a Delaunay triangulation polygon Returns ---------- xy : NP x 2 float array points living on vertices of dual to triangulation NL : NP x NN int array Neighbor list KL : NP x NN int array Connectivity list BL : Nbonds x 2 int array Bond list """ tri = Delaunay(points) p = tri.points[tri.vertices] # Triangle vertices A = p[:, 0, :].T B = p[:, 1, :].T C = p[:, 2, :].T # See http://en.wikipedia.org/wiki/Circumscribed_circle#Circumscribed_circles_of_triangles # The following is just a direct transcription of the formula there a = A - C b = B - C def dot2(u, v): return u[0] * v[0] + u[1] * v[1] def cross2(u, v, w): """u x (v x w)""" return dot2(u, w) * v - dot2(u, v) * w def ncross2(u, v): """|| u x v ||^2""" return sq2(u) * sq2(v) - dot2(u, v) ** 2 def sq2(u): return dot2(u, u) cc = cross2(sq2(a) * b - sq2(b) * a, a, b) / (2 * ncross2(a, b)) + C # Construct NL from the triangulation object print 'Constructing NL, KL...' if NN > 3: NP = len(c[0]) NL = np.zeros(NP, NN).astype(int) NL[:, 0:3] = tri.neighbors else: NL = tri.neighbors KL = (NL > -0.5).astype(int) NL[NL == -1] = 0 print 'Constructing xy, BL...' xy = np.array([[cc[0][ii], cc[1][ii]] for ii in range(len(cc[0]))]) BL = NL2BL(NL, KL) if kill_outliers: # Only keep xy inside the bounds of the supplied points # points -= np.mean(points, axis=0) keep = np.where(np.logical_and(abs(xy[:, 0]) < max(points[:, 0]), abs(xy[:, 1]) < max(points[:, 1])))[0] xy, NL, KL, BL = remove_pts(keep, xy, BL) if polygon == 'auto' or polygon is None: xycent = xy else: print 'cropping polygon = ', polygon pth = Path(polygon, closed=False) keep = pth.contains_points(xy) if check: print 'xy = ', xy print 'keep = ', keep plt.plot(xy[:, 0], xy[:, 1], 'b.') plt.plot(polygon[:, 0], polygon[:, 1], 'r-') plt.show() xy, NL, KL, BL = remove_pts(keep, xy, BL, NN=3) # vc = cc[:,tri.neighbors] # # kill edges at infinity, plotting those would need more work... # vc[:,tri.neighbors == -1] = np.nan # # lines = [] # lines.extend(zip(cc.T, vc[:,:,0].T)) # lines.extend(zip(cc.T, vc[:,:,1].T)) # lines.extend(zip(cc.T, vc[:,:,2].T)) return xy, NL, KL, BL
[docs]def voronoi_rect_periodic_from_pts(xy, LL, BBox='auto', dist=7., check=False): """Convert 2D pt set to dual lattice (xy, NL, KL, BL) via Voronoi construction Parameters ---------- points : #triangulated pts x 2 array xy points from a Delaunay triangulation polygon Returns ---------- xy : NP x 2 float array points living on vertices of dual to triangulation NL : NP x NN int array Neighbor list KL : NP x NN int array Connectivity list BL : Nbonds x 2 int array Bond list """ xytmp = buffer_points_for_rectangular_periodicBC(xy, LL, dist=dist) xy, NL, KL, BL = voronoi_lattice_from_pts(xytmp, polygon=None, kill_outliers=True) xytrim, NL, KL, BLtrim, PVxydict = buffered_pts_to_periodic_network(xy, BL, LL, BBox=BBox, check=check) return xytrim, NL, KL, BLtrim, PVxydict
[docs]def delaunay_centroid_lattice_from_pts(xy, polygon=None, trimbound=True, thres=2.0, shear=-1, check=False): """ Convert 2D pt set to lattice (xy, NL, KL, BL, BM) via traingulation. Performs: A) Triangulate the point set. B) If trimbound==True, trim boundary triangles (needs to be a triangulation to work) C) Find centroids D) Connect centroids in z=3 lattice Part of this program kills edges which are unnatural according to the following definition: 1. The outside edge is the hypotenuse of the triangle 2. Call the hypotenuse the "base" and determine the "height" of the triangle. Then if base/height > x (where x is some criteria value, say 4), delete that edge/triangle (depending on data structure/object definitions). Parameters ---------- xy : #pts to be triangulated x 2 array xy points from which to construct lattice polygon : numpy float array [[x0,y0],...,[xN,yN]], or string 'auto' polygon to cut out from centroid lattice. If 'auto', cuts out a box with a buffer distance of 5% trimbound : bool Whether to trim off high-aspect-ratio triangles from edges of the triangulation before performing centroid operation thres : float (default = 2.0) cut boundary triangles with height/base longer than this value shear : float (ignored if negative) shear inverse slope (run/rise) to apply to xy in order to prevent degeneracy in triangulation (breaks symmetry) check : bool Whether to plot results as they are computed Returns ---------- xycent : NP x 2 float array Centroids of triangulation of xy NL : NP x NN int array Neighbor list (of centroids of triangulation of xy) KL : NP x NN int array Connectivity list (of centroids of triangulation of xy) BL : Nbonds x 2 int array Bond list (of centroids of triangulation of xy) BM : NP x NN float array unstretched (reference) bond length matrix (of centroids of triangulation of xy) """ ############################### # A) Triangulate the point set. ############################### NP = len(xy) if shear > 0: xytmp = np.dstack((xy[:, 0] + shear * xy[:, 1], xy[:, 1]))[0] tri = Delaunay(xytmp) else: tri = Delaunay(xy) TRI = tri.vertices print 'max(TRI) = ', max(TRI.ravel()) # check if check: plt.triplot(xy[:, 0], xy[:, 1], TRI, 'go-') plt.show() # centxy = xyandTRI2centroid(xy,TRI) # BL = TRI2BL(TRI) if trimbound: ############################### # B) Trim boundary triangles ############################### print 'converting TRI to NL, KL, BL...' BL = TRI2BL(TRI) NL, KL = BL2NLandKL(BL, NP='auto', NN='min') if check: display_lattice_2D(xy, BL, title='TRI lattice before cutting edge TRIs, before centroid') # Cut unnatural edge bonds (ones that are long and skinny) NL, KL, BL, TRI = delaunay_cut_unnatural_boundary(xy, NL, KL, BL, TRI, thres) if check: display_lattice_2D(xy, BL, title='TRI lattice after cutting edge TRIs, before centroid') else: BL = TRI2BL(TRI) # check # plt.clf() # plt.triplot(xy[:,0], xy[:,1], TRI, 'go-') # plt.show() ###################### # C) Find centroids ###################### centxy = xyandTRI2centroid(xy, TRI) if check: display_lattice_2D(xy, BL, title='TRI lattice after cutting edge TRIs, before centroid', colorz=False, close=False) plt.plot(centxy[:, 0], centxy[:, 1], 'b.') plt.show() # Now make TRIout # NL = tri.neighbors # KL = np.zeros_like(NL, dtype=int) # KL[NL >0 ] = 1 # BL = NL2BL(NL,KL) # Now make TRIout NL, KL, BL = TRI2centroidNLandKLandBL(TRI) if check: display_lattice_2D(centxy, BL, title='centroid lattice before cropping', close=False) plt.triplot(xy[:, 0], xy[:, 1], TRI, 'go-') plt.show() print 'centroid BL = ', BL print 'centroid max(BL) =', np.max(BL.ravel()) print 'centroid len(centxy) = ', len(centxy) # The chopping below really isn't necessary for the centroid method if polygon == 'auto' or polygon is None: xycent = centxy else: print 'cropping polygon = ', polygon pth = Path(polygon, closed=False) keep = pth.contains_points(centxy) if check: print 'centxy = ', centxy print 'keep = ', keep plt.plot(centxy[:, 0], centxy[:, 1], 'b.') plt.plot(polygon[:, 0], polygon[:, 1], 'r-') plt.show() xycent, NL, KL, BL = remove_pts(keep, centxy, BL, NN=3) if check: display_lattice_2D(xycent, BL, title='centroid lattice after cropping', close=False) plt.triplot(xy[:, 0], xy[:, 1], TRI, 'go-') plt.show() # print 'Constructing BM...' # BM = NL2BM(xycent, NL,KL) # vc = cc[:,tri.neighbors] # # kill edges at infinity, plotting those would need more work... # vc[:,tri.neighbors == -1] = np.nan # # lines = [] # lines.extend(zip(cc.T, vc[:,:,0].T)) # lines.extend(zip(cc.T, vc[:,:,1].T)) # lines.extend(zip(cc.T, vc[:,:,2].T)) return xycent, NL, KL, BL
[docs]def delaunay_centroid_rect_periodic_network_from_pts(xy, LL, BBox='auto', check=False): """Convert 2D pt set to lattice (xy, NL, KL, BL, BM, PVxydict) via traingulation, handling periodic BCs. Performs: A) Triangulate an enlarged version of the point set. B) Find centroids C) Connect centroids in z=3 lattice D) Crops to original bounding box and connects periodic BCs Note: Normally BBox is centered such that original BBox is [-LL[0]*0.5, -LL[1]*0.5], [LL[0]*0.5, -LL[1]*0.5], etc. Parameters ---------- xy : NP x 2 float array xy points from which to find centroids, so xy are in the triangular representation LL : tuple of 2 floats Horizontal and vertical extent of the bounding box (a rectangle) through which there are periodic boundaries. BBox : #vertices x 2 numpy float array bounding box for the network. Here, this MUST be rectangular, and the side lengths should be taken to be LL[0], LL[1] for it to be sensible. check : bool Whether to view intermediate results Returns ------- """ # Algorithm for handling boundaries: # - Copy parts of lattice to buffer up the edges # - Cut the bonds with the bounding box of the loaded configuration # - For each cut bond, match the outside endpt with its corresponding mirror particle xytmp = buffer_points_for_rectangular_periodicBC(xy, LL) xy, NL, KL, BL = delaunay_centroid_lattice_from_pts(xytmp, polygon=None, trimbound=False, check=check) xytrim, NL, KL, BLtrim, PVxydict = buffered_pts_to_periodic_network(xy, BL, LL, BBox=BBox, check=check) return xytrim, NL, KL, BLtrim, PVxydict
[docs]def delaunay_rect_periodic_network_from_pts(xy, LL, BBox='auto', check=False, target_z=-1, max_bond_length=-1, zmethod='random', minimum_bonds=-1, dist=7.0): """Buffers the true point set with a mirrored point set across each boundary, for a rectangular boundary. Parameters ---------- xy : NP x 2 float array Particle positions LL : tuple of 2 floats Horizontal and vertical extent of the bounding box (a rectangle) through which there are periodic boundaries. BBox : 4 x 2 float array or 'auto' The bounding box to use as the periodic boundary box check : bool Whether to display intermediate results target_z: float Average coordinate to which the function will tune the network, if specified to be > 0. max_bond_length : float cut bonds longer than this value zmethod : string ('random' 'highest') Whether to cut randomly or cut bonds from nodes with highest z minimum_bonds: int If >0, remove pts with fewer bonds dist : float minimum depth of the buffer on each side Returns ------- xytrim : (~NP) x 2 float array triangulated point set with periodic BCs on right, left, above, and below """ # Algorithm for handling boundaries: # - Copy parts of lattice to buffer up the edges # - Cut the bonds with the bounding box of the loaded configuration # - For each cut bond, match the outside endpt with its corresponding mirror particle xytmp = buffer_points_for_rectangular_periodicBC(xy, LL, dist=dist) xy, NL, KL, BL, BM = delaunay_lattice_from_pts(xytmp, trimbound=False, target_z=target_z, max_bond_length=max_bond_length, zmethod=zmethod, minimum_bonds=minimum_bonds, check=check) xytrim, NL, KL, BLtrim, PVxydict = buffered_pts_to_periodic_network(xy, BL, LL, BBox=BBox, check=check) return xytrim, NL, KL, BLtrim, PVxydict
[docs]def buffer_points_for_rectangular_periodicBC(xy, LL, dist=7.0): """Buffers the true point set with a mirrored point set across each boundary, for a rectangular boundary. Parameters ---------- xy : NP x 2 float array Particle positions LL : tuple of 2 floats Horizontal and vertical extent of the bounding box (a rectangle) through which there are periodic boundaries. dist : float minimum depth of the buffer on each side Returns ------- xyout : (>= NP) x 2 float array Buffered point set with edges of sample tiled on the right, left, above and below """ # Copy some of lattice to north, south, east, west and corners west = np.where(xy[:, 0] < (np.min(xy[:, 0]) + dist))[0] sout = np.where(xy[:, 1] < (np.min(xy[:, 1]) + dist))[0] east = np.where(xy[:, 0] > (np.max(xy[:, 0]) - dist))[0] nort = np.where(xy[:, 1] > (np.max(xy[:, 1]) - dist))[0] swest = np.intersect1d(sout, west) seast = np.intersect1d(sout, east) neast = np.intersect1d(nort, east) nwest = np.intersect1d(nort, west) Epts = xy[west] + np.array([LL[0], 0.0]) Npts = xy[sout] + np.array([0.0, LL[1]]) Wpts = xy[east] + np.array([-LL[0], 0.0]) Spts = xy[nort] + np.array([0.0, -LL[1]]) NEpts = xy[swest] + np.array([LL[0], LL[1]]) NWpts = xy[seast] + np.array([-LL[0], LL[1]]) SWpts = xy[neast] + np.array([-LL[0], -LL[1]]) SEpts = xy[nwest] + np.array([LL[0], -LL[1]]) xyout = np.vstack((xy, Epts, NEpts, Npts, NWpts, Wpts, SWpts, Spts, SEpts)) return xyout
[docs]def buffered_pts_to_periodic_network(xy, BL, LL, BBox='auto', check=False): """Crops to original bounding box and connects periodic BCs of rectangular sample Note: Default BBox is centered such that original BBox is [-LL[0]*0.5, -LL[1]*0.5], [LL[0]*0.5, -LL[1]*0.5], etc. Parameters ---------- xy : NP x 2 float array xy points with buffer points, so xy are in the triangular representation LL : tuple of 2 floatsf Horizontal and vertical extent of the bounding box (a rectangle) through which there are periodic boundaries. BBox : #vertices x 2 numpy float array bounding box for the network. Here, this MUST be rectangular, and the side lengths should be taken to be LL[0], LL[1] for it to be sensible. check : bool Whether to view intermediate results Returns ------- """ if BBox == 'auto': # Assuming that BBox is centered and has width, height of LL[0], LL[1] BBox = 0.5 * np.array([[-LL[0], -LL[1]], [LL[0], -LL[1]], [LL[0], LL[1]], [-LL[0], LL[1]]]) keep = np.where(np.logical_and(abs(xy[:, 0]) < LL[0] * 0.5, abs(xy[:, 1]) < LL[1] * 0.5))[0] else: bpath = mplpath.Path(BBox) keep = np.where(bpath.contains_points(xy))[0] if check: print 'checking that keep is not a logical ==> ' print ' this would be bool keep = ', bpath.contains_points(xy) print ' and this is keep = ', keep minX = np.min(BBox[:, 0]) maxX = np.max(BBox[:, 0]) minY = np.min(BBox[:, 1]) maxY = np.max(BBox[:, 1]) PVdict = {'e': np.array([LL[0], 0.0]), 'n': np.array([0.0, LL[1]]), 'w': np.array([-LL[0], 0.0]), 's': np.array([0.0, -LL[1]]), 'ne': np.array([LL[0], LL[1]]), 'nw': np.array([-LL[0], LL[1]]), 'sw': np.array([-LL[0], -LL[1]]), 'se': np.array([LL[0], -LL[1]])} # Create a kd tree of the points tree = scipy.spatial.KDTree(xy) # Find bonds that will be cut. For each bond, match to other particle and add pair to BL and PVxydict BLcut, cutIND = find_cut_bonds(BL, keep) if check: plt.scatter(xy[:, 0], xy[:, 1], c='g', marker='x') plt.scatter(xy[keep, 0], xy[keep, 1], c='b', marker='o') highlight_bonds(xy, BL, ax=plt.gca(), color='b', show=False) highlight_bonds(xy, BLcut, ax=plt.gca(), color='r', lw=1, show=False) xxtmp = np.hstack((BBox[:, 0], np.array(BBox[:, 0]))) print 'xxtmp = ', xxtmp yytmp = np.hstack((BBox[:, 1], np.array(BBox[:, 1]))) print 'yytmp = ', yytmp plt.plot(xxtmp, yytmp, 'k-', lw=2) plt.title('Showing bonds that are cut, btwn original and mirrored network') plt.show() # preallocate BL2add and PVs BL2add = np.zeros((len(BLcut), 2), dtype=int) PVd = {} # = np.zeros((len(BLcut),2), dtype=float) kk = 0 for bond in BLcut: # which endpt is outside? ptA = bond[0] ptB = bond[1] # mpt is short for 'mirror point', the point outside the bounding box if ptA not in keep: mpt, kpt = ptA, ptB else: mpt, kpt = ptB, ptA if xy[mpt, 0] < minX: if xy[mpt, 1] < minY: # Mirror particle is SW PV = PVdict['sw'] elif xy[mpt, 1] > maxY: # Mirror particle is NW PV = PVdict['nw'] else: # Mirror particle is West PV = PVdict['w'] elif xy[mpt, 0] > maxX: if xy[mpt, 1] < minY: # Mirror particle is SE PV = PVdict['se'] elif xy[mpt, 1] > maxY: # Mirror particle is NE PV = PVdict['ne'] else: # Mirror particle is East PV = PVdict['e'] elif xy[mpt, 1] < minY: # Mirror particle is South PV = PVdict['s'] else: # Mirror particle is North PV = PVdict['n'] # Get index of the particle that resides a vector -PV away from mirror particle dist, ind = tree.query(xy[mpt] - PV) BL2add[kk] = np.array([-kpt, -ind]) PVd[(kpt, ind)] = PV kk += 1 if check: print 'PVd = ', PVd display_lattice_2D(xy, np.abs(BL), title="showing extended lattice (w/o PBCs)") # Crop network, and add back cut bonds as periodic ones BL = np.vstack((BL, BL2add)) xytrim, NL, KL, BLtrim = remove_pts(keep, xy, BL) # Adjusting BL2add to account for smaller #npts (post-cropping) is already done in remove_pts # Adjust PVs to account for smaller #npts (post-cropping) remove = np.setdiff1d(np.arange(len(xy)), keep) PVxydict = {} for key in PVd: # adjust key to lower indices # count how many pts in remove are lower than key[0] and key[1], respectively lower0 = np.sum(remove < key[0]) lower1 = np.sum(remove < key[1]) newkey = (key[0] - lower0, key[1] - lower1) PVxydict[newkey] = PVd[key] if check: # Plot lattice without PBCs display_lattice_2D(xytrim, np.abs(BLtrim), title="showing lattice connectivity w/o PBCs") display_lattice_2D(xytrim, BLtrim, PVxydict=PVxydict, title="showing lattice connectivity with PBCs") return xytrim, NL, KL, BLtrim, PVxydict
[docs]def delaunay_periodic_network_from_pts(xy, PV, BBox='auto', check=False, target_z=-1, max_bond_length=-1, zmethod='random', minimum_bonds=-1, ensure_periodic=False): """Buffers the true point set with a mirrored point set across each boundary, for a rectangular boundary. Parameters ---------- xy : NP x 2 float array Particle positions LL : tuple of 2 floats Horizontal and vertical extent of the bounding box (a rectangle) through which there are periodic boundaries. BBox : 4 x 2 float array or 'auto' The bounding box to use as the periodic boundary box check : bool Whether to display intermediate results target_z: float Average coordinate to which the function will tune the network, if specified to be > 0. max_bond_length : float cut bonds longer than this value zmethod : string ('random' 'highest') Whether to cut randomly or cut bonds from nodes with highest z minimum_bonds: int If >0, remove pts with fewer bonds dist : float minimum depth of the buffer on each side Returns ------- xytrim : (~NP) x 2 float array triangulated point set with periodic BCs on right, left, above, and below """ # Algorithm for handling boundaries: # - Copy parts of lattice to buffer up the edges # - Cut the bonds with the bounding box of the loaded configuration # - For each cut bond, match the outside endpt with its corresponding mirror particle xytmp = buffer_points_for_periodicBC(xy, PV) if check: plt.show() plt.plot(xytmp[:, 0], xytmp[:, 1], 'b.') plt.title('Buffered points') plt.show() xy, NL, KL, BL, BM = delaunay_lattice_from_pts(xytmp, trimbound=False, target_z=target_z, max_bond_length=max_bond_length, zmethod=zmethod, minimum_bonds=minimum_bonds, check=check) if ensure_periodic: BL = ensure_periodic_connectivity(xy, NL, KL, BL) NL, KL = BL2NLandKL(BL) # todo: allow for other shapes of periodic boundaries other than parallelogram xytrim, NL, KL, BLtrim, PVxydict = \ buffered_pts_to_periodic_network_parallelogram(xy, BL, PV, BBox=BBox, check=check) return xytrim, NL, KL, BLtrim, PVxydict
[docs]def ensure_periodic_connectivity(xy, NL, KL, BL): """For each paticle, look at bonds, and look at the bonds of the same particles elsewhere in a lattice according to the periodic vectors PV. If there are bonds that have been triangulated differently than they in different parts of a periodic network, which can happen due to roundoff error, fix these bonds""" return BL
[docs]def delaunay_centroid_periodic_network_from_pts(xy, PV, BBox='auto', flex_pvxy=False, shear=-1., check=False): """Convert 2D pt set to lattice (xy, NL, KL, BL, BM, PVxydict) via traingulation, handling periodic BCs. Performs: A) Triangulate an enlarged version of the point set. B) Find centroids C) Connect centroids in z=3 lattice D) Crops to original bounding box and connects periodic BCs Note: Normally BBox is centered such that original BBox is [-LL[0]*0.5, -LL[1]*0.5], [LL[0]*0.5, -LL[1]*0.5], etc. Parameters ---------- xy : NP x 2 float array xy points from which to find centroids, so xy are in the triangular representation LL : tuple of 2 floats Horizontal and vertical extent of the bounding box (a rectangle) through which there are periodic boundaries. BBox : #vertices x 2 numpy float array bounding box for the network. Here, this MUST be rectangular, and the side lengths should be taken to be LL[0], LL[1] for it to be sensible. check : bool Whether to view intermediate results Returns ------- """ # Algorithm for handling boundaries: # - Copy parts of lattice to buffer up the edges # - Cut the bonds with the bounding box of the loaded configuration # - For each cut bond, match the outside endpt with its corresponding mirror particle xytmp = buffer_points_for_periodicBC(xy, PV, check=check) xy, NL, KL, BL = delaunay_centroid_lattice_from_pts(xytmp, polygon=None, trimbound=False, shear=shear, check=check) xytrim, NL, KL, BLtrim, PVxydict = \ buffered_pts_to_periodic_network_parallelogram(xy, BL, PV, BBox=BBox, flex_pvxy=flex_pvxy, check=check) return xytrim, NL, KL, BLtrim, PVxydict
[docs]def buffer_points_for_periodicBC(xy, PV, check=False): """Buffers the true point set with a mirrored point set across each boundary, for a parallelogram (or evenutally a more argitrary boundary). Parameters ---------- xy : NP x 2 float array Particle positions PV : 2 x 2 float array Periodic vectors: the first has x and y components, the second has only positive y component. dist : float minimum depth of the buffer on each side Returns ------- xyout : (>= NP) x 2 float array Buffered point set with edges of sample tiled on the right, left, above and below """ Epts = xy + PV[0] Npts = xy + PV[1] Wpts = xy - PV[0] Spts = xy - PV[1] NEpts = xy + PV[0] + PV[1] NWpts = xy - PV[0] + PV[1] SWpts = xy - PV[0] - PV[1] SEpts = xy + PV[0] - PV[1] xyout = np.vstack((xy, Epts, NEpts, Npts, NWpts, Wpts, SWpts, Spts, SEpts)) if check: eps = 0.1 plt.scatter(xy[:, 0], xy[:, 1], c='r', edgecolor='none') plt.scatter(Epts[:, 0] + eps, Epts[:, 1], c='y', edgecolor='none') plt.scatter(NEpts[:, 0] + eps, NEpts[:, 1] + eps, c='g', edgecolor='none') plt.scatter(Npts[:, 0], Npts[:, 1] + eps, c='b', edgecolor='none') plt.scatter(NWpts[:, 0] - eps, NWpts[:, 1] + eps, c='w') plt.scatter(Wpts[:, 0] - eps, Wpts[:, 1], c='m', edgecolor='none') plt.scatter(SWpts[:, 0] - eps, SWpts[:, 1] - eps, c='k', edgecolor='none') plt.scatter(Spts[:, 0], Spts[:, 1] - eps, c='lightgrey', edgecolor='none') plt.scatter(SEpts[:, 0] - eps, SEpts[:, 1] - eps, c='c', edgecolor='none') plt.show() return xyout
[docs]def buffered_pts_to_periodic_network_parallelogram(xy, BL, PV, BBox='auto', flex_pvxy=False, check=False): """Crops to original bounding box and connects periodic BCs of sample in a parallelogram Note: Default BBox is such that original BBox is [-PV[0, 0]*0.5, -(PV[1, 1] + PV[0, 1])*0.5], [PV[0, 0]*0.5, (-PV[1, 1] + PV[0, 1])*0.5], etc. Presently this only allows parallelograms with vertical sides. /|PV[0] + PV[1] / | / | | | PV[0] | / | / |/ (0,0) Parameters ---------- xy : NP x 2 float array xy points with buffer points, so xy are in the triangular representation BL : #bonds x 2 int array Bond list for the network: a row with [i, j] means i and j are connected. If negative, across periodic boundary PV : 2 x 2 float array Periodic vectors: the first has x and y components, the second has only positive y component. BBox : 4 x 2 numpy float array bounding box for the network. Here, this must be a parallelogram. The first point must be the lower left point! check : bool Whether to view intermediate results Returns ------- """ if BBox == 'auto': # Assuming that BBox is centered and has width, height of LL[0], LL[1] BBox = 0.5 * np.array([[-PV[0, 0], -PV[1, 1] - PV[0, 1]], [PV[0, 0], -PV[1, 1] + PV[0, 1]], [PV[0, 0], PV[1, 1] + PV[0, 1]], [-PV[0, 0], PV[1, 1] - PV[0, 1]]]) bpath = mplpath.Path(BBox) keep = np.where(bpath.contains_points(xy))[0] if check: plt.plot(xy[:, 0], xy[:, 1], 'b.') plt.plot(BBox[:, 0], BBox[:, 1], 'r.-') plt.title('le.buffered_pts_to_periodic_network_parallelogram BBox') plt.show() minX = np.min(BBox[:, 0]) maxX = np.max(BBox[:, 0]) # Note that minY is the minY on the LEFT of the system, not total # Similarly, note that minY is the maxY on the RIGHT of the system, not total slope = PV[0, 1] / (maxX - minX) minY = BBox[0, 1] maxY = minY + PV[1, 1] + PV[0, 1] def lowerY(x): return minY + slope * (x - minX) def upperY(x): return minY + PV[1, 1] + slope * (x - minX) PVdict = {'e': PV[0], 'n': PV[1], 'w': -PV[0], 's': -PV[1], 'ne': PV[0] + PV[1], 'nw': -PV[0] + PV[1], 'sw': -PV[0] - PV[1], 'se': PV[0] - PV[1]} # Create a kd tree of the points tree = scipy.spatial.KDTree(xy) # Find bonds that will be cut. For each bond, match to other particle and add pair to BL and PVxydict BLcut, cutIND = find_cut_bonds(BL, keep) if check: plt.scatter(xy[:, 0], xy[:, 1], c='g', marker='x') plt.scatter(xy[keep, 0], xy[keep, 1], c='b', marker='o') highlight_bonds(xy, BL, ax=plt.gca(), color='b', show=False) highlight_bonds(xy, BLcut, ax=plt.gca(), color='r', lw=1, show=False) xxtmp = np.hstack((BBox[:, 0], np.array(BBox[:, 0]))) print 'xxtmp = ', xxtmp yytmp = np.hstack((BBox[:, 1], np.array(BBox[:, 1]))) print 'yytmp = ', yytmp plt.plot(xxtmp, yytmp, 'k-', lw=2) plt.title('Showing bonds that are cut, btwn original and mirrored network') plt.show() plt.scatter(xy[:, 0], xy[:, 1], c='g', marker='x') plt.scatter(xy[keep, 0], xy[keep, 1], c='b', marker='o') highlight_bonds(xy, BL, ax=plt.gca(), color='b', show=False) highlight_bonds(xy, BLcut, ax=plt.gca(), color='r', lw=1, show=False) xxtmp = np.hstack((BBox[:, 0], np.array(BBox[:, 0]))) print 'xxtmp = ', xxtmp yytmp = np.hstack((BBox[:, 1], np.array(BBox[:, 1]))) print 'yytmp = ', yytmp plt.plot(xxtmp, yytmp, 'k-', lw=2) plt.title('Showing bonds that are cut, with pt #s') for ind in range(len(xy)): plt.text(xy[ind, 0] + 0.1, xy[ind, 1] - 0.1, str(ind)) plt.show() # Prepare image to display NSWE scattered on top highlight_bonds(xy, BL, ax=plt.gca(), color='lightgrey', show=False) highlight_bonds(xy, BLcut, ax=plt.gca(), color='dimgray', lw=1, show=False) print 'preparing image....' # preallocate BL2add and PVs BL2add = np.zeros((len(BLcut), 2), dtype=int) PVd = {} kk = 0 for bond in BLcut: # which endpt is outside? ptA = bond[0] ptB = bond[1] # mpt is short for 'mirror point', the point outside the bounding box if ptA not in keep: mpt, kpt = ptA, ptB else: mpt, kpt = ptB, ptA if xy[mpt, 0] < minX: # Mirror particle is to the left of the system (West) if xy[mpt, 1] < lowerY(xy[mpt, 0]): # Mirror particle is SW bPV = PVdict['sw'] elif xy[mpt, 1] > upperY(xy[mpt, 0]): # Mirror particle is NW bPV = PVdict['nw'] else: # Mirror particle is West bPV = PVdict['w'] elif xy[mpt, 0] > maxX: # Mirror particles is the right of the system (East) if xy[mpt, 1] < lowerY(xy[mpt, 0]): # Mirror particle is SE bPV = PVdict['se'] elif xy[mpt, 1] > upperY(xy[mpt, 0]): # Mirror particle is NE bPV = PVdict['ne'] else: # Mirror particle is East bPV = PVdict['e'] elif xy[mpt, 1] < lowerY(xy[mpt, 0]): # Mirror particle is South bPV = PVdict['s'] else: # Mirror particle is North bPV = PVdict['n'] if check: print 'adding pt...' if (bPV == PVdict['sw']).all(): plt.scatter(xy[mpt, 0], xy[mpt, 1], c='r', edgecolor='none', zorder=9999) elif (bPV == PVdict['w']).all(): plt.scatter(xy[mpt, 0], xy[mpt, 1], c='g', edgecolor='none', zorder=9999) elif (bPV == PVdict['nw']).all(): plt.scatter(xy[mpt, 0], xy[mpt, 1], c='y', edgecolor='none', zorder=9999) elif (bPV == PVdict['n']).all(): plt.scatter(xy[mpt, 0], xy[mpt, 1], c='b', edgecolor='none', zorder=9999) elif (bPV == PVdict['ne']).all(): plt.scatter(xy[mpt, 0], xy[mpt, 1], c='c', edgecolor='none', zorder=9999) elif (bPV == PVdict['e']).all(): plt.scatter(xy[mpt, 0], xy[mpt, 1], c='m', edgecolor='none', zorder=9999) elif (bPV == PVdict['se']).all(): plt.scatter(xy[mpt, 0], xy[mpt, 1], c='k', edgecolor='none', zorder=9999) elif (bPV == PVdict['s']).all(): plt.scatter(xy[mpt, 0], xy[mpt, 1], c='w', edgecolor='none', zorder=9999) # Link keep point (kpt) to the particle that resides a vector -PV away from mirror particle dist, ind = tree.query(xy[mpt] - bPV) BL2add[kk] = np.array([-kpt, -ind]) PVd[(kpt, ind)] = bPV kk += 1 if check: plt.plot(np.hstack((BBox[:, 0], np.array([BBox[0, 0]]))), np.hstack((BBox[:, 1], np.array([BBox[0, 1]]))), 'r-') plt.show() if check: print 'PVd = ', PVd xyshake = xy + 0.1 * np.random.rand(np.shape(xy)[0], np.shape(xy)[1]) display_lattice_2D(xyshake, np.abs(BL), title="showing extended lattice (w/o PBCs)") # Crop network, and add back cut bonds as periodic ones BL = np.vstack((BL, BL2add)) xytrim, NL, KL, BLtrim = remove_pts(keep, xy, BL) # Adjusting BL2add to account for smaller #npts (post-cropping) is already done in remove_pts # Adjust PVs to account for smaller #npts (post-cropping) remove = np.setdiff1d(np.arange(len(xy)), keep) if check: print 'PVd = ', PVd xyshake = xy + 0.1 * np.random.rand(np.shape(xy)[0], np.shape(xy)[1]) display_lattice_2D(xyshake, np.abs(BL), title="showing extended lattice with BL2add", close=False) plt.scatter(xy[remove, 0], xy[remove, 1], c='c', zorder=999999) plt.show() # Use PVd (which included buffered pts) to make PVxydict PVxydict = {} for key in PVd: # adjust key to lower indices # count how many pts in remove are lower than key[0] and key[1], respectively lower0 = np.sum(remove < key[0]) lower1 = np.sum(remove < key[1]) newkey = (key[0] - lower0, key[1] - lower1) PVxydict[newkey] = PVd[key] # if lower0 > 0 or lower1 > 0: # print 'key =', key # print 'newkey =', newkey # print 'lower0 =', lower0 # print 'lower1 =', lower1 if check: # Plot lattice without PBCs display_lattice_2D(xytrim, np.abs(BLtrim), title="showing lattice connectivity w/o PBCs", close=False) for ind in range(len(xytrim)): plt.text(xytrim[ind, 0], xytrim[ind, 1], str(ind)) plt.show() print 'PVxydict = ', PVxydict NL, KL = BL2NLandKL(BLtrim) PVx, PVy = PVxydict2PVxPVy(PVxydict, NL, check=check) print 'PVx = ', PVx display_lattice_2D(xytrim, BLtrim, PVxydict=PVxydict, PVx=PVx, PVy=PVy, title="showing lattice connectivity with PBCs") return xytrim, NL, KL, BLtrim, PVxydict
[docs]def highlight_bonds(xy, BL, ax=None, color='r', lw=1, show=True): """Plot bonds specified in BL on specified axis. Parameters ---------- xy BL ax color show Returns ------- ax """ if ax is None: ax = plt.gca() for bond in BL: ax.plot([xy[bond[0], 0], xy[bond[1], 0]], [xy[bond[0], 1], xy[bond[1], 1]], '-', color=color, lw=lw) if show: plt.show() return ax
[docs]def find_cut_bonds(BL, keep): """Identify which bonds are cut by the supplied mask 'keep'. Parameters ---------- BL keep Returns ------- BLcut : #cut bonds x 2 int array The cut bonds cutIND : #bonds x 1 int array indices of BL of cut bonds """ # ensure that keep is int array of indices, not bool if keep.dtype == 'bool': print 'converting bool keep to int array...' keep = np.where(keep)[0] # Make output BLcut and the indices of BL that are cut (cutIND) # Find rows of BL for which both elems are in keep inBL0 = np.in1d(np.abs(BL[:, 0]), keep) inBL1 = np.in1d(np.abs(BL[:, 1]), keep) cutIND = np.logical_xor(inBL0, inBL1) BLcut = BL[cutIND, :] return BLcut, cutIND
[docs]def PBCmap_for_rectangular_buffer(xy, LL, dist=7.0): """Buffers the true point set with a mirrored point set across each boundary, for a rectangular boundary, and creates a dict mapping each mirror (outer) particle to its true (inner) particle. NOTE: This code is UNTESTED! Parameters ---------- xy : NP x 2 float array Particle positions LL : tuple of 2 floats Horizontal and vertical extent of the bounding box (a rectangle) through which there are periodic boundaries. dist : float maximum depth of the buffer on each side Returns ------- xyout : PBCmap : dict with int keys and tuple of (int, numpy 1x2 float array) as values Periodic boundary condition map, takes (key) index of xyout to (value) its reference/true point For example, say that particle 2 looks to particle 1 as if beyond a boundary, translated by (LL[0],0). Since this function buffers the true point set with a mirrored point set across each boundary, then PBCmap maps the index of the mirror point to its true particle position inside the bounding box of the sample, tupled with the true particle's periodic vector (the vector mapping the true point to the mirror pt). """ # Copy some of lattice to north, south, east, west and corners west = np.where(xy[:, 0] < (np.min(xy[:, 0]) + dist))[0] sout = np.where(xy[:, 1] < (np.min(xy[:, 1]) + dist))[0] east = np.where(xy[:, 0] > (np.max(xy[:, 0]) - dist))[0] nort = np.where(xy[:, 1] > (np.max(xy[:, 1]) - dist))[0] swest = np.intersect1d(sout, west) seast = np.intersect1d(sout, east) neast = np.intersect1d(nort, east) nwest = np.intersect1d(nort, west) Epts = xy[west] + np.array([LL[0], 0.0]) Npts = xy[sout] + np.array([0.0, LL[1]]) Wpts = xy[east] + np.array([-LL[0], 0.0]) Spts = xy[nort] + np.array([0.0, -LL[1]]) NEpts = xy[swest] + np.array([LL[0], LL[1]]) NWpts = xy[seast] + np.array([-LL[0], LL[1]]) SWpts = xy[neast] + np.array([-LL[0], -LL[1]]) SEpts = xy[nwest] + np.array([LL[0], -LL[1]]) xyout = np.vstack((xy, Epts, NEpts, Npts, NWpts, Wpts, SWpts, Spts, SEpts)) groupLister = ['w'] * len(west) + ['s'] * len(sout) + ['e'] * len(east) + ['n'] * len(nort) + \ ['sw'] * len(swest) + ['se'] * len(seast) + ['ne'] * len(neast) + ['nw'] * len(nwest) # PVdict maps the location of the true point to its mirror (outside bbox) point PVdict = {'w': np.array([LL[0], 0.0]), 's': np.array([0.0, LL[1]]), 'e': np.array([-LL[0], 0.0]), 'n': np.array([0.0, -LL[1]]), 'sw': np.array([LL[0], LL[1]]), 'se': np.array([-LL[0], LL[1]]), 'ne': np.array([-LL[0], -LL[1]]), 'nw': np.array([LL[0], -LL[1]])} # Now form dictionary taking (key) index of xyout to (value) its reference/mirror point. # Note that order matters!! PBCmap = {} ind = len(xy) for group in [west, sout, east, nort, swest, seast, neast, nwest]: PV = PVdict[groupLister[ind - len(xy)]] for ii in group: PBCmap[ind] = (ii, PV) ind += 1 return xyout, PBCmap
[docs]def centroid_lattice_from_TRI(xy, TRI, check=False): """Convert triangular representation (such as a triangulation) of lattice to xy, NL, KL of centroid lattice. Parameters ---------- xy : #pts x 2 float array xy points from which to find centroids, so xy are in the triangular representation TRI : array of dimension #tris x 3 Each row contains indices of the 3 points lying at the vertices of the tri. Returns ---------- xy : #pts x 2 float array centroids for lattice NL : NP x NN int array Neighbor list KL : NP x NN int array Connectivity list BL : Nbonds x 2 int array Bond list BM : NP x NN float array unstretched (reference) bond length matrix """ # Check if any elements of TRI are negative, and remove those rows of TRI # DO THIS # Compute centroids centxy = xyandTRI2centroid(xy, TRI) sizes = np.random.rand(len(centxy)) * 100 colors = np.random.rand(len(centxy)) if check: plt.triplot(xy[:, 0], xy[:, 1], TRI, 'go-') plt.scatter(centxy[:, 0], centxy[:, 1], s=sizes, c=colors, alpha=0.2) plt.show() # Now make NL and KL from TRI NL, KL = TRI2centroidNLandKL(TRI) BL = NL2BL(NL, KL) BM = NL2BM(centxy, NL, KL) return centxy, NL, KL, BL, BM
################################################### # Below are some pieces of the plotting section ###################################################
[docs]def collect_lines(xy, BL, bs, climv): """Creates collection of line segments, colored according to an array of values. Parameters ---------- xy : array of dimension nx2 2D lattice of points (positions x,y) BL : array of dimension #bonds x 2 Each row is a bond and contains indices of connected points bs : array of dimension #bonds x 1 Strain in each bond climv : float or tuple Color limit for coloring bonds by bs Returns ---------- line_segments : matplotlib.collections.LineCollection Collection of line segments """ lines = [zip(xy[BL[i, :], 0], xy[BL[i, :], 1]) for i in range(len(BL))] line_segments = LineCollection(lines, # Make a sequence of x,y pairs linewidths=1., # could iterate over list linestyles='solid', cmap='coolwarm', norm=plt.Normalize(vmin=-climv, vmax=climv)) line_segments.set_array(bs) print(lines) return line_segments
[docs]def draw_lattice(ax, lat, bondcolor='k', colormap='BlueBlackRed', lw=1, climv=0.1): """Add a network to an axis instance (draw the bonds of the network) Parameters ---------- ax xy BL bondcolor colormap lw climv Returns ------- """ if (lat.BL < 0).any(): if lat.PVxydict is None: raise RuntimeError('PVxydict must be supplied to draw_lattice when periodic BCs exist!') else: PVx, PVy = PVxydict2PVxPVy(lat.PVxydict, lat.NL) # get indices of periodic bonds perINDS = np.unique(np.where(lat.BL < 0)[0]) perBL = np.abs(lat.BL[perINDS]) # # Check # print 'perBL = ', perBL # plt.plot(xy[:,0], xy[:,1],'b.') # for i in range(len(xy)): # plt.text(xy[i,0]+0.05, xy[i,1],str(i)) # plt.show() normINDS = np.setdiff1d(np.arange(len(lat.BL)), perINDS) BLtmp = lat.BL[normINDS] lines = [zip(lat.xy[BLtmp[i, :], 0], lat.xy[BLtmp[i, :], 1]) for i in range(len(BLtmp))] xy_add = np.zeros((4, 2)) # Add periodic bond lines to image for row in perBL: # print 'NL[row[0]] = ', NL[row[0]] colA = np.argwhere(lat.NL[row[0]] == row[1])[0][0] colB = np.argwhere(lat.NL[row[1]] == row[0])[0][0] xy_add[0] = lat.xy[row[0]] xy_add[1] = lat.xy[row[1]] + np.array([PVx[row[0], colA], PVy[row[0], colA]]) xy_add[2] = lat.xy[row[1]] xy_add[3] = lat.xy[row[0]] + np.array([PVx[row[1], colB], PVy[row[1], colB]]) # print 'first line : ', zip(xy_add[0:2,0], xy_add[0:2,1]) # print 'second line : ', zip(xy_add[2:4,0], xy_add[2:4,1]) lines += zip(xy_add[0:2, 0], xy_add[0:2, 1]), zip(xy_add[2:4, 0], xy_add[2:4, 1]) # CHECK # line_segments = LineCollection(lines, # Make a sequence of x,y pairs # linewidths = lw, #could iterate over list # linestyles = 'solid', # cmap='seismic', # norm=plt.Normalize(vmin=-climv,vmax=climv)) # ax.add_collection(line_segments) # for i in range(len(xy)): # ax.text(xy[i,0] + 0.05, xy[i,1],str(i)) # plt.pause(.01) else: if np.shape(BL)[0] > 1: lines = [zip(lat.xy[lat.BL[i, :], 0], lat.xy[lat.BL[i, :], 1]) for i in range(np.shape(lat.BL)[0])] else: lines = [zip(lat.xy[lat.BL[i][0]], lat.xy[lat.BL[i][1]]) for i in range(np.shape(lat.BL)[0])] if bondcolor is None: line_segments = LineCollection(lines, # Make a sequence of x,y pairs linewidths=lw, # could iterate over list linestyles='solid', cmap=colormap, norm=plt.Normalize(vmin=-climv, vmax=climv)) line_segments.set_array(bs) else: line_segments = LineCollection(lines, linewidths=lw, linestyles='solid', colors=bondcolor) ax.add_collection(line_segments) return ax
[docs]def movie_plot_2D(xy, BL, bs=None, fname='none', title='', NL=[], KL=[], BLNNN=[], NLNNN=[], KLNNN=[], PVx=[], PVy=[], PVxydict={}, ax=None, fig=None, axcb='auto', xlimv='auto', ylimv='auto', climv=0.1, colorz=True, ptcolor=None, figsize='auto', colorpoly=False, bondcolor=None, colormap='seismic', bgcolor=None, axis_off=False, axis_equal=True, text_topleft=None, lw=-1., ptsize=10, negative_NNN_arrows=False, show=False, arrow_alpha=1.0, fontsize=8): """Plots (and saves if fname is not 'none') a 2D image of the lattice with colored bonds and particles colored by coordination (both optional). Parameters ---------- xy : array of dimension nx2 2D lattice of points (positions x,y) BL : array of dimension #bonds x 2 Each row is a bond and contains indices of connected points bs : array of dimension #bonds x 1 Strain in each bond fname : string Full path including name of the file (.png, etc), if None, will not save figure title : string The title of the frame NL : NP x NN int array (optional, for speed) Specify to speed up computation, if colorz or colorpoly or if periodic boundary conditions KL : NP x NN int array (optional, for speed) Specify to speed up computation, if colorz or colorpoly or if periodic boundary conditions PVx : NP x NN float array (optional, for periodic lattices and speed) ijth element of PVx is the x-component of the vector taking NL[i,j] to its image as seen by particle i If PVx and PVy are specified, PVxydict need not be specified. PVy : NP x NN float array (optional, for periodic lattices and speed) ijth element of PVy is the y-component of the vector taking NL[i,j] to its image as seen by particle i If PVx and PVy are specified, PVxydict need not be specified. PVxydict : dict (optional, for periodic lattices) dictionary of periodic bonds (keys) to periodic vectors (values) ax: matplotlib figure axis instance Axis on which to draw the network fig: matplotlib figure instance Figure on which to draw the network axcb: matplotlib colorbar instance Colorbar to use for strains in bonds xlimv: float or tuple of floats ylimv: float or tuple of floats climv : float or tuple Color limit for coloring bonds by bs colorz: bool whether to color the particles by their coordination number ptcolor: string color spec or tuple color spec or None color specification for coloring the points, if colorz is False. Default is None (no coloring of points) figsize : tuple w,h tuple in inches colorpoly : bool Whether to color in polygons formed by bonds according to the number of sides bondcolor : color specification (hexadecimal or RGB) colormap : if bondcolor is None, uses bs array to color bonds bgcolor : hex format string, rgb color spec, or None If not None, sets the bgcolor. Often used is '#d9d9d9' axis_off axis_equal text_topleft lw: float line width for plotting bonds. If lw == -1, then uses automatic line width to adjust for bond density.. ptsize: float size of points passed to absolute_sizer Returns ---------- [ax,axcb] : stuff to clear after plotting """ print 'fig = ', fig print 'ax = ', ax if fig is None or fig == 'none': fig = plt.gcf() if ax is None or ax == 'none': if figsize == 'auto': plt.clf() else: fig = plt.figure(figsize=figsize) ax = plt.axes() if colormap not in plt.colormaps(): lecmaps.register_colormaps() NP = len(xy) if lw == -1: if NP < 10000: lw = 0.5 s = absolute_sizer() else: lw = (10 / np.sqrt(len(xy))) if NL == [] and KL == []: if colorz or colorpoly: NL, KL = BL2NLandKL(BL, NP=NP, NN='min') if (BL < 0).any(): if len(PVxydict) == 0: raise RuntimeError('PVxydict must be supplied to display_lattice_2D() when periodic BCs exist, ' + 'if NL and KL not supplied!') else: PVx, PVy = PVxydict2PVxPVy(PVxydict, NL) if colorz: zvals = (KL != 0).sum(1) zmed = np.median(zvals) # print 'zmed = ', zmed under1 = np.logical_and(zvals < zmed - 0.5, zvals > zmed - 1.5) over1 = np.logical_and(zvals > zmed + 0.5, zvals < zmed + 1.5) Cz = np.zeros((len(xy), 3), dtype=int) # far under black // under blue // equal white // over red // far over green Cz[under1] = [0. / 255, 50. / 255, 255. / 255] Cz[zvals == zmed] = [255. / 255, 255. / 255, 255. / 255] Cz[over1] = [255. / 255, 0. / 255, 0. / 255] Cz[zvals > zmed + 1.5] = [0. / 255, 255. / 255, 50. / 255] # Cz[zvals<zmed-1.5] = [0./255,255./255,150./255] #leave these black s = absolute_sizer() sval = min([.005, .12 / np.sqrt(len(xy))]) sizes = np.zeros(NP, dtype=float) sizes[zvals > zmed + 0.5] = sval sizes[zvals < zmed - 0.5] = sval # topinds = zvals!=zmed ax.scatter(xy[:, 0], xy[:, 1], s=s(sizes), c=Cz, edgecolor='none', zorder=10) ax.axis('equal') elif ptcolor is not None and ptcolor != 'none' and ptcolor != '': if NP < 10000: # if smallish #pts, plot them # print 'xy = ', xy # plt.plot(xy[:,0],xy[:,1],'k.') s = absolute_sizer() ax.scatter(xy[:, 0], xy[:, 1], s=ptsize, alpha=0.5, facecolor=ptcolor, edgecolor='none') if colorpoly: # Color the polygons based on # sides # First extract polygons. To do that, if there are periodic boundaries, we need to supply as dict if PVxydict == {} and len(PVx) > 0: PVxydict = PVxy2PVxydict(PVx, PVy, NL, KL=KL) polygons = extract_polygons_lattice(xy, BL, NL=NL, KL=KL, viewmethod=True, PVxydict=PVxydict) PolyPC = polygons2PPC(polygons) # number of polygon sides Pno = np.array([len(polyg) for polyg in polygons], dtype=int) print 'Pno = ', Pno print 'medPno = ', np.floor(np.median(Pno)) medPno = np.floor(np.median(Pno)) uIND = np.where(Pno == medPno - 1)[0] mIND = np.where(Pno == medPno)[0] oIND = np.where(Pno == medPno + 1)[0] loIND = np.where(Pno < medPno - 1.5)[0] hiIND = np.where(Pno > medPno + 1.5)[0] print ' uIND = ', uIND print ' oIND = ', oIND print ' loIND = ', loIND print ' hiIND = ', hiIND if len(uIND) > 0: PPCu = [PolyPC[i] for i in uIND] pu = PatchCollection(PPCu, color='b', alpha=0.5) ax.add_collection(pu) if len(mIND) > 0: PPCm = [PolyPC[i] for i in mIND] pm = PatchCollection(PPCm, color=[0.5, 0.5, 0.5], alpha=0.5) ax.add_collection(pm) if len(oIND) > 0: PPCo = [PolyPC[i] for i in oIND] po = PatchCollection(PPCo, color='r', alpha=0.5) ax.add_collection(po) if len(loIND) > 0: PPClo = [PolyPC[i] for i in loIND] plo = PatchCollection(PPClo, color='k', alpha=0.5) ax.add_collection(plo) if len(hiIND) > 0: PPChi = [PolyPC[i] for i in hiIND] phi = PatchCollection(PPChi, color='g', alpha=0.5) ax.add_collection(phi) # Efficiently plot many lines in a single set of axes using LineCollection # First check if there are periodic bonds if BL.size > 0: if (BL < 0).any(): if PVx == [] or PVy == []: raise RuntimeError('PVx and PVy must be supplied to display_lattice_2D when periodic BCs exist!') else: # get indices of periodic bonds perINDS = np.unique(np.where(BL < 0)[0]) perBL = np.abs(BL[perINDS]) # # Check # print 'perBL = ', perBL # plt.plot(xy[:,0], xy[:,1],'b.') # for i in range(len(xy)): # plt.text(xy[i,0]+0.05, xy[i,1],str(i)) # plt.show() normINDS = np.setdiff1d(np.arange(len(BL)), perINDS) BLtmp = BL[normINDS] lines = [zip(xy[BLtmp[i, :], 0], xy[BLtmp[i, :], 1]) for i in range(len(BLtmp))] xy_add = np.zeros((4, 2)) # Add periodic bond lines to image for row in perBL: # print 'NL[row[0]] = ', NL[row[0]] colA = np.argwhere(NL[row[0]] == row[1])[0][0] colB = np.argwhere(NL[row[1]] == row[0])[0][0] xy_add[0] = xy[row[0]] xy_add[1] = xy[row[1]] + np.array([PVx[row[0], colA], PVy[row[0], colA]]) xy_add[2] = xy[row[1]] xy_add[3] = xy[row[0]] + np.array([PVx[row[1], colB], PVy[row[1], colB]]) # print 'first line : ', zip(xy_add[0:2,0], xy_add[0:2,1]) # print 'second line : ', zip(xy_add[2:4,0], xy_add[2:4,1]) lines += zip(xy_add[0:2, 0], xy_add[0:2, 1]), zip(xy_add[2:4, 0], xy_add[2:4, 1]) # CHECK # line_segments = LineCollection(lines, # Make a sequence of x,y pairs # linewidths = lw, #could iterate over list # linestyles = 'solid', # cmap='seismic', # norm=plt.Normalize(vmin=-climv,vmax=climv)) # ax.add_collection(line_segments) # for i in range(len(xy)): # ax.text(xy[i,0] + 0.05, xy[i,1],str(i)) # plt.pause(.01) else: if np.shape(BL)[0] > 1: lines = [zip(xy[BL[i, :], 0], xy[BL[i, :], 1]) for i in range(np.shape(BL)[0])] else: lines = [zip(xy[BL[i][0]], xy[BL[i][1]]) for i in range(np.shape(BL)[0])] if bondcolor is None: line_segments = LineCollection(lines, # Make a sequence of x,y pairs linewidths=lw, # could iterate over list linestyles='solid', cmap=colormap, norm=plt.Normalize(vmin=-climv, vmax=climv)) line_segments.set_array(bs) else: line_segments = LineCollection(lines, linewidths=lw, linestyles='solid', colors=bondcolor) ax.add_collection(line_segments) if axcb == 'auto': print 'Instantiating colorbar...' axcb = fig.colorbar(line_segments) if axcb != 'none' and axcb is not None: print 'Creating colorbar...' axcb.set_label('Strain', fontsize=fontsize) axcb.set_clim(vmin=-climv, vmax=climv) else: axcb = 'none' if len(BLNNN) > 0: # Efficiently plot many lines in a single set of axes using LineCollection lines = [zip(xy[BLNNN[i, :], 0], xy[BLNNN[i, :], 1]) for i in range(len(BLNNN))] linesNNN = LineCollection(lines, # Make a sequence of x,y pairs linewidths=lw, # could iterate over list linestyles='dashed', color='blue', zorder=100) ax.add_collection(linesNNN) elif len(NLNNN) > 0 and len(KLNNN) > 0: if (BL < 0).any(): print 'plotting periodic NNN...' for i in range(NP): todo = np.where(KLNNN[i, :] > 1e-12)[0] for ind in NLNNN[i, todo]: ax.arrow(xy[i, 0], xy[i, 1], (xy[ind, 0] - xy[i, 0]) * 0.98, (xy[ind, 1] - xy[i, 1]) * 0.98, head_width=0.1, head_length=0.2, fc='b', ec='b') # plt.pause(5) else: # amount to offset clockwise nnn arrows for i in range(NP): todo = np.where(KLNNN[i, :] > 1e-12)[0] # Allow for both blue and red arrows (forward/backward), or just blue. If just blue, use no offset and # full scale factor if negative_NNN_arrows: scalef = 0.3 else: scalef = 0.8 offset = np.array([0.0, 0.0]) for ind in NLNNN[i, todo]: if negative_NNN_arrows: offset = (xy[ind, :] - xy[i, :]) * 0.5 ax.arrow(xy[i, 0] + offset[0], xy[i, 1] + offset[1], (xy[ind, 0] - xy[i, 0]) * scalef, (xy[ind, 1] - xy[i, 1]) * scalef, head_width=0.1, head_length=0.2, fc='b', ec='b', alpha=arrow_alpha) if negative_NNN_arrows: todo = np.where(KLNNN[i, :] < -1e-12)[0] for ind in NLNNN[i, todo]: offset = (xy[ind, :] - xy[i, :]) * 0.5 ax.arrow(xy[i, 0] + offset[0], xy[i, 1] + offset[1], (xy[ind, 0] - xy[i, 0]) * 0.3, (xy[ind, 1] - xy[i, 1]) * 0.3, head_width=0.1, head_length=0.2, fc='r', ec='r', alpha=arrow_alpha) if bgcolor is not None: ax.set_axis_bgcolor(bgcolor) # set limits ax.axis('scaled') if xlimv != 'auto' and xlimv is not None: if isinstance(xlimv, tuple): ax.set_xlim(xlimv[0], xlimv[1]) else: print 'setting xlimv' ax.set_xlim(-xlimv, xlimv) else: ax.set_xlim(np.min(xy[:, 0]) - 1, np.max(xy[:, 0]) + 1) if ylimv != 'auto' and ylimv is not None: if isinstance(ylimv, tuple): print 'setting ylimv to tuple' ax.set_ylim(ylimv[0], ylimv[1]) else: ax.set_ylim(-ylimv, ylimv) else: print '\nsetting', min(xy[:, 1]), max(xy[:, 1]) ax.set_ylim(np.min(xy[:, 1]) - 1, np.max(xy[:, 1]) + 1) if title is not None: ax.set_title(title, fontsize=fontsize) if text_topleft is not None: ax.text(0.05, .98, text_topleft, horizontalalignment='right', verticalalignment='top', transform=ax.transAxes) if axis_off: ax.axis('off') if fname != 'none' and fname != '' and fname is not None: print 'saving figure: ', fname plt.savefig(fname) if show: plt.show() return [ax, axcb]
def get_points_per_unit(ax=None): if ax is None: ax = P.gca() ax.apply_aspect() x0, x1 = ax.get_xlim() return ax.bbox.width / abs(x1 - x0) def absolute_sizer(ax=None): ppu = get_points_per_unit(ax) return lambda x: np.pi * (x*ppu)**2
[docs]def display_lattice_2D(xy, BL, NL=[], KL=[], BLNNN=[],NLNNN=[], KLNNN=[], PVxydict={}, PVx=[], PVy=[], bs='none', title='', xlimv=None, ylimv=None, climv=0.1, colorz=True, ptcolor=None, ptsize=10, close=True, colorpoly=False, viewmethod=False, labelinds=False, colormap='seismic', bgcolor='#d9d9d9', axis_off=False, fig=None, ax=None, linewidth=0.0, edgecolors=None):
# another popular choice: 'BlueBlackRed' , #FFFFFF True """Plots and displays a 2D image of the lattice with colored bonds. Parameters ---------- xy : array of dimension nx2 2D lattice of points (positions x,y) BL : array of dimension #bonds x 2 Each row is a bond and contains indices of connected points. Negative values denote periodic BCs bs : array of dimension #bonds x 1 Strain in each bond fname : string Full path including name of the file (.png, etc) title : string The title of the frame climv : float or tuple Color limit for coloring bonds by bs colorz : bool Whether to color the particles by their coordination number close : bool Whether or not to leave the plot hanging to force it to be closed colorpoly : bool Whether to color polygons by the number of edges BLNNN : NP x NNNN int array Bond list for next nearest neighbor couplings. Returns ---------- ax : matplotlib axis instance or None If close==True, returns None. Otherwise returns the axis with the network plotted on it. """ if bs == 'none': bs = np.zeros_like(BL[:, 0]) if ax is None: plt.clf() ax = plt.axes() NP = len(xy) if linewidth == 0.0: if NP<10000: lw = (2.) else: lw=(30/np.sqrt(len(xy))) else: lw = linewidth if NL == [] or KL == []: if colorz or colorpoly: NL, KL = BL2NLandKL(BL, NP=NP, NN='min') if (BL < 0).any(): if len(PVxydict) == 0: raise RuntimeError('PVxydict must be supplied to display_lattice_2D() when periodic BCs exist, if NL and KL not supplied!') else: PVx, PVy = PVxydict2PVxPVy(PVxydict,NL) if colorz: zvals = (KL != 0).sum(1) zmed = np.median(zvals) # print 'zmed = ', zmed under1 = np.logical_and(zvals < zmed-0.5, zvals > zmed-1.5) over1 = np.logical_and(zvals > zmed+0.5, zvals < zmed+1.5) Cz = np.zeros((len(xy), 3), dtype=int) # far under black // under blue // equal white // over red // far over green Cz[under1] = [ 0./255, 50./255, 255./255] Cz[zvals == zmed] = [255./255, 255./255, 255./255] Cz[over1] = [255./255, 0./255, 0./255] Cz[zvals > zmed+1.5] = [0./255,255./255,50./255] # Cz[zvals<zmed-1.5] = [0./255,255./255,150./255] #leave these black s = absolute_sizer() sval = min([.005, .12/np.sqrt(len(xy))]) sizes = np.zeros(NP, dtype=float) sizes[zvals > zmed+0.5] = sval sizes[zvals < zmed-0.5] = sval # topinds = zvals!=zmed ax.scatter(xy[:, 0], xy[:, 1], s=s(sizes), c=Cz, edgecolor='none', zorder=10) ax.axis('equal') elif ptcolor is not None: if NP < 1000: # if smallish #pts, plot them # print 'xy = ', xy # plt.plot(xy[:,0],xy[:,1],'k.') s = absolute_sizer() ax.scatter(xy[:, 0], xy[:, 1], s=ptsize, alpha=0.5, color=ptcolor, zorder=10, edgecolors=edgecolors) # Efficiently plot many lines in a single set of axes using LineCollection # First check if there are periodic bonds if (BL < 0).any(): if len(PVx) == 0 or len(PVy) == 0: if len(PVxydict) == 0: raise RuntimeError('PVx and PVy or PVxydict must be supplied to display_lattice_2D when periodic BCs exist!') else: PVx, PVy = PVxydict2PVxPVy(PVxydict, NL) # get indices of periodic bonds perINDS = np.unique(np.where(BL<0)[0]) perBL = np.abs(BL[perINDS]) normINDS = np.setdiff1d( np.arange(len(BL)), perINDS) BLtmp = BL[normINDS] lines = [zip(xy[BLtmp[i, :], 0], xy[BLtmp[i, :], 1]) for i in range(len(BLtmp))] xy_add = np.zeros((4, 2)) # Add periodic bond lines to image for row in perBL: colA = np.argwhere(NL[row[0]] == row[1])[0][0] colB = np.argwhere(NL[row[1]] == row[0])[0][0] xy_add[0] = xy[row[0]] xy_add[1] = xy[row[1]] + np.array([ PVx[row[0], colA], PVy[row[0], colA] ]) xy_add[2] = xy[row[1]] xy_add[3] = xy[row[0]] + np.array([ PVx[row[1], colB], PVy[row[1], colB] ]) lines += zip(xy_add[0:2, 0], xy_add[0:2, 1]), zip(xy_add[2:4, 0], xy_add[2:4, 1]) else: lines = [zip(xy[BL[i, :], 0], xy[BL[i, :], 1]) for i in range(len(BL))] if isinstance(climv, tuple): vmin = climv[0] vmax = climv[1] else: vmin = -climv vmax = climv line_segments = LineCollection(lines, # Make a sequence of x,y pairs linewidths=lw, # could iterate over list linestyles='solid', cmap=colormap, norm=plt.Normalize(vmin=vmin, vmax=vmax)) line_segments.set_array(bs) ax.add_collection(line_segments) ax.set_axis_bgcolor(bgcolor) # [214./255.,214./255.,214./255.] ) #'#E8E8E8') # POLYGONS if colorpoly: # Color the polygons based on # sides print 'Extracting polygons from lattice...' print 'NL = ', NL polygons = extract_polygons_lattice(xy, BL, NL, KL, viewmethod=viewmethod, PVx=PVx, PVy=PVy, PVxydict=PVxydict) PolyPC = polygons2PPC(polygons) ax = plt.gca() # number of polygon sides Pno = np.array([len(polyg) for polyg in polygons],dtype=int) print 'Pno = ', Pno print 'medPno = ', np.floor(np.median(Pno)) medPno = np.floor(np.median(Pno)) uIND = np.where(Pno == medPno-1)[0] mIND = np.where(Pno == medPno)[0] oIND = np.where(Pno == medPno+1)[0] loIND = np.where(Pno < medPno-1.5)[0] hiIND = np.where(Pno > medPno+1.5)[0] print ' uIND = ', uIND print ' oIND = ', oIND print ' loIND = ', loIND print ' hiIND = ', hiIND if len(uIND) > 0: PPCu = [PolyPC[i] for i in uIND] pu = PatchCollection(PPCu, color='b', alpha=0.5) ax.add_collection(pu) if len(mIND) > 0: PPCm = [PolyPC[i] for i in mIND] pm = PatchCollection(PPCm, color=[0.5, 0.5, 0.5], alpha=0.5) ax.add_collection(pm) if len(oIND) > 0: PPCo = [PolyPC[i] for i in oIND] po = PatchCollection(PPCo, color='r', alpha=0.5) ax.add_collection(po) if len(loIND) > 0: PPClo = [PolyPC[i] for i in loIND] plo = PatchCollection(PPClo, color='k', alpha=0.5) ax.add_collection(plo) if len(hiIND) > 0: PPChi = [PolyPC[i] for i in hiIND] phi = PatchCollection(PPChi, color='g', alpha=0.5) ax.add_collection(phi) if not bs == 'none': if len(np.nonzero(bs)) == 0: axcb = plt.colorbar(line_segments) axcb.set_label('Strain') axcb.set_clim(vmin=vmin, vmax=vmax) # set limits if isinstance(xlimv, tuple): ax.set_xlim(xlimv) elif xlimv: # print 'setting xlimv here...' ax.set_xlim(-xlimv,xlimv) else: # print 'setting auto limits...' # print 'max(xy[:,0]) = ', max(xy[:,0]) # print 'min(xy[:,0]) = ', min(xy[:,0]) extent = max(xy[:, 0]) - min(xy[:, 0]) # print 'extent = ', extent ax.set_xlim(min(xy[:, 0]) - extent * 0.1, max(xy[:, 0]) + extent * 0.1) if isinstance(ylimv, tuple): ax.set_ylim(ylimv) elif ylimv: ax.set_ylim(-ylimv, ylimv) else: extent = max(xy[:, 1]) - min(xy[:, 1]) ax.set_ylim(min(xy[:, 1]) - extent * 0.1, max(xy[:, 1]) + extent * 0.1) ax.axis('equal') if len(BLNNN) > 0: # Efficiently plot many lines in a single set of axes using LineCollection lines = [zip(xy[BLNNN[i, :], 0], xy[BLNNN[i, :], 1]) for i in range(len(BLNNN))] linesNNN = LineCollection(lines, # Make a sequence of x,y pairs linewidths=lw, # could iterate over list linestyles='dashed', color='blue', zorder=100) ax.add_collection(linesNNN) if len(NLNNN) > 0 and len(KLNNN) > 0: if (BL < 0).any(): print 'plotting periodic NNN...' for i in range(NP): todo = np.where(KLNNN[i, :] > 1e-12)[0] for ind in NLNNN[i, todo]: ax.arrow(xy[i, 0], xy[i, 1], (xy[ind, 0] - xy[i, 0]) * 0.98, (xy[ind, 1] - xy[i, 1]) * 0.98, head_width=0.1, head_length=0.2, fc='b', ec='b') else: # amount to offset clockwise nnn arrows for i in range(NP): todo = np.where(KLNNN[i, :] > 1e-12)[0] for ind in NLNNN[i, todo]: offset = (xy[ind, :]-xy[i, :])*0.5 ax.arrow(xy[i, 0] + offset[0], xy[i, 1]+offset[1], (xy[ind, 0]-xy[i, 0])*0.3, (xy[ind, 1]-xy[i, 1])*0.3, head_width=0.1, head_length=0.2, fc='b', ec='b') todo = np.where(KLNNN[i,:] < -1e-12)[0] for ind in NLNNN[i, todo]: offset = (xy[ind, :]-xy[i, :])*0.5 ax.arrow(xy[i,0]+offset[0], xy[i,1]+offset[1], (xy[ind,0]-xy[i,0])*0.3, (xy[ind,1]-xy[i,1])*0.3, head_width=0.1, head_length=0.2, fc='r', ec='r') if labelinds: for i in range(NP): ax.text(xy[i, 0]+0.1, xy[i, 1], str(i)) ax.set_title(title) print '...ending the display lattice function ' if axis_off: ax.axis('off') if close: plt.show() return None else: return ax ################################################### ################################################### if __name__ == '__main__': import argparse import make_lattice as makeL parser = argparse.ArgumentParser('Demonstrate some functions in lattice_elasticity') parser.add_argument('-all_demos', '--all_demos', help='Do all demos', action='store_true') parser.add_argument('-demo_dirgym', '--demo_dirgym', help='Directory gymnastics demo', action='store_true') parser.add_argument('-demo_kagome', '--demo_kagome', help='kagome lattice demo', action='store_true') parser.add_argument('-demo_cutbonds', '--demo_cutbonds', help='cutting bonds demo', action='store_true') parser.add_argument('-demo_boundary', '--demo_boundary', help='do demo for handling boundary', action='store_true') parser.add_argument('-demo_coordination', '--demo_coordination', help='do demo for tuning coordination', action='store_true') parser.add_argument('-demo_centroid', '--demo_centroid', help='do demo for decorating lattice as centroid', action='store_true') parser.add_argument('-demo_polygons', '--demo_polygons', help='do demo for extracting and coloring polygons', action='store_true') parser.add_argument('-demo_haldane', '--demo_haldane', help='do demo for computing haldane model', action='store_true') parser.add_argument('-demo_unique_rows', '--demo_unique_rows', help='do demo for getting unique rows in a numpy array', action='store_true') parser.add_argument('-demo_chern', '--demo_chern', help='do demo calculating chern number of disordered gyro lattice', action='store_true') parser.add_argument('-demo_BL', '--demo_BL', help='Do demo handling bond list', action='store_true') parser.add_argument('-demo_dislocation', '--demo_dislocation', help='do demo inputting a dislocation in a lattice', action='store_true') parser.add_argument('-demo_triangulation', '--demo_triangulation', help='do demo with triangulating points', action='store_true') args = parser.parse_args() if args.all_demos: demo_dirgym = True demo_kagome = True demo_cutbonds = True demo_boundary = True demo_coordination = True demo_centroid = True demo_polygons = True demo_haldane = True demo_unique_rows = True demo_chern = True demo_BL = True demo_dislocation = True demo_triangulation = True else: demo_dirgym = args.demo_dirgym demo_kagome = args.demo_kagome demo_cutbonds = args.demo_cutbonds demo_boundary = args.demo_boundary demo_coordination = args.demo_coordination demo_centroid = args.demo_centroid demo_polygons = args.demo_polygons demo_haldane = args.demo_haldane demo_unique_rows = args.demo_unique_rows demo_chern = args.demo_chern demo_BL = args.demo_BL demo_dislocation = args.demo_dislocation demo_triangulation = args.demo_triangulation if demo_boundary: #xy = np.random.randn(17, 2) #print 'xy = ', xy xy = np.array([[ 0.1512926, -0.37403114], [ 0.66390186, -1.21998997], [ 1.17480764, 0.27231353], [-3.81844152, -0.30166257], [ 0.93878695, 0.87060133], [ 0.53435557, 1.30176558], [ 1.31181627, -0.91816195], [ 1.05345549, -0.01980385], [-0.91245103, -0.36542935], [-0.80641536, 1.06187272], [-0.3041718 , 1.05616468], [-0.31971738, -1.20429765], [-0.52918824, -0.1159254 ], [-0.03666739, -0.85863593], [-0.108136 , -1.4940799 ], [ 2.56295229, -1.92179549], [ 0.16828214, -0.63639772]]) # Show what just a simple triangulation would do: xy, NL,KL,BL,BM = delaunay_lattice_from_pts(xy,trimbound=False, max_bond_length=1.4, thres=10.0, check=True) TRI = BL2TRI(BL,xy) fig = plt.figure() plt.gca().set_aspect('equal') plt.triplot(xy[:,0], xy[:,1], TRI, 'bo-') plt.title('Resulting triangulation from delaunay_lattice_from_pts') plt.show() # Show how to kill unnatural boundaries (trimbound) xy, NL,KL,BL,BM = delaunay_lattice_from_pts(xy,trimbound=True, max_bond_length=1.4, thres=10.0, check=True) TRI = BL2TRI(BL,xy) fig = plt.figure() plt.gca().set_aspect('equal') plt.triplot(xy[:,0], xy[:,1], TRI, 'go-') plt.show() # Extract the new boundary of a bonded point set print ' demo : extract boundary...' boundary = extract_boundary(xy,NL,KL, BL, check=True) fig = plt.figure() plt.clf() ax = plt.axes() ax.set_aspect('equal') ax.plot(xy[:,0],xy[:,1],'b.') #lines = [zip(xy[boundary[i],0], xy[boundary[i],1]) for i in range(len(BL))] ax.plot(xy[boundary,0],xy[boundary,1],'b-') ax.plot(xy[[boundary[-1],boundary[0]],0],xy[[boundary[-1],boundary[0]],1],'b-') plt.title('Resulting boundary from extract_boundary()') plt.show() # Make BL to make TRI BL = NL2BL(NL,KL) TRI = BL2TRI(BL,xy) # # # Show how to identify the boundary triangles # btri = boundary_triangles(TRI,boundary) # print 'Identified the boundary triangles as:', TRI[btri] # zfaces = np.zeros(len(TRI),dtype=float) # zfaces[btri] = 1. # # Color the boundary triangles in a plot # plt.figure() # plt.gca().set_aspect('equal') # plt.tripcolor(xy[:,0], xy[:,1], TRI, facecolors=zfaces, edgecolors='k') # plt.colorbar() # for i in range(len(xy)): # plt.text(xy[i,0]-0.2, xy[i,1], str(i)) # plt.title('tripcolor() showing boundary tris') # plt.show() # # display_lattice_2D(xy,BL,title='',close=False) # for i in range(len(xy)): # plt.text(xy[i,0]+0.05,xy[i,1],str(i)) # plt.show()