2 These functions are mostly deprecated by the C counterparts from lattices.h.
3 This file is still kept for reference, but might be removed in the future.
11 class LatticeType(Enum
):
13 All the five Bravais lattices in 2D
19 EQUILATERAL_TRIANGULAR
=3
20 RIGHT_ISOSCELES
=SQUARE
21 PARALLELOGRAMMIC
=OBLIQUE
22 CENTERED_RHOMBIC
=RECTANGULAR
23 RIGHT_TRIANGULAR
=RECTANGULAR
24 CENTERED_RECTANGULAR
=RHOMBIC
25 ISOSCELE_TRIANGULAR
=RHOMBIC
26 RIGHT_ISOSCELE_TRIANGULAR
=SQUARE
27 HEXAGONAL
=EQUILATERAL_TRIANGULAR
29 def reduceBasisSingle(b1
, b2
):
31 Lagrange-Gauss reduction of a 2D basis.
32 cf. https://www.math.auckland.ac.nz/~sgal018/crypto-book/ch17.pdf
33 inputs and outputs are (2,)-shaped numpy arrays
34 The output shall satisfy |b1| <= |b2| <= |b2 - b1|
37 TODO perhaps have the (on-demand?) guarantee of obtuse angle between b1, b2?
38 TODO possibility of returning the (in-order, no-obtuse angles) b as well?
42 if b1
.shape
!= (2,) or b2
.shape
!= (2,):
43 raise ValueError('Shape of b1 and b2 must be (2,)')
44 B1
= np
.sum(b1
* b1
, axis
=-1, keepdims
=True)
45 mu
= np
.sum(b1
* b2
, axis
=-1, keepdims
=True) / B1
46 b2
= b2
- np
.rint(mu
) * b1
47 B2
= np
.sum(b2
* b2
, axis
=-1, keepdims
=True)
48 while(np
.any(B2
< B1
)):
53 mu
= np
.sum(b1
* b2
, axis
=-1, keepdims
=True) / B1
54 b2
= b2
- np
.rint(mu
) * b1
55 B2
= np
.sum(b2
*b2
, axis
=-1, keepdims
=True)
56 return np
.array((b1
,b2
))
58 def shortestBase3(b1
, b2
):
60 returns the "ordered shortest triple" of base vectors (each pair from
61 the triple is a base) and there may not be obtuse angle between b1, b2
64 b1
, b2
= reduceBasisSingle(b1
,b2
)
65 if is_obtuse(b1
, b2
, tolerance
=0):
72 def shortestBase46(b1
, b2
, tolerance
=1e-13):
73 b1
, b2
= reduceBasisSingle(b1
,b2
)
78 eps
= tolerance
* (b2s
+ b1s
)
79 if abs(b3s
- b2s
- b1s
) < eps
:
80 return(b1
, b2
, -b1
, -b2
)
82 if b3s
- b2s
- b1s
> eps
: #obtuse
85 return (b1
, b2
, b3
, -b1
, -b2
, -b3
)
88 def is_obtuse(b1
, b2
, tolerance
=1e-13):
93 eps
= tolerance
* (b2s
+ b1s
)
94 return (b3s
- b2s
- b1s
> eps
)
96 def classifyLatticeSingle(b1
, b2
, tolerance
=1e-13):
98 Given two basis vectors, returns 2D Bravais lattice type.
99 Tolerance is relative.
102 b1
, b2
= reduceBasisSingle(b1
, b2
)
103 b1s
= np
.sum(b1
** 2)
104 b2s
= np
.sum(b2
** 2)
106 b3s
= np
.sum(b3
** 2)
107 eps
= tolerance
* (b2s
+ b1s
)
108 # Avoid obtuse angle between b1 and b2. TODO This should be yet thoroughly tested.
109 # TODO use is_obtuse here?
110 if b3s
- b2s
- b1s
> eps
:
113 # N. B. now the assumption |b3| >= |b2| is no longer valid
115 b2s
= np
.sum(b2
** 2)
116 b3s
= np
.sum(b3
** 2)
117 if abs(b2s
- b1s
) < eps
or abs(b2s
- b3s
) < eps
: # isoscele
118 if abs(b3s
- b1s
) < eps
:
119 return LatticeType
.EQUILATERAL_TRIANGULAR
120 elif abs(b3s
- 2 * b1s
) < eps
:
121 return LatticeType
.SQUARE
123 return LatticeType
.RHOMBIC
124 elif abs(b3s
- b2s
- b1s
) < eps
:
125 return LatticeType
.RECTANGULAR
127 return LatticeType
.OBLIQUE
129 def range2D(maxN
, mini
=1, minj
=0, minN
= 0):
132 Generates pairs of non-negative integer indices (i, j) such that
133 minN ≤ i + j ≤ maxN, i ≥ mini, j ≥ minj.
134 TODO doc and possibly different orderings
136 for maxn
in range(min(mini
, minj
, minN
), floor(maxN
+1)): # i + j == maxn
137 for i
in range(mini
, maxn
+ 1):
141 def generateLattice(b1
, b2
, maxlayer
=5, include_origin
=False, order
='leaves'):
142 bvs
= shortestBase46(b1
, b2
)
143 cc
= len(bvs
) # "corner count"
145 if order
== 'leaves':
146 indices
= np
.array(list(range2D(maxlayer
)))
149 cc
= len(bvs
) # 4 for square/rec,
151 if include_origin
: leaves
.append(np
.array([[0,0]]))
155 leaves
.append(ia
[:,nx
]*ba
+ ib
[:,nx
]*bb
)
156 return np
.concatenate(leaves
)
158 raise ValueError('Lattice point order not implemented: ', order
)
160 def generateLatticeDisk(b1
, b2
, r
, include_origin
=False, order
='leaves'):
161 b1
, b2
= reduceBasisSingle(b1
,b2
)
162 blen
= np
.linalg
.norm(b1
, ord=2)
163 maxlayer
= 2*r
/blen
# FIXME kanon na vrabce? Nestačí odmocnina ze 2?
164 points
= generateLattice(b1
,b2
, maxlayer
=maxlayer
, include_origin
=include_origin
, order
=order
)
165 mask
= (np
.linalg
.norm(points
, axis
=-1, ord=2) <= r
)
168 def cellCornersWS(b1
, b2
,):
170 Given basis vectors, returns the corners of the Wigner-Seitz unit cell
171 (w1, w2, -w1, w2) for rectangular and square lattice or
172 (w1, w2, w3, -w1, -w2, -w3) otherwise
179 lsm
= ((-v1y
, v2y
), (v1x
, -v2x
))
180 rs
= ((v1x
-v2x
)/2, (v1y
- v2y
)/2)
181 t
= np
.linalg
.solve(lsm
, rs
)
182 return np
.array(v1
)/2 + t
[0]*np
.array((v1y
, -v1x
))
183 b1
, b2
= reduceBasisSingle(b1
, b2
)
184 latticeType
= classifyLatticeSingle(b1
, b2
)
185 if latticeType
is LatticeType
.RECTANGULAR
or latticeType
is LatticeType
.SQUARE
:
193 bvs
= shortestBase46(b1
,b2
,tolerance
=0)
194 return np
.array([solveWS(bvs
[i
], bvs
[(i
+1)%6]) for i
in range(6)])
196 def cutWS(points
, b1
, b2
, scale
=1., tolerance
=1e-13):
198 From given points, return only those that are inside (or on the edge of)
199 the Wigner-Seitz cell of a (scale*b1, scale*b2)-based lattice.
201 # TODO check input dimensions?
202 bvs
= shortestBase46(b1
, b2
)
203 points
= np
.array(points
)
205 mask
= (np
.tensordot(points
, b
, axes
=(-1, 0)) <= (scale
* (1+tolerance
) / 2) *np
.linalg
.norm(b
, ord=2)**2 )
206 points
= points
[mask
]
209 def filledWS(b1
, b2
, density
=10, scale
=1.):
212 TODO more intelligent generation, anisotropy balancing etc.
214 points
= generateLattice(b1
,b2
,maxlayer
=density
*scale
, include_origin
=True)
215 points
= cutWS(points
/density
, np
.array(b1
)*scale
, np
.array(b2
)*scale
)
219 def reciprocalBasis1(*pargs
):
220 a
= reduceBasisSingle(*pargs
)
221 return np
.linalg
.inv(a
).T
223 def reciprocalBasis2pi(*pargs
):
224 return 2*np
.pi
*reciprocalBasis1(*pargs
)
226 # TODO fill it with "points from reciprocal space" instead
227 def filledWS2(b1
,b2
, density
=10, scale
=1.):
228 b1
, b2
= reduceBasisSingle(b1
,b2
)
229 b1r
, b2r
= reciprocalBasis2pi(b1
,b2
)
230 b1l
= np
.linalg
.norm(b1
, ord=2)
231 b2l
= np
.linalg
.norm(b2
, ord=2)
232 b1rl
= np
.linalg
.norm(b1r
, ord=2)
233 b2rl
= np
.linalg
.norm(b2r
, ord=2)
234 # Black magick. Think later.™ Really. FIXME
235 sicher_ratio
= np
.maximum(b1rl
/b2rl
, b2rl
/b1rl
) * np
.maximum(b1l
/b2l
, b2l
/b1l
) # This really has to be adjusted
236 points
= generateLattice(b1r
,b2r
,maxlayer
=density
*scale
*sicher_ratio
, include_origin
=True)
237 points
= cutWS(points
*b1l
/b1rl
/density
, b1
*scale
, b2
*scale
)
241 def change_basis(srcbasis
, destbasis
, srccoords
, srccoordsaxis
=-1, lattice
=True):
242 srcbasis
= np
.array(srcbasis
)
243 destbasis
= np
.array(destbasis
)
244 trmatrix
= np
.dot(np
.linalg
.inv(np
.transpose(destbasis
)), np
.transpose(srcbasis
))
245 if lattice
: # if srcbasis and destbasis are two bases of the same lattice, its elements are ints
247 trmatrix
= np
.round(trmatrix
)
248 if not np
.all(np
.isclose(trmatrix
, otrmatrix
)):
249 raise ValueError("Given srcbasis and destbasis are not bases"
250 "of the same lattice", srcbasis
, destbasis
, trmatrix
-otrmatrix
)
251 destcoords
= np
.tensordot(srccoords
, trmatrix
, axes
=(srccoordsaxis
, -1))
259 - (nehoří) výhledově pořešit problém „hodně anisotropních“ mřížek (tj. kompensovat
260 rozdílné délky základních vektorů).