1import hashlib 2 3import numpy as np 4 5from ase.units import Hartree, Bohr 6 7 8def L_to_lm(L): 9 """Convert L index to (l, m) index.""" 10 l = int(np.sqrt(L)) 11 m = L - l**2 - l 12 return l, m 13 14 15def lm_to_L(l, m): 16 """Convert (l, m) index to L index.""" 17 return l**2 + l + m 18 19 20def split_formula(formula): 21 """Count elements in a chemical formula. 22 23 E.g. split_formula('C2H3Mg') -> ['C', 'C', 'H', 'H', 'H', 'Mg'] 24 """ 25 res = [] 26 for c in formula: 27 if c.isupper(): 28 res.append(c) 29 elif c.islower(): 30 res[-1] += c 31 else: 32 res.extend([res[-1]] * (eval(c) - 1)) 33 return res 34 35 36def construct_reciprocal(gd, q_c=None): 37 """Construct the reciprocal lattice from ``GridDescriptor`` instance. 38 39 The generated reciprocal lattice has lattice vectors corresponding to the 40 real-space lattice defined in the input grid. Note that it is the squared 41 length of the reciprocal lattice vectors that are returned. 42 43 The ordering of the reciprocal lattice agrees with the one typically used 44 in fft algorithms, i.e. positive k-values followed by negative. 45 46 Note that the G=(0,0,0) entry is set to one instead of zero. This 47 bit should probably be moved somewhere else ... 48 49 Parameters 50 ---------- 51 q_c: ndarray 52 Offset for the reciprocal lattice vectors (in scaled coordinates of the 53 reciprocal lattice vectors, i.e. array with index ``c``). When 54 specified, the returned array contains the values of (q+G)^2 where G 55 denotes the reciprocal lattice vectors. 56 57 """ 58 59 assert gd.pbc_c.all(), 'Works only with periodic boundary conditions!' 60 61 q_c = np.zeros((3, 1), dtype=float) if q_c is None else q_c.reshape((3, 1)) 62 63 # Calculate reciprocal lattice vectors 64 N_c1 = gd.N_c[:, None] 65 i_cq = np.indices(gd.n_c, dtype=float).reshape((3, -1)) # offsets.... 66 i_cq += gd.beg_c[:, None] 67 i_cq += N_c1 // 2 68 i_cq %= N_c1 69 i_cq -= N_c1 // 2 70 71 i_cq += q_c 72 73 # Convert from scaled to absolute coordinates 74 B_vc = 2.0 * np.pi * gd.icell_cv.T 75 k_vq = np.dot(B_vc, i_cq) 76 77 k_vq *= k_vq 78 k2_Q = k_vq.sum(axis=0).reshape(gd.n_c) 79 80 # Avoid future divide-by-zero by setting k2_Q[G=(0,0,0)] = 1.0 if needed 81 if k2_Q[0, 0, 0] < 1e-10: 82 k2_Q[0, 0, 0] = 1.0 # Only make sense iff 83 assert gd.comm.rank == 0 # * on rank 0 (G=(0,0,0) is only there) 84 assert abs(q_c).sum() < 1e-8 # * q_c is (almost) zero 85 86 assert k2_Q.min() > 0.0 # Now there should be no zero left 87 88 # Determine N^3 89 # 90 # Why do we need to calculate and return this? The caller already 91 # has access to gd and thus knows how many points there are. 92 N3 = gd.n_c[0] * gd.n_c[1] * gd.n_c[2] 93 return k2_Q, N3 94 95 96def coordinates(gd, origin=None, tiny=1e-12): 97 """Constructs and returns matrices containing cartesian coordinates, 98 and the square of the distance from the origin. 99 100 The origin can be given explicitely (in Bohr units, not Anstroms). 101 Otherwise the origin is placed in the center of the box described 102 by the given grid-descriptor 'gd'. 103 """ 104 105 if origin is None: 106 origin = 0.5 * gd.cell_cv.sum(0) 107 r0_v = np.array(origin) 108 109 r_vG = gd.get_grid_point_distance_vectors(r0_v) 110 r2_G = np.sum(r_vG**2, axis=0) 111 # Remove singularity at origin and replace with small number 112 r2_G = np.where(r2_G < tiny, tiny, r2_G) 113 114 # Return r^2 matrix 115 return r_vG, r2_G 116 117 118def pick(a_ix, i): 119 """Take integer index of a, or a linear combination of the elements of a""" 120 if isinstance(i, int): 121 return a_ix[i] 122 shape = a_ix.shape 123 a_x = np.dot(i, a_ix[:].reshape(shape[0], -1)) 124 return a_x.reshape(shape[1:]) 125 126 127def dagger(a, copy=True): 128 """Return Hermitian conjugate of input 129 130 If copy is False, the original array might be overwritten. This is faster, 131 but use with care. 132 """ 133 if copy: 134 return np.conj(a.T) 135 else: 136 a = a.T 137 if a.dtype == complex: 138 a.imag *= -1 139 return a 140 141 142def project(a, b): 143 """Return the projection of b onto a.""" 144 return a * (np.dot(a.conj(), b) / np.linalg.norm(a)) 145 146 147def normalize(U): 148 """Normalize columns of U.""" 149 for col in U.T: 150 col /= np.linalg.norm(col) 151 152 153def get_matrix_index(ind1, ind2=None): 154 if ind2 is None: 155 dim1 = len(ind1) 156 return np.resize(ind1, (dim1, dim1)) 157 else: 158 dim1 = len(ind1) 159 dim2 = len(ind2) 160 return np.resize(ind1, (dim2, dim1)).T, np.resize(ind2, (dim1, dim2)) 161 162 163def gram_schmidt(U): 164 """Orthonormalize columns of U according to the Gram-Schmidt procedure.""" 165 for i, col in enumerate(U.T): 166 for col2 in U.T[:i]: 167 col -= col2 * np.dot(col2.conj(), col) 168 col /= np.linalg.norm(col) 169 170 171def lowdin(U, S=None): 172 """Orthonormalize columns of U according to the Lowdin procedure. 173 174 If the overlap matrix is know, it can be specified in S. 175 """ 176 if S is None: 177 S = np.dot(dagger(U), U) 178 eig, rot = np.linalg.eigh(S) 179 rot = np.dot(rot / np.sqrt(eig), dagger(rot)) 180 U[:] = np.dot(U, rot) 181 182 183def lowdin_svd(U): 184 """Orthogonalize according to the Lowdin procedure 185 using singular value decomposition. 186 187 U is an N x M matrix containing M vectors as its columns. 188 """ 189 Z, D, V = np.linalg.svd(U, full_matrices=0) 190 return np.dot(Z, V) 191 192 193def symmetrize(matrix): 194 """Symmetrize input matrix.""" 195 np.add(dagger(matrix), matrix, matrix) 196 np.multiply(.5, matrix, matrix) 197 return matrix 198 199 200def tri2full(H_nn, UL='L', map=np.conj): 201 """Fill in values of hermitian or symmetric matrix. 202 203 Fill values in lower or upper triangle of H_nn based on the opposite 204 triangle, such that the resulting matrix is symmetric/hermitian. 205 206 UL='U' will copy (conjugated) values from upper triangle into the 207 lower triangle. 208 209 UL='L' will copy (conjugated) values from lower triangle into the 210 upper triangle. 211 212 The map parameter can be used to specify a different operation than 213 conjugation, which should work on 1D arrays. Example:: 214 215 def antihermitian(src, dst): 216 np.conj(-src, dst) 217 218 tri2full(H_nn, map=antihermitian) 219 220 """ 221 N, tmp = H_nn.shape 222 assert N == tmp, 'Matrix must be square' 223 if UL != 'L': 224 H_nn = H_nn.T 225 226 for n in range(N - 1): 227 map(H_nn[n + 1:, n], H_nn[n, n + 1:]) 228 229 230def apply_subspace_mask(H_nn, f_n): 231 """Uncouple occupied and unoccupied subspaces. 232 233 This method forces the H_nn matrix into a block-diagonal form 234 in the occupied and unoccupied states respectively. 235 """ 236 occ = 0 237 nbands = len(f_n) 238 while occ < nbands and f_n[occ] > 1e-3: 239 occ += 1 240 H_nn[occ:, :occ] = H_nn[:occ, occ:] = 0 241 242 243def cutoff2gridspacing(E): 244 """Convert planewave energy cutoff to a real-space gridspacing.""" 245 return np.pi / np.sqrt(2 * E / Hartree) * Bohr 246 247 248def gridspacing2cutoff(h): 249 """Convert real-space gridspacing to planewave energy cutoff.""" 250 # In Hartree units, E = k^2 / 2, where k_max is approx. given by pi / h 251 # See PRB, Vol 54, 14362 (1996) 252 return 0.5 * (np.pi * Bohr / h)**2 * Hartree 253 254 255def tridiag(a, b, c, r, u): 256 """Solve linear system with tridiagonal coefficient matrix. 257 258 a is the lower band, b is the diagonal, c is the upper band, and 259 r is the right hand side. 260 The solution is returned in u. 261 262 263 [b1 c1 0 ... ] [u1] [r1] 264 [a1 b2 c2 0 ... ] [ :] [ :] 265 [ 0 a2 b3 c3 0 ... ] [ ] = [ ] 266 [ ] [ ] [ ] 267 [ ... 0 an-2 bn-1 cn-1] [ :] [ :] 268 [ ... 0 an-1 bn ] [un] [rn] 269 """ 270 n = len(b) 271 tmp = np.zeros(n - 1) # necessary temporary array 272 if b[0] == 0: 273 raise RuntimeError('System is effectively order N-1') 274 275 beta = b[0] 276 u[0] = r[0] / beta 277 for i in range(1, n): 278 # Decompose and forward substitution 279 tmp[i - 1] = c[i - 1] / beta 280 beta = b[i] - a[i - 1] * tmp[i - 1] 281 if beta == 0: 282 raise RuntimeError('Method failure') 283 u[i] = (r[i] - a[i - 1] * u[i - 1]) / beta 284 285 for i in range(n - 1, 0, -1): 286 # Backward substitution 287 u[i - 1] -= tmp[i - 1] * u[i] 288 289 290def signtrim(data, decimals=None): 291 """Trim off the sign of potential zeros, usually occurring after round. 292 293 data is the ndarray holding NumPy data to round and trim. 294 decimals is an integer specifying how many decimals to round to. 295 """ 296 if decimals is not None: 297 data = data.round(decimals) # np.round is buggy because -0 != 0 298 299 shape = data.shape 300 data = data.reshape(-1) 301 302 if data.dtype == complex: 303 i = np.argwhere(np.sign(data.real) == 0).ravel() 304 j = np.argwhere(np.sign(data.imag) == 0).ravel() 305 data.real[i] = 0 306 data.imag[j] = 0 307 else: 308 i = np.argwhere(np.sign(data) == 0).ravel() 309 data[i] = 0 310 311 return data.reshape(shape) 312 313 314def md5_array(data, numeric=False): 315 """Create MD5 hex digest from NumPy array. 316 317 Optionally, will cast the 128 bit hexadecimal hash to a 64 bit integer. 318 319 Warning: For floating point types, only bitwise identical data will 320 produce matching MD5 fingerprints, so do not attempt to match sets 321 of nearly identical data by rounding off beforehand. 322 323 Example: 324 325 >>> data = np.linspace(0, np.pi, 1000000) 326 >>> eps = 1e-6 327 >>> a = md5_array(data.round(3)) 328 >>> b = md5_array((data + eps).round(3)) 329 >>> a == b 330 False 331 332 This is due to the inexact nature of the floating point representation. 333 """ 334 335 if not isinstance(data, np.ndarray): 336 data = np.asarray(data) 337 338 # Only accepts float,complex,int,bool,... 339 if (not np.issubdtype(data.dtype, np.number) and 340 data.dtype not in [bool, np.bool, np.bool_]): 341 raise TypeError('MD5 hex digest only accepts numeric/boolean arrays.') 342 343 datahash = hashlib.md5(data.tobytes()) 344 345 if numeric: 346 def xor(a, b): 347 return chr(ord(a) ^ ord(b)) # bitwise xor on 2 bytes -> 1 byte 348 349 sbuf128 = datahash.digest() 350 sbuf64 = ''.join([xor(a, b) 351 for a, b in zip(sbuf128[::2], sbuf128[1::2])]) 352 return np.fromstring(sbuf64, np.int64).item() 353 else: 354 return datahash.hexdigest() 355 356 357def split_nodes(length, parrank, parsize): 358 """Split length over nodes. 359 360 Divide length into parsize even sized chunks, and return the start/end 361 indices of the parrank'th chunk. 362 """ 363 if parsize == 1: 364 return 0, length 365 pernode = int(round(length / float(parsize))) 366 if parrank == parsize - 1: 367 return parrank * pernode, length 368 return parrank * pernode, (parrank + 1) * pernode 369 370 371class Spline: 372 def __init__(self, xi, yi, leftderiv=None, rightderiv=None): 373 """Cubic spline approximation class. 374 375 xi, yi specifies the known data points. 376 377 leftderiv and rightderiv specifies the first derivative on the 378 boundaries. If set to None, the second derivative is set to zero. 379 380 Example usage:: 381 382 >>> xi = np.arange(.1, 5, .5) # known data points 383 >>> yi = np.cos(xi) # known data points 384 >>> sp = Spline(xi, yi) # make spline 385 >>> x = np.arange(-.5, 5.5, .05) # points to interpolate to 386 >>> y = sp(x) # get spline value on an entire list 387 >>> y2 = sp(4) # get spline value at a single point 388 389 Based on 'Numerical recipes in c' 390 """ 391 self.xy = (xi, yi) 392 N = len(xi) 393 self.ypp = u = np.zeros(N) # the second derivatives y'' 394 tmp = np.zeros(N - 1) 395 396 # Set left boundary condition 397 if leftderiv is None: # natural spline - second derivative is zero 398 tmp[0] = u[0] = 0.0 399 else: # clamped spline - first derivative is fixed 400 tmp[0] = 3 / (xi[1] - xi[0]) * ( 401 (yi[1] - yi[0]) / (xi[1] - xi[0]) - leftderiv) 402 u[0] = -.5 403 404 for i in range(1, N - 1): 405 sig = (xi[i] - xi[i - 1]) / (xi[i + 1] - xi[i - 1]) 406 p = sig * u[i - 1] + 2 407 u[i] = (sig - 1) / p 408 tmp[i] = (yi[i + 1] - yi[i]) / (xi[i + 1] - xi[i]) - \ 409 (yi[i] - yi[i - 1]) / (xi[i] - xi[i - 1]) 410 tmp[i] = (6 * tmp[i] / 411 (xi[i + 1] - xi[i - 1]) - sig * tmp[i - 1]) / p 412 413 # Set right boundary condition 414 if rightderiv is None: # natural spline - second derivative is zero 415 qn = tmpn = 0.0 416 else: # clamped spline - first derivative is fixed 417 qn = .5 418 tmpn = 3 / (xi[N - 1] - xi[N - 2]) * ( 419 rightderiv - (yi[N - 1] - yi[N - 2]) / (xi[N - 1] - xi[N - 2])) 420 421 u[N - 1] = (tmpn - qn * tmp[N - 2]) / (qn * u[N - 1] + 1) 422 for k in range(N - 2, -1, -1): # backsubstitution step 423 u[k] = u[k] * u[k + 1] + tmp[k] 424 425 def __call__(self, x): 426 """Evaluate spline for each point in input argument. 427 428 The value in point x[i-1] < x <= x[i] is determined by:: 429 430 '' '' 431 y(x) = a y + b y + c y + d y 432 i-1 i i-1 i 433 434 """ 435 x = np.array(x, float) 436 if x.ndim == 0: 437 x.shape = (1,) 438 y = np.zeros_like(x) 439 xi, yi = self.xy 440 441 i = None 442 for j, xval in enumerate(x): 443 i = self.locate(xval, i) 444 h = xi[i] - xi[i - 1] 445 a = (xi[i] - xval) / h 446 b = 1. - a 447 c = (a**3 - a) * h**2 / 6. 448 d = (b**3 - b) * h**2 / 6. 449 y[j] = (a * yi[i - 1] + b * yi[i] + 450 c * self.ypp[i - 1] + d * self.ypp[i]) 451 return y 452 453 def locate(self, x, guess=None): 454 """return i such that x[i-1] < x <= xi[i] 455 456 1 or len(xi) - 1 is returned if x is outside list range. 457 """ 458 xi = self.xy[0] 459 if x <= xi[0]: 460 return 1 461 elif x > xi[-1]: 462 return len(xi) - 1 463 elif guess and xi[guess - 1] < x <= xi[guess]: 464 return guess 465 else: 466 return np.searchsorted(xi, x) 467