7 class LatticeType(Enum
):
9 All the five Bravais lattices in 2D
15 EQUILATERAL_TRIANGULAR
=3
16 RIGHT_ISOSCELES
=SQUARE
17 PARALLELOGRAMMIC
=OBLIQUE
18 CENTERED_RHOMBIC
=RECTANGULAR
19 RIGHT_TRIANGULAR
=RECTANGULAR
20 CENTERED_RECTANGULAR
=RHOMBIC
21 ISOSCELE_TRIANGULAR
=RHOMBIC
22 RIGHT_ISOSCELE_TRIANGULAR
=SQUARE
23 HEXAGONAL
=EQUILATERAL_TRIANGULAR
25 def reduceBasisSingle(b1
, b2
):
27 Lagrange-Gauss reduction of a 2D basis.
28 cf. https://www.math.auckland.ac.nz/~sgal018/crypto-book/ch17.pdf
29 inputs and outputs are (2,)-shaped numpy arrays
30 The output shall satisfy |b1| <= |b2| <= |b2 - b1|
33 TODO perhaps have the (on-demand?) guarantee of obtuse angle between b1, b2?
34 TODO possibility of returning the (in-order, no-obtuse angles) b as well?
38 if b1
.shape
!= (2,) or b2
.shape
!= (2,):
39 raise ValueError('Shape of b1 and b2 must be (2,)')
40 B1
= np
.sum(b1
* b1
, axis
=-1, keepdims
=True)
41 mu
= np
.sum(b1
* b2
, axis
=-1, keepdims
=True) / B1
42 b2
= b2
- np
.rint(mu
) * b1
43 B2
= np
.sum(b2
* b2
, axis
=-1, keepdims
=True)
44 while(np
.any(B2
< B1
)):
49 mu
= np
.sum(b1
* b2
, axis
=-1, keepdims
=True) / B1
50 b2
= b2
- np
.rint(mu
) * b1
51 B2
= np
.sum(b2
*b2
, axis
=-1, keepdims
=True)
52 return np
.array((b1
,b2
))
54 def shortestBase3(b1
, b2
):
56 returns the "ordered shortest triple" of base vectors (each pair from
57 the triple is a base) and there may not be obtuse angle between b1, b2
60 b1
, b2
= reduceBasisSingle(b1
,b2
)
61 if is_obtuse(b1
, b2
, tolerance
=0):
68 def shortestBase46(b1
, b2
, tolerance
=1e-13):
69 b1
, b2
= reduceBasisSingle(b1
,b2
)
74 eps
= tolerance
* (b2s
+ b1s
)
75 if abs(b3s
- b2s
- b1s
) < eps
:
76 return(b1
, b2
, -b1
, -b2
)
78 if b3s
- b2s
- b1s
> eps
: #obtuse
81 return (b1
, b2
, b3
, -b1
, -b2
, -b3
)
84 def is_obtuse(b1
, b2
, tolerance
=1e-13):
89 eps
= tolerance
* (b2s
+ b1s
)
90 return (b3s
- b2s
- b1s
> eps
)
92 def classifyLatticeSingle(b1
, b2
, tolerance
=1e-13):
94 Given two basis vectors, returns 2D Bravais lattice type.
95 Tolerance is relative.
98 b1
, b2
= reduceBasisSingle(b1
, b2
)
100 b2s
= np
.sum(b2
** 2)
102 b3s
= np
.sum(b3
** 2)
103 eps
= tolerance
* (b2s
+ b1s
)
104 # Avoid obtuse angle between b1 and b2. TODO This should be yet thoroughly tested.
105 # TODO use is_obtuse here?
106 if b3s
- b2s
- b1s
> eps
:
109 # N. B. now the assumption |b3| >= |b2| is no longer valid
111 b2s
= np
.sum(b2
** 2)
112 b3s
= np
.sum(b3
** 2)
113 if abs(b2s
- b1s
) < eps
or abs(b2s
- b3s
) < eps
: # isoscele
114 if abs(b3s
- b1s
) < eps
:
115 return LatticeType
.EQUILATERAL_TRIANGULAR
116 elif abs(b3s
- 2 * b1s
) < eps
:
117 return LatticeType
.SQUARE
119 return LatticeType
.RHOMBIC
120 elif abs(b3s
- b2s
- b1s
) < eps
:
121 return LatticeType
.RECTANGULAR
123 return LatticeType
.OBLIQUE
125 def range2D(maxN
, mini
=1, minj
=0, minN
= 0):
128 Generates pairs of non-negative integer indices (i, j) such that
129 minN ≤ i + j ≤ maxN, i ≥ mini, j ≥ minj.
130 TODO doc and possibly different orderings
132 for maxn
in range(min(mini
, minj
, minN
), floor(maxN
+1)): # i + j == maxn
133 for i
in range(mini
, maxn
+ 1):
137 def generateLattice(b1
, b2
, maxlayer
=5, include_origin
=False, order
='leaves'):
138 bvs
= shortestBase46(b1
, b2
)
139 cc
= len(bvs
) # "corner count"
141 if order
== 'leaves':
142 indices
= np
.array(list(range2D(maxlayer
)))
145 cc
= len(bvs
) # 4 for square/rec,
147 if include_origin
: leaves
.append(np
.array([[0,0]]))
151 leaves
.append(ia
[:,nx
]*ba
+ ib
[:,nx
]*bb
)
152 return np
.concatenate(leaves
)
154 raise ValueError('Lattice point order not implemented: ', order
)
156 def generateLatticeDisk(b1
, b2
, r
, include_origin
=False, order
='leaves'):
157 b1
, b2
= reduceBasisSingle(b1
,b2
)
158 blen
= np
.linalg
.norm(b1
, ord=2)
159 maxlayer
= 2*r
/blen
# FIXME kanon na vrabce? Nestačí odmocnina ze 2?
160 points
= generateLattice(b1
,b2
, maxlayer
=maxlayer
, include_origin
=include_origin
, order
=order
)
161 mask
= (np
.linalg
.norm(points
, axis
=-1, ord=2) <= r
)
164 def cellCornersWS(b1
, b2
,):
166 Given basis vectors, returns the corners of the Wigner-Seitz unit cell
167 (w1, w2, -w1, w2) for rectangular and square lattice or
168 (w1, w2, w3, -w1, -w2, -w3) otherwise
175 lsm
= ((-v1y
, v2y
), (v1x
, -v2x
))
176 rs
= ((v1x
-v2x
)/2, (v1y
- v2y
)/2)
177 t
= np
.linalg
.solve(lsm
, rs
)
178 return np
.array(v1
)/2 + t
[0]*np
.array((v1y
, -v1x
))
179 b1
, b2
= reduceBasisSingle(b1
, b2
)
180 latticeType
= classifyLatticeSingle(b1
, b2
)
181 if latticeType
is LatticeType
.RECTANGULAR
or latticeType
is LatticeType
.SQUARE
:
189 bvs
= shortestBase46(b1
,b2
,tolerance
=0)
190 return np
.array([solveWS(bvs
[i
], bvs
[(i
+1)%6]) for i
in range(6)])
192 def cutWS(points
, b1
, b2
, scale
=1., tolerance
=1e-13):
194 From given points, return only those that are inside (or on the edge of)
195 the Wigner-Seitz cell of a (scale*b1, scale*b2)-based lattice.
197 # TODO check input dimensions?
198 bvs
= shortestBase46(b1
, b2
)
199 points
= np
.array(points
)
201 mask
= (np
.tensordot(points
, b
, axes
=(-1, 0)) <= (scale
* (1+tolerance
) / 2) *np
.linalg
.norm(b
, ord=2)**2 )
202 points
= points
[mask
]
205 def filledWS(b1
, b2
, density
=10, scale
=1.):
208 TODO more intelligent generation, anisotropy balancing etc.
210 points
= generateLattice(b1
,b2
,maxlayer
=density
*scale
, include_origin
=True)
211 points
= cutWS(points
/density
, np
.array(b1
)*scale
, np
.array(b2
)*scale
)
215 def reciprocalBasis1(*pargs
):
216 a
= reduceBasisSingle(*pargs
)
217 return np
.linalg
.inv(a
).T
219 def reciprocalBasis2pi(*pargs
):
220 return 2*np
.pi
*reciprocalBasis1(*pargs
)
222 # TODO fill it with "points from reciprocal space" instead
223 def filledWS2(b1
,b2
, density
=10, scale
=1.):
224 b1
, b2
= reduceBasisSingle(b1
,b2
)
225 b1r
, b2r
= reciprocalBasis2pi(b1
,b2
)
226 b1l
= np
.linalg
.norm(b1
, ord=2)
227 b2l
= np
.linalg
.norm(b2
, ord=2)
228 b1rl
= np
.linalg
.norm(b1r
, ord=2)
229 b2rl
= np
.linalg
.norm(b2r
, ord=2)
230 # Black magick. Think later.™ Really. FIXME
231 sicher_ratio
= np
.maximum(b1rl
/b2rl
, b2rl
/b1rl
) * np
.maximum(b1l
/b2l
, b2l
/b1l
) # This really has to be adjusted
232 points
= generateLattice(b1r
,b2r
,maxlayer
=density
*scale
*sicher_ratio
, include_origin
=True)
233 points
= cutWS(points
*b1l
/b1rl
/density
, b1
*scale
, b2
*scale
)
237 def change_basis(srcbasis
, destbasis
, srccoords
, srccoordsaxis
=-1, lattice
=True):
238 srcbasis
= np
.array(srcbasis
)
239 destbasis
= np
.array(destbasis
)
240 trmatrix
= np
.dot(np
.linalg
.inv(np
.transpose(destbasis
)), np
.transpose(srcbasis
))
241 if lattice
: # if srcbasis and destbasis are two bases of the same lattice, its elements are ints
243 trmatrix
= np
.round(trmatrix
)
244 if not np
.all(np
.isclose(trmatrix
, otrmatrix
)):
245 raise ValueError("Given srcbasis and destbasis are not bases"
246 "of the same lattice", srcbasis
, destbasis
, trmatrix
-otrmatrix
)
247 destcoords
= np
.tensordot(srccoords
, trmatrix
, axes
=(srccoordsaxis
, -1))
255 - (nehoří) výhledově pořešit problém „hodně anisotropních“ mřížek (tj. kompensovat
256 rozdílné délky základních vektorů).