diff --git a/lattices2d.py b/lattices2d.py deleted file mode 100644 index 5ea07cb..0000000 --- a/lattices2d.py +++ /dev/null @@ -1,247 +0,0 @@ -import numpy as np -from enum import Enum - -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(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 - - -rot90_ = np.array([[0,1],[-1,0]]) -def reciprocalBasis(a1, a2): - a1, a2 = reduceBasisSingle(a1,a2) # this can be replaced with the vector version of reduceBasis when it is made - prefac = 2*np.pi/np.sum(np.tensordot(a1, rot90_, axes=[-1,0]) * a2, axis=-1) - b1 = np.tensordot(rot90_, a2, axes=[-1,-1]) * prefac - b2 = np.tensordot(rot90_, a1, axes=[-1,-1]) * prefac - return (b1, b2) - - -# TODO fill it with "points from reciprocal space" instead -def filledWS2(b1,b2, density=10, scale=1.): - b1, b2 = reduceBasisSingle(b1,b2) - b1r, b2r = reciprocalBasis(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 - - - -""" -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ů). - -""" diff --git a/qpms/lattices2d.py b/qpms/lattices2d.py index 670b763..5ea07cb 100644 --- a/qpms/lattices2d.py +++ b/qpms/lattices2d.py @@ -151,6 +151,14 @@ def generateLattice(b1, b2, maxlayer=5, include_origin=False, order='leaves'): 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,): """