""" These functions are mostly deprecated by the C counterparts from lattices.h. This file is still kept for reference, but might be removed in the future. """ import numpy as np from enum import Enum from math import floor nx = None class LatticeType(Enum): """ All the five Bravais lattices in 2D """ OBLIQUE=1 RECTANGULAR=2 SQUARE=4 RHOMBIC=5 EQUILATERAL_TRIANGULAR=3 RIGHT_ISOSCELES=SQUARE PARALLELOGRAMMIC=OBLIQUE CENTERED_RHOMBIC=RECTANGULAR RIGHT_TRIANGULAR=RECTANGULAR CENTERED_RECTANGULAR=RHOMBIC ISOSCELE_TRIANGULAR=RHOMBIC RIGHT_ISOSCELE_TRIANGULAR=SQUARE HEXAGONAL=EQUILATERAL_TRIANGULAR def reduceBasisSingle(b1, b2): """ Lagrange-Gauss reduction of a 2D basis. cf. https://www.math.auckland.ac.nz/~sgal018/crypto-book/ch17.pdf inputs and outputs are (2,)-shaped numpy arrays The output shall satisfy |b1| <= |b2| <= |b2 - b1| TODO doc TODO perhaps have the (on-demand?) guarantee of obtuse angle between b1, b2? TODO possibility of returning the (in-order, no-obtuse angles) b as well? """ b1 = np.array(b1) b2 = np.array(b2) if b1.shape != (2,) or b2.shape != (2,): raise ValueError('Shape of b1 and b2 must be (2,)') B1 = np.sum(b1 * b1, axis=-1, keepdims=True) mu = np.sum(b1 * b2, axis=-1, keepdims=True) / B1 b2 = b2 - np.rint(mu) * b1 B2 = np.sum(b2 * b2, axis=-1, keepdims=True) while(np.any(B2 < B1)): b2t = b1 b1 = b2 b2 = b2t B1 = B2 mu = np.sum(b1 * b2, axis=-1, keepdims=True) / B1 b2 = b2 - np.rint(mu) * b1 B2 = np.sum(b2*b2, axis=-1, keepdims=True) return np.array((b1,b2)) def shortestBase3(b1, b2): ''' returns the "ordered shortest triple" of base vectors (each pair from the triple is a base) and there may not be obtuse angle between b1, b2 and between b2, b3 ''' b1, b2 = reduceBasisSingle(b1,b2) if is_obtuse(b1, b2, tolerance=0): b3 = b2 b2 = b2 + b1 else: b3 = b2 - b1 return (b1, b2, b3) def shortestBase46(b1, b2, tolerance=1e-13): b1, b2 = reduceBasisSingle(b1,b2) b1s = np.sum(b1 ** 2) b2s = np.sum(b2 ** 2) b3 = b2 - b1 b3s = np.sum(b3 ** 2) eps = tolerance * (b2s + b1s) if abs(b3s - b2s - b1s) < eps: return(b1, b2, -b1, -b2) else: if b3s - b2s - b1s > eps: #obtuse b3 = b2 b2 = b2 + b1 return (b1, b2, b3, -b1, -b2, -b3) def is_obtuse(b1, b2, tolerance=1e-13): b1s = np.sum(b1 ** 2) b2s = np.sum(b2 ** 2) b3 = b2 - b1 b3s = np.sum(b3 ** 2) eps = tolerance * (b2s + b1s) return (b3s - b2s - b1s > eps) def classifyLatticeSingle(b1, b2, tolerance=1e-13): """ Given two basis vectors, returns 2D Bravais lattice type. Tolerance is relative. TODO doc """ b1, b2 = reduceBasisSingle(b1, b2) b1s = np.sum(b1 ** 2) b2s = np.sum(b2 ** 2) b3 = b2 - b1 b3s = np.sum(b3 ** 2) eps = tolerance * (b2s + b1s) # Avoid obtuse angle between b1 and b2. TODO This should be yet thoroughly tested. # TODO use is_obtuse here? if b3s - b2s - b1s > eps: b3 = b2 b2 = b2 + b1 # N. B. now the assumption |b3| >= |b2| is no longer valid #b3 = b2 - b1 b2s = np.sum(b2 ** 2) b3s = np.sum(b3 ** 2) if abs(b2s - b1s) < eps or abs(b2s - b3s) < eps: # isoscele if abs(b3s - b1s) < eps: return LatticeType.EQUILATERAL_TRIANGULAR elif abs(b3s - 2 * b1s) < eps: return LatticeType.SQUARE else: return LatticeType.RHOMBIC elif abs(b3s - b2s - b1s) < eps: return LatticeType.RECTANGULAR else: return LatticeType.OBLIQUE def range2D(maxN, mini=1, minj=0, minN = 0): """ "Triangle indices" Generates pairs of non-negative integer indices (i, j) such that minN ≤ i + j ≤ maxN, i ≥ mini, j ≥ minj. TODO doc and possibly different orderings """ for maxn in range(min(mini, minj, minN), floor(maxN+1)): # i + j == maxn for i in range(mini, maxn + 1): yield (i, maxn - i) def generateLattice(b1, b2, maxlayer=5, include_origin=False, order='leaves'): bvs = shortestBase46(b1, b2) cc = len(bvs) # "corner count" if order == 'leaves': indices = np.array(list(range2D(maxlayer))) ia = indices[:,0] ib = indices[:,1] cc = len(bvs) # 4 for square/rec, leaves = list() if include_origin: leaves.append(np.array([[0,0]])) for c in range(cc): ba = bvs[c] bb = bvs[(c+1)%cc] leaves.append(ia[:,nx]*ba + ib[:,nx]*bb) return np.concatenate(leaves) else: raise ValueError('Lattice point order not implemented: ', order) def generateLatticeDisk(b1, b2, r, include_origin=False, order='leaves'): b1, b2 = reduceBasisSingle(b1,b2) blen = np.linalg.norm(b1, ord=2) maxlayer = 2*r/blen # FIXME kanon na vrabce? Nestačí odmocnina ze 2? points = generateLattice(b1,b2, maxlayer=maxlayer, include_origin=include_origin, order=order) mask = (np.linalg.norm(points, axis=-1, ord=2) <= r) return points[mask] def cellCornersWS(b1, b2,): """ Given basis vectors, returns the corners of the Wigner-Seitz unit cell (w1, w2, -w1, w2) for rectangular and square lattice or (w1, w2, w3, -w1, -w2, -w3) otherwise """ def solveWS(v1, v2): v1x = v1[0] v1y = v1[1] v2x = v2[0] v2y = v2[1] lsm = ((-v1y, v2y), (v1x, -v2x)) rs = ((v1x-v2x)/2, (v1y - v2y)/2) t = np.linalg.solve(lsm, rs) return np.array(v1)/2 + t[0]*np.array((v1y, -v1x)) b1, b2 = reduceBasisSingle(b1, b2) latticeType = classifyLatticeSingle(b1, b2) if latticeType is LatticeType.RECTANGULAR or latticeType is LatticeType.SQUARE: return np.array( ( (+b1+b2), (+b2-b1), (-b1-b2), (-b2+b1), )) / 2 else: bvs = shortestBase46(b1,b2,tolerance=0) return np.array([solveWS(bvs[i], bvs[(i+1)%6]) for i in range(6)]) def cutWS(points, b1, b2, scale=1., tolerance=1e-13): """ From given points, return only those that are inside (or on the edge of) the Wigner-Seitz cell of a (scale*b1, scale*b2)-based lattice. """ # TODO check input dimensions? bvs = shortestBase46(b1, b2) points = np.array(points) for b in bvs: mask = (np.tensordot(points, b, axes=(-1, 0)) <= (scale * (1+tolerance) / 2) *np.linalg.norm(b, ord=2)**2 ) points = points[mask] return points def filledWS(b1, b2, density=10, scale=1.): """ TODO doc TODO more intelligent generation, anisotropy balancing etc. """ points = generateLattice(b1,b2,maxlayer=density*scale, include_origin=True) points = cutWS(points/density, np.array(b1)*scale, np.array(b2)*scale) return points def reciprocalBasis1(*pargs): a = reduceBasisSingle(*pargs) return np.linalg.inv(a).T def reciprocalBasis2pi(*pargs): return 2*np.pi*reciprocalBasis1(*pargs) # TODO fill it with "points from reciprocal space" instead def filledWS2(b1,b2, density=10, scale=1.): b1, b2 = reduceBasisSingle(b1,b2) b1r, b2r = reciprocalBasis2pi(b1,b2) b1l = np.linalg.norm(b1, ord=2) b2l = np.linalg.norm(b2, ord=2) b1rl = np.linalg.norm(b1r, ord=2) b2rl = np.linalg.norm(b2r, ord=2) # Black magick. Think later.™ Really. FIXME sicher_ratio = np.maximum(b1rl/b2rl, b2rl/b1rl) * np.maximum(b1l/b2l, b2l/b1l) # This really has to be adjusted points = generateLattice(b1r,b2r,maxlayer=density*scale*sicher_ratio, include_origin=True) points = cutWS(points*b1l/b1rl/density, b1*scale, b2*scale) return points def change_basis(srcbasis, destbasis, srccoords, srccoordsaxis=-1, lattice=True): srcbasis = np.array(srcbasis) destbasis = np.array(destbasis) trmatrix = np.dot(np.linalg.inv(np.transpose(destbasis)), np.transpose(srcbasis)) if lattice: # if srcbasis and destbasis are two bases of the same lattice, its elements are ints otrmatrix = trmatrix trmatrix = np.round(trmatrix) if not np.all(np.isclose(trmatrix, otrmatrix)): raise ValueError("Given srcbasis and destbasis are not bases" "of the same lattice", srcbasis, destbasis, trmatrix-otrmatrix) destcoords = np.tensordot(srccoords, trmatrix, axes=(srccoordsaxis, -1)) return destcoords """ TODO ==== - DOC!!!!! - (nehoří) výhledově pořešit problém „hodně anisotropních“ mřížek (tj. kompensovat rozdílné délky základních vektorů). """