tbkit 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tbkit/__init__.py +27 -0
- tbkit/dos.py +65 -0
- tbkit/error_handling.py +988 -0
- tbkit/graphene.py +173 -0
- tbkit/kspace.py +563 -0
- tbkit/lattice.py +424 -0
- tbkit/lattices.py +126 -0
- tbkit/plot.py +690 -0
- tbkit/propagation.py +338 -0
- tbkit/save.py +98 -0
- tbkit/system.py +744 -0
- tbkit-0.2.0.dist-info/METADATA +260 -0
- tbkit-0.2.0.dist-info/RECORD +16 -0
- tbkit-0.2.0.dist-info/WHEEL +5 -0
- tbkit-0.2.0.dist-info/licenses/LICENSE +29 -0
- tbkit-0.2.0.dist-info/top_level.txt +1 -0
tbkit/system.py
ADDED
|
@@ -0,0 +1,744 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Callable
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from numpy.typing import NDArray
|
|
7
|
+
import scipy.sparse as sparse
|
|
8
|
+
import scipy.linalg as LA
|
|
9
|
+
import numpy.random as rand
|
|
10
|
+
import numpy.char as npc
|
|
11
|
+
from math import sin, cos
|
|
12
|
+
import tbkit.error_handling as error_handling
|
|
13
|
+
from tbkit.lattice import Lattice, COOR_DTYPE
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
PI = np.pi
|
|
17
|
+
ATOL = 1e-3
|
|
18
|
+
HOP_DTYPE = [('n', 'u2'), ('i', 'u4'), ('j', 'u4'),
|
|
19
|
+
('ang', 'f8'), ('tag', 'U2'), ('t', 'c16')]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class System():
|
|
23
|
+
'''
|
|
24
|
+
Solve the Tight-Binding eigenvalue problem of a lattice defined
|
|
25
|
+
by the class **lattice**.
|
|
26
|
+
|
|
27
|
+
:param lat: **lattice** class instance.
|
|
28
|
+
'''
|
|
29
|
+
|
|
30
|
+
def __init__(self, lat: Lattice) -> None:
|
|
31
|
+
error_handling.lat(lat)
|
|
32
|
+
self.lat = lat
|
|
33
|
+
self.sites = self.lat.sites # used to check if sites changes
|
|
34
|
+
self.coor_hop = np.array([], dtype=COOR_DTYPE)
|
|
35
|
+
self.vec_hop = np.array([], dtype=[('dis', 'f8'), ('ang', 'f8')]) # Hopping distances and angles
|
|
36
|
+
self.dist_uni = np.array([], 'f8') # Different hopping distances
|
|
37
|
+
self.store_hop = {} # Store the relevant hoppings (dynamic programming)
|
|
38
|
+
self.hop = np.array([], dtype=HOP_DTYPE) # Hoppings to build-up the Hamiltonian
|
|
39
|
+
self.onsite = np.array([], 'c16') # Onsite energies
|
|
40
|
+
self.ham = sparse.csr_matrix(([], ([], [])), shape=(self.lat.sites, self.lat.sites)) # Hamiltonian
|
|
41
|
+
self.en = np.array([], 'c16') # Eigenenergies
|
|
42
|
+
self.rn = np.array([], 'c16') # Right eigenvectors: H |rn> = en |rn>
|
|
43
|
+
self.ln = np.array([], 'c16') # Left eigenvectors: <ln| H = en <ln|
|
|
44
|
+
self.intensity = np.array([], 'f8') # Intensities (|rn|**2)
|
|
45
|
+
self.pola = np.array([], 'f8') # sublattices polarisation (|rn^{(S)}|**2)
|
|
46
|
+
self.petermann = np.array([], 'f8') # Inverse Participation Ratio
|
|
47
|
+
self.nmax = 0 # number of different hoppings
|
|
48
|
+
|
|
49
|
+
def clear_hopping(self) -> None:
|
|
50
|
+
'''
|
|
51
|
+
Clear structured array *hop*.
|
|
52
|
+
'''
|
|
53
|
+
self.hop = np.array([], dtype=HOP_DTYPE)
|
|
54
|
+
|
|
55
|
+
def get_distances(self) -> None:
|
|
56
|
+
'''
|
|
57
|
+
Private method.
|
|
58
|
+
Get distances and angles of the edges.
|
|
59
|
+
'''
|
|
60
|
+
error_handling.sites(self.lat.sites)
|
|
61
|
+
dif_x = self.lat.coor['x'] - self.lat.coor['x'].reshape(self.lat.sites, 1)
|
|
62
|
+
dif_y = self.lat.coor['y'] - self.lat.coor['y'].reshape(self.lat.sites, 1)
|
|
63
|
+
dist = np.sqrt(dif_x ** 2 + dif_y ** 2)
|
|
64
|
+
ang = (180 / PI * np.arctan2(dif_y, dif_x))
|
|
65
|
+
self.vec_hop = np.zeros(dist.shape, dtype=[('dis', 'f8'), ('ang', 'f8')])
|
|
66
|
+
self.vec_hop['dis'] = dist
|
|
67
|
+
self.vec_hop['ang'] = ang
|
|
68
|
+
self.dist_uni = np.unique(self.vec_hop['dis'].round(4))
|
|
69
|
+
|
|
70
|
+
def print_distances(self, n: int = 1) -> None:
|
|
71
|
+
r'''
|
|
72
|
+
Print distances and positive angles (in degrees) :math:`\phi_+\in[0, 180)`
|
|
73
|
+
of the nth shortest edges. Negative angles are given by:
|
|
74
|
+
:math:`\phi_-= \phi_+-180` and :math:`\phi_+\in[-180, 0)`.
|
|
75
|
+
|
|
76
|
+
:param n: Positive integer. Number of shortest edges.
|
|
77
|
+
'''
|
|
78
|
+
error_handling.sites(self.lat.sites)
|
|
79
|
+
self.get_distances()
|
|
80
|
+
self.nmax = len(self.dist_uni) - 1
|
|
81
|
+
error_handling.positive_int_lim(n, 'n', self.nmax)
|
|
82
|
+
print('\n{} different distances between sites:'.format(self.nmax))
|
|
83
|
+
print('\nDistances between sites:')
|
|
84
|
+
for i, d in enumerate(self.dist_uni[1: n+1]):
|
|
85
|
+
if i == 0:
|
|
86
|
+
hop_name = 'st'
|
|
87
|
+
elif i == 1:
|
|
88
|
+
hop_name = 'nd'
|
|
89
|
+
elif i == 2:
|
|
90
|
+
hop_name = 'rd'
|
|
91
|
+
else:
|
|
92
|
+
hop_name = 'th'
|
|
93
|
+
print('{}{} hopping, length: {:.3f}'.format(i+1, hop_name, d))
|
|
94
|
+
print('\twith positive angles:')
|
|
95
|
+
positive_ang = self.vec_hop['ang'][np.isclose(d, self.vec_hop['dis'], atol=ATOL) &
|
|
96
|
+
(self.vec_hop['ang'] >= 0.) &
|
|
97
|
+
(self.vec_hop['ang'] < 180.)]
|
|
98
|
+
print('\t', np.unique(positive_ang.round(4)))
|
|
99
|
+
|
|
100
|
+
def set_onsite(self, dict_onsite: dict[str, complex]) -> None:
|
|
101
|
+
'''
|
|
102
|
+
Set onsite energies.
|
|
103
|
+
|
|
104
|
+
:param on: Array. Sublattice onsite energies.
|
|
105
|
+
|
|
106
|
+
Example usage::
|
|
107
|
+
|
|
108
|
+
# Line-Centered Square lattice
|
|
109
|
+
sys.set_onsite({'a': -1j, 'b': -2j})
|
|
110
|
+
'''
|
|
111
|
+
error_handling.sites(self.lat.sites)
|
|
112
|
+
error_handling.set_onsite(dict_onsite, self.lat.tags)
|
|
113
|
+
self.onsite = np.zeros(self.lat.sites, 'c16')
|
|
114
|
+
for tag, on in dict_onsite.items():
|
|
115
|
+
self.onsite[self.lat.coor['tag'] ==tag] = on
|
|
116
|
+
|
|
117
|
+
def fill_store_hop(self, n: int) -> None:
|
|
118
|
+
'''
|
|
119
|
+
Private method.
|
|
120
|
+
|
|
121
|
+
Store in *store_hop* indices (with :math:`i < j`), positive angles, and tags
|
|
122
|
+
of a given type of hopping.
|
|
123
|
+
'''
|
|
124
|
+
ind = np.argwhere(np.isclose(self.dist_uni[n], self.vec_hop['dis'], atol=ATOL))
|
|
125
|
+
ind_up = ind[ind[:, 1] > ind[:, 0]]
|
|
126
|
+
hop = np.zeros(len(ind_up), dtype=HOP_DTYPE[:-1])
|
|
127
|
+
hop['i'] = ind_up[:, 0]
|
|
128
|
+
hop['j'] = ind_up[:, 1]
|
|
129
|
+
hop['ang'] = self.vec_hop['ang'][ind_up[:, 0], ind_up[:, 1]]
|
|
130
|
+
hop['tag'] = npc.add(self.lat.coor['tag'][ind_up[:, 0]],
|
|
131
|
+
self.lat.coor['tag'][ind_up[:, 1]])
|
|
132
|
+
self.store_hop[n] = hop
|
|
133
|
+
|
|
134
|
+
def set_hopping(self, list_hop: list[dict], upper_part: bool = True) -> None:
|
|
135
|
+
r'''
|
|
136
|
+
Set lattice hoppings.
|
|
137
|
+
|
|
138
|
+
:param list_hop: List of Dictionaries.
|
|
139
|
+
Dictionary with keys ('n', 'ang', 'tag', 't') where:
|
|
140
|
+
|
|
141
|
+
* 'n' Positive integer, type of hoppings:
|
|
142
|
+
|
|
143
|
+
* 'n': 1 for nearest neighbours.
|
|
144
|
+
* 'n': 2 for next-nearest neighbours.
|
|
145
|
+
* 'n': 3 for next-next-nearest neighbours.
|
|
146
|
+
* etc...
|
|
147
|
+
|
|
148
|
+
* 'ang' value, float, angle, in deg, of the hoppings. (optional).
|
|
149
|
+
|
|
150
|
+
Hopping angles are given by the method *print_distances*.
|
|
151
|
+
|
|
152
|
+
* If :math:`ang \in[0, 180)`, fill the Hamiltonian upper part.
|
|
153
|
+
* If :math:`ang \in[-180, 0)`, fill the Hamiltonian lower part.
|
|
154
|
+
|
|
155
|
+
* 'tag' string of length 2 (optional).
|
|
156
|
+
|
|
157
|
+
Hopping tags.
|
|
158
|
+
|
|
159
|
+
* 't' Complex number.
|
|
160
|
+
|
|
161
|
+
Hopping value.
|
|
162
|
+
|
|
163
|
+
:param upper_part: Boolean. Default value True.
|
|
164
|
+
|
|
165
|
+
* True get hoppings with (:math:`i<j`) *i.e.* fill the Hamiltonian upper part.
|
|
166
|
+
* False get hoppings with (:math:`i>j`) *i.e.* fill the Hamiltonian lower part.
|
|
167
|
+
|
|
168
|
+
Example usage::
|
|
169
|
+
|
|
170
|
+
# fill upper part:
|
|
171
|
+
sys.set_hopping([{'n': 1, t: 1.}])
|
|
172
|
+
# fill lower part:
|
|
173
|
+
sys.set_hopping([{'n': 1, t: 1.}], upper_part=False)
|
|
174
|
+
# fill upper part: specifying the angles:
|
|
175
|
+
sys.set_hopping([{'n': 1, 'ang': 0., t: 1.}, {'n': 1, 'ang': 90, t: 2.}])
|
|
176
|
+
# fill lower part:
|
|
177
|
+
sys.set_hopping([{'n': 1, 'ang': -180., t: 1.}, {'n': 1, 'ang': -90, t: 2.}], upper_part=False)
|
|
178
|
+
# fill upper part: specifying the tags:
|
|
179
|
+
sys.set_hopping([{'n': 1, 'tag': 'ab', t: 1.}, {'n': 1, 'tag': 'ba', t: 2.}])
|
|
180
|
+
# fill lower part:
|
|
181
|
+
sys.set_hopping([{'n': 1, 'tag': 'ab', t: 1.}, {'n': 1, 'tag': 'ba', t: 2.}], upper_part=False)
|
|
182
|
+
# fill upper part: specifying the angles and tags:
|
|
183
|
+
sys.set_hopping([{'n': 1, 'ang': 0., 'tag': 'ab', t: 1.},
|
|
184
|
+
{'n': 1, 'ang': 0., 'tag': 'ba', t: 2.},
|
|
185
|
+
{'n': 1, 'ang': 90., 'tag': 'ab', t: 3.},
|
|
186
|
+
{'n': 1, 'ang': 90., 'tag': 'ba', t: 4.}])
|
|
187
|
+
# fill lower part:
|
|
188
|
+
sys.set_hopping([{'n': 1, 'ang': 0., 'tag': 'ab', t: 1.},
|
|
189
|
+
{'n': 1, 'ang': 0., 'tag': 'ba', t: 2.},
|
|
190
|
+
{'n': 1, 'ang': 90., 'tag': 'ab', t: 3.},
|
|
191
|
+
{'n': 1, 'ang': 90., 'tag': 'ba', t: 4.}]), upper_part=False)
|
|
192
|
+
|
|
193
|
+
.. note::
|
|
194
|
+
|
|
195
|
+
A Hermitian hopping matrix can be build-up only using
|
|
196
|
+
its upper part OR only using its lower part. The full matrix is then
|
|
197
|
+
automatic built by Hermitian conjugaison.
|
|
198
|
+
|
|
199
|
+
If both upper AND lower parts are used to build up the hopping matrix.
|
|
200
|
+
non Hermitian conjugaison is not performed *i.e.* non-Hermitian hopping matrix
|
|
201
|
+
can be built.
|
|
202
|
+
'''
|
|
203
|
+
error_handling.sites(self.lat.sites)
|
|
204
|
+
error_handling.boolean(upper_part, 'upper_part')
|
|
205
|
+
self.get_distances()
|
|
206
|
+
self.nmax = len(self.dist_uni) - 1
|
|
207
|
+
error_handling.set_hopping(list_hop, self.nmax)
|
|
208
|
+
list_n = np.unique([dic['n'] for dic in list_hop])
|
|
209
|
+
# fill, if needed self.store_hop
|
|
210
|
+
self.check_sites()
|
|
211
|
+
for n in list_n:
|
|
212
|
+
if n not in self.store_hop:
|
|
213
|
+
self.fill_store_hop(n)
|
|
214
|
+
# fill self.hop
|
|
215
|
+
for dic in list_hop:
|
|
216
|
+
if len(dic) == 2:
|
|
217
|
+
size = len(self.store_hop[dic['n']])
|
|
218
|
+
if upper_part:
|
|
219
|
+
mask = (self.hop['n'] == dic['n']) & (self.hop['i'] < self.hop['j'])
|
|
220
|
+
else:
|
|
221
|
+
mask = (self.hop['n'] == dic['n']) & (self.hop['i'] > self.hop['j'])
|
|
222
|
+
if np.sum(mask):
|
|
223
|
+
self.hop = self.hop[np.logical_not(mask)]
|
|
224
|
+
ind = np.ones(size, bool)
|
|
225
|
+
hop = self.set_given_hopping(dic['n'], size, dic, ind, upper_part=upper_part)
|
|
226
|
+
elif len(dic) == 3 and 'ang' in dic:
|
|
227
|
+
error_handling.angle(dic['ang'], np.unique(self.store_hop[dic['n']]['ang']), upper_part)
|
|
228
|
+
if dic['ang'] >= 0:
|
|
229
|
+
ang_store = dic['ang']
|
|
230
|
+
else:
|
|
231
|
+
ang_store = dic['ang'] + 180.
|
|
232
|
+
size = np.sum(np.isclose(ang_store, self.store_hop[dic['n']]['ang'], atol=ATOL))
|
|
233
|
+
mask = (self.hop['n'] == dic['n']) & np.isclose(self.hop['ang'], dic['ang'], atol=ATOL)
|
|
234
|
+
if np.sum(mask):
|
|
235
|
+
self.hop = self.hop[np.logical_not(mask)]
|
|
236
|
+
ind = np.isclose(ang_store, self.store_hop[dic['n']]['ang'], atol=ATOL)
|
|
237
|
+
error_handling.index(ind, dic)
|
|
238
|
+
hop = self.set_given_hopping(dic['n'], size, dic, ind, upper_part=upper_part)
|
|
239
|
+
elif len(dic) == 3 and 'tag' in dic:
|
|
240
|
+
if upper_part:
|
|
241
|
+
tag_store = dic['tag']
|
|
242
|
+
else:
|
|
243
|
+
tag_store = dic['tag'][::-1]
|
|
244
|
+
size = np.sum(self.store_hop[dic['n']]['tag'] == tag_store)
|
|
245
|
+
mask = (self.hop['n'] == dic['n']) & (self.hop['tag'] == dic['tag'])
|
|
246
|
+
if upper_part:
|
|
247
|
+
mask = self.hop['n'] == dic['n'] & (self.hop['tag'] == dic['tag']) & (self.hop['i'] < self.hop['j'])
|
|
248
|
+
else:
|
|
249
|
+
mask = self.hop['n'] == dic['n'] & (self.hop['tag'] == dic['tag']) & (self.hop['i'] > self.hop['j'])
|
|
250
|
+
if np.sum(mask):
|
|
251
|
+
self.hop = self.hop[np.logical_not(mask)]
|
|
252
|
+
ind = self.store_hop[dic['n']]['tag'] == tag_store
|
|
253
|
+
error_handling.index(ind, dic)
|
|
254
|
+
hop = self.set_given_hopping(dic['n'], size, dic, ind, upper_part=upper_part)
|
|
255
|
+
else:
|
|
256
|
+
error_handling.angle(dic['ang'], np.unique(self.store_hop[dic['n']]['ang']), upper_part=upper_part)
|
|
257
|
+
error_handling.tag(dic['tag'], np.unique(self.store_hop[dic['n']]['tag']))
|
|
258
|
+
if dic['ang'] >= 0:
|
|
259
|
+
ang_store = dic['ang']
|
|
260
|
+
else:
|
|
261
|
+
ang_store = dic['ang'] + 180.
|
|
262
|
+
if upper_part:
|
|
263
|
+
tag_store = dic['tag']
|
|
264
|
+
else:
|
|
265
|
+
tag_store = dic['tag'][::-1]
|
|
266
|
+
size = np.sum((self.store_hop[dic['n']]['tag'] == tag_store) &
|
|
267
|
+
(np.isclose(ang_store, self.store_hop[dic['n']]['ang'], atol=ATOL)))
|
|
268
|
+
bool1 = (self.hop['n'] == dic['n']) & (self.hop['tag'] == dic['tag'])
|
|
269
|
+
bool2 = np.isclose(self.hop['ang'], dic['ang'], atol=ATOL)
|
|
270
|
+
mask = bool1 & bool2
|
|
271
|
+
if np.sum(mask):
|
|
272
|
+
self.hop = self.hop[np.logical_not(mask)]
|
|
273
|
+
ind = ((self.store_hop[dic['n']]['tag'] == tag_store) &
|
|
274
|
+
(np.isclose(ang_store, self.store_hop[dic['n']]['ang'], atol=1)))
|
|
275
|
+
error_handling.index(ind, dic)
|
|
276
|
+
hop = self.set_given_hopping(dic['n'], size, dic, ind, upper_part=upper_part)
|
|
277
|
+
self.hop = np.concatenate([self.hop, hop])
|
|
278
|
+
|
|
279
|
+
def check_sites(self) -> None:
|
|
280
|
+
'''
|
|
281
|
+
Private method.
|
|
282
|
+
Check if the number of sites was changed after calling the
|
|
283
|
+
method system.set_hopping().
|
|
284
|
+
'''
|
|
285
|
+
if self.sites != self.lat.sites:
|
|
286
|
+
self.store_hop = {}
|
|
287
|
+
self.sites = self.lat.sites
|
|
288
|
+
|
|
289
|
+
def set_given_hopping(
|
|
290
|
+
self, n: int, size: int, dic: dict, mask: NDArray, upper_part: bool,
|
|
291
|
+
) -> NDArray:
|
|
292
|
+
'''
|
|
293
|
+
Private method.
|
|
294
|
+
Fill self.hop.
|
|
295
|
+
|
|
296
|
+
:param n: Integer. Hopping type.
|
|
297
|
+
:param size: Integer. Number of hoppings.
|
|
298
|
+
:param doc: Dictionary. Hopping dictionary.
|
|
299
|
+
:param mask: np.ndarray. Mask.
|
|
300
|
+
:param upper_part: Boolean. If True, self.hop['i'] < self.hop['j'].
|
|
301
|
+
'''
|
|
302
|
+
hop = np.empty(size, dtype=HOP_DTYPE)
|
|
303
|
+
hop['n'] = dic['n']
|
|
304
|
+
hop['t'] = dic['t']
|
|
305
|
+
if upper_part:
|
|
306
|
+
hop['i'] = self.store_hop[n]['i'][mask]
|
|
307
|
+
hop['j'] = self.store_hop[n]['j'][mask]
|
|
308
|
+
hop['ang'] = self.store_hop[n]['ang'][mask]
|
|
309
|
+
hop['tag'] = self.store_hop[n]['tag'][mask]
|
|
310
|
+
else:
|
|
311
|
+
hop['i'] = self.store_hop[n]['j'][mask]
|
|
312
|
+
hop['j'] = self.store_hop[n]['i'][mask]
|
|
313
|
+
hop['ang'] = self.store_hop[n]['ang'][mask] - 180
|
|
314
|
+
hop['tag'] = npc.add(self.lat.coor['tag'][hop['i']],
|
|
315
|
+
self.lat.coor['tag'][hop['j']])
|
|
316
|
+
return hop
|
|
317
|
+
|
|
318
|
+
def set_hopping_manual(self, dict_hop: dict[tuple[int, int], complex], upper_part: bool = True) -> None:
|
|
319
|
+
'''
|
|
320
|
+
Set hoppings manually.
|
|
321
|
+
|
|
322
|
+
:param dict_hop: Dictionary of hoppings.
|
|
323
|
+
key: hopping indices, val: hopping values.
|
|
324
|
+
|
|
325
|
+
:parameter upper_part: Boolean.
|
|
326
|
+
|
|
327
|
+
* True, fill the Hamiltonian upper part.
|
|
328
|
+
* False, fill the Hamiltonian lower part.
|
|
329
|
+
'''
|
|
330
|
+
hop = np.zeros(len(dict_hop), dtype=HOP_DTYPE)
|
|
331
|
+
i = [h[0] for h in dict_hop.keys()]
|
|
332
|
+
j = [h[1] for h in dict_hop.keys()]
|
|
333
|
+
t = [val for val in dict_hop.values()]
|
|
334
|
+
hop['i'], hop['j']= i, j
|
|
335
|
+
hop['t'] = t
|
|
336
|
+
hop['tag'] = npc.add(self.lat.coor['tag'][i],
|
|
337
|
+
self.lat.coor['tag'][j])
|
|
338
|
+
ang = 180 / PI * np.arctan2(self.lat.coor['y'][j]-self.lat.coor['y'][i],
|
|
339
|
+
self.lat.coor['x'][j]-self.lat.coor['x'][i])
|
|
340
|
+
if upper_part:
|
|
341
|
+
ang[ang < 0] += 180
|
|
342
|
+
else:
|
|
343
|
+
ang[ang >= 0] -= 180
|
|
344
|
+
hop['ang'] = ang
|
|
345
|
+
self.hop = np.concatenate([self.hop, hop])
|
|
346
|
+
|
|
347
|
+
def set_hopping_dis(self, alpha: complex) -> None:
|
|
348
|
+
'''
|
|
349
|
+
Set uniform hopping disorder.
|
|
350
|
+
|
|
351
|
+
:param alpha: Complex or Real number. Disorder stength.
|
|
352
|
+
|
|
353
|
+
Example usage::
|
|
354
|
+
|
|
355
|
+
sys.set_hopping_dis(alpha=0.1)
|
|
356
|
+
|
|
357
|
+
'''
|
|
358
|
+
error_handling.empty_hop(self.hop)
|
|
359
|
+
error_handling.number(alpha, 'alpha')
|
|
360
|
+
self.hop['t'] *= 1. + alpha * rand.uniform(-1., 1., len(self.hop))
|
|
361
|
+
|
|
362
|
+
def set_peierls_phase(self, phase: Callable[..., NDArray]) -> None:
|
|
363
|
+
r'''
|
|
364
|
+
Apply the Peierls substitution to the existing hoppings, to
|
|
365
|
+
capture the effect of an orbital magnetic field:
|
|
366
|
+
|
|
367
|
+
.. math::
|
|
368
|
+
|
|
369
|
+
t_{ij} \to t_{ij}\, e^{i\phi_{ij}}\, ,\quad
|
|
370
|
+
\phi_{ij} = \frac{2\pi}{\Phi_0}\int_{\mathbf{r}_i}^{\mathbf{r}_j}
|
|
371
|
+
\mathbf{A}\cdot d\mathbf{l}
|
|
372
|
+
|
|
373
|
+
where :math:`\mathbf{A}` is the vector potential, integrated along
|
|
374
|
+
the straight bond from site :math:`i` to site :math:`j`.
|
|
375
|
+
|
|
376
|
+
Must be called after *set_hopping* / *set_hopping_manual* (it
|
|
377
|
+
rescales the existing hoppings *in place*) and before *get_ham*.
|
|
378
|
+
For a uniform perpendicular field, use the convenience method
|
|
379
|
+
*set_magnetic_field* instead.
|
|
380
|
+
|
|
381
|
+
:param phase: Callable. ``phase(xi, yi, xj, yj)`` returns
|
|
382
|
+
:math:`\phi_{ij}`, the (real-valued) Peierls phase for the bond
|
|
383
|
+
from :math:`(x_i, y_i)` to :math:`(x_j, y_j)`. Called with
|
|
384
|
+
Numpy arrays (one value per hopping in *sys.hop*).
|
|
385
|
+
|
|
386
|
+
.. note::
|
|
387
|
+
|
|
388
|
+
*get_ham* automatically assigns the reversed bond its complex
|
|
389
|
+
conjugate, so the Hamiltonian stays Hermitian as long as
|
|
390
|
+
*phase* is antisymmetric under swapping :math:`i` and
|
|
391
|
+
:math:`j` -- true for the line integral of any vector
|
|
392
|
+
potential, since reversing the integration path negates it.
|
|
393
|
+
|
|
394
|
+
Example usage::
|
|
395
|
+
|
|
396
|
+
# Peierls phase from a uniform field via the Landau gauge
|
|
397
|
+
# A = (0, B x): captures the same physics as set_magnetic_field,
|
|
398
|
+
# just in a different (equally valid) gauge.
|
|
399
|
+
B = 0.05
|
|
400
|
+
sys.set_peierls_phase(lambda xi, yi, xj, yj: B * (xj - xi) * (xi + xj) / 2)
|
|
401
|
+
'''
|
|
402
|
+
error_handling.empty_hop(self.hop)
|
|
403
|
+
error_handling.is_callable(phase, 'phase')
|
|
404
|
+
xi = self.lat.coor['x'][self.hop['i']]
|
|
405
|
+
yi = self.lat.coor['y'][self.hop['i']]
|
|
406
|
+
xj = self.lat.coor['x'][self.hop['j']]
|
|
407
|
+
yj = self.lat.coor['y'][self.hop['j']]
|
|
408
|
+
self.hop['t'] = self.hop['t'] * np.exp(1j * phase(xi, yi, xj, yj))
|
|
409
|
+
|
|
410
|
+
def set_magnetic_field(self, alpha: float) -> None:
|
|
411
|
+
r'''
|
|
412
|
+
Set a uniform perpendicular magnetic field via the Peierls
|
|
413
|
+
substitution (see *set_peierls_phase*), using the symmetric gauge
|
|
414
|
+
:math:`\mathbf{A} = \frac{B}{2}(-y, x)`:
|
|
415
|
+
|
|
416
|
+
.. math::
|
|
417
|
+
|
|
418
|
+
\phi_{ij} = \pi\alpha\,(x_iy_j - x_jy_i)
|
|
419
|
+
|
|
420
|
+
:param alpha: Real number. Flux density :math:`B/\Phi_0`, in flux
|
|
421
|
+
quanta per unit area (in the lattice's length units) -- *i.e.*
|
|
422
|
+
the flux through a region of area :math:`S` is
|
|
423
|
+
:math:`\alpha S` flux quanta.
|
|
424
|
+
|
|
425
|
+
Example usage::
|
|
426
|
+
|
|
427
|
+
# one flux quantum per 100 unit cells of a lattice with
|
|
428
|
+
# lattice constant 1:
|
|
429
|
+
sys.set_magnetic_field(alpha=0.01)
|
|
430
|
+
'''
|
|
431
|
+
error_handling.real_number(alpha, 'alpha')
|
|
432
|
+
self.set_peierls_phase(lambda xi, yi, xj, yj: PI * alpha * (xi*yj - xj*yi))
|
|
433
|
+
|
|
434
|
+
def set_onsite_dis(self, alpha: complex) -> None:
|
|
435
|
+
'''
|
|
436
|
+
Set uniform onsite disorder.
|
|
437
|
+
|
|
438
|
+
:param alpha: Complex or Real number. Disorder stength.
|
|
439
|
+
|
|
440
|
+
Example usage::
|
|
441
|
+
|
|
442
|
+
sys.set_onsite_dis(alpha=0.1)
|
|
443
|
+
|
|
444
|
+
'''
|
|
445
|
+
error_handling.empty_onsite(self.onsite)
|
|
446
|
+
error_handling.number(alpha, 'alpha')
|
|
447
|
+
self.onsite += alpha * rand.uniform(-1., 1., self.lat.sites)
|
|
448
|
+
|
|
449
|
+
def set_onsite_def(self, onsite_def: dict[int, complex]) -> None:
|
|
450
|
+
'''
|
|
451
|
+
Set specific onsite energies.
|
|
452
|
+
|
|
453
|
+
:param onsite_def: Dictionary.
|
|
454
|
+
key: site indices, val: onsite values.
|
|
455
|
+
|
|
456
|
+
Example usage::
|
|
457
|
+
|
|
458
|
+
set_onsite_def(0: 1., 1: -1j)
|
|
459
|
+
'''
|
|
460
|
+
error_handling.empty_onsite(self.onsite)
|
|
461
|
+
error_handling.set_onsite_def(onsite_def, self.lat.sites)
|
|
462
|
+
for i, o in onsite_def.items():
|
|
463
|
+
self.onsite[i] = o
|
|
464
|
+
|
|
465
|
+
def set_hopping_def(self, hopping_def: dict[tuple[int, int], complex]) -> None:
|
|
466
|
+
'''
|
|
467
|
+
Set specific hoppings.
|
|
468
|
+
|
|
469
|
+
:param hopping_def: Dictionary of hoppings.
|
|
470
|
+
key: hopping indices, val: hopping values.
|
|
471
|
+
|
|
472
|
+
Example usage::
|
|
473
|
+
|
|
474
|
+
sys.set_hopping_def({(0, 1): 1., (1, 2): -1j})
|
|
475
|
+
'''
|
|
476
|
+
error_handling.empty_hop(self.hop)
|
|
477
|
+
error_handling.set_hopping_def(self.hop, hopping_def, self.lat.sites)
|
|
478
|
+
for key, val in hopping_def.items():
|
|
479
|
+
cond = (self.hop['i'] == key[0]) & (self.hop['j'] == key[1])
|
|
480
|
+
self.hop['t'][cond] = val
|
|
481
|
+
self.hop['ang'] = self.vec_hop['ang'][key[0], key[1]]
|
|
482
|
+
self.hop['tag'] = npc.add(self.lat.coor['tag'][key[0]],
|
|
483
|
+
self.lat.coor['tag'][key[1]])
|
|
484
|
+
|
|
485
|
+
def set_new_hopping(self, list_hop: list[dict], ind: NDArray) -> None:
|
|
486
|
+
'''
|
|
487
|
+
Private method.
|
|
488
|
+
Set new hoppings.
|
|
489
|
+
|
|
490
|
+
:param list_hop: List of Dictionary (see set_hopping definition).
|
|
491
|
+
:param ind: List. List of indices.
|
|
492
|
+
'''
|
|
493
|
+
for dic in list_hop:
|
|
494
|
+
if len(dic) == 2:
|
|
495
|
+
self.hop['t'][ind] = dic['t']
|
|
496
|
+
elif len(dic) == 3 and 'ang' in dic:
|
|
497
|
+
self.hop['t'][ind & (self.hop['ang'] == dic['ang'])] = dic['t']
|
|
498
|
+
elif len(dic) == 3 and 'tag' in dic:
|
|
499
|
+
self.hop['t'][ind & (self.hop['tag'] == dic['tag'])] = dic['t']
|
|
500
|
+
else:
|
|
501
|
+
self.hop['t'][ind & (self.hop['tag'] == dic['tag'])
|
|
502
|
+
& (self.hop['ang'] == dic['ang'])] = dic['t']
|
|
503
|
+
|
|
504
|
+
def find_square(self, xlims: tuple[float, float], ylims: tuple[float, float]) -> NDArray:
|
|
505
|
+
'''
|
|
506
|
+
Private method.
|
|
507
|
+
Find hoppings within the square.
|
|
508
|
+
|
|
509
|
+
:param xlims: List or Tuple. :math:`x` interval.
|
|
510
|
+
:param ylims: List or Tuple. :math:`y` interval.
|
|
511
|
+
'''
|
|
512
|
+
error_handling.lims(xlims)
|
|
513
|
+
error_handling.lims(ylims)
|
|
514
|
+
in1 = (self.lat.coor['x'][self.hop['i']] >= xlims[0]) & \
|
|
515
|
+
(self.lat.coor['y'][self.hop['i']] >= ylims[0]) & \
|
|
516
|
+
(self.lat.coor['x'][self.hop['j']] >= xlims[0]) & \
|
|
517
|
+
(self.lat.coor['y'][self.hop['j']] >= ylims[0])
|
|
518
|
+
in2 = (self.lat.coor['x'][self.hop['i']] <= xlims[1]) & \
|
|
519
|
+
(self.lat.coor['y'][self.hop['i']] <= ylims[1]) & \
|
|
520
|
+
(self.lat.coor['x'][self.hop['j']] <= xlims[1]) & \
|
|
521
|
+
(self.lat.coor['y'][self.hop['j']] <= ylims[1])
|
|
522
|
+
return in1 * in2
|
|
523
|
+
|
|
524
|
+
def find_ellipse(self, rx: float, ry: float, x0: float, y0: float) -> NDArray:
|
|
525
|
+
'''
|
|
526
|
+
Private method.
|
|
527
|
+
Find hoppings within the ellipse.
|
|
528
|
+
|
|
529
|
+
:param rx: Positive Float. Radius along :math:`x`.
|
|
530
|
+
:param ry: Positive Float. Radius along :math:`y`.
|
|
531
|
+
:param x0: Float. Defalut value 0. :math:`x` center.
|
|
532
|
+
:param y0: Float. Defalut value 0. :math:`x` center.
|
|
533
|
+
'''
|
|
534
|
+
in1 = (self.lat.coor['x'][self.hop['i']] - x0) ** 2 / rx ** 2 + \
|
|
535
|
+
(self.lat.coor['y'][self.hop['i']] - y0) ** 2 / ry ** 2 <= 1.
|
|
536
|
+
in2 = (self.lat.coor['x'][self.hop['j']] - x0) ** 2 / rx ** 2 + \
|
|
537
|
+
(self.lat.coor['y'][self.hop['j']] - y0) ** 2 / ry ** 2 <= 1.
|
|
538
|
+
return in1 * in2
|
|
539
|
+
|
|
540
|
+
def change_hopping_square(
|
|
541
|
+
self, list_hop: list[dict], xlims: tuple[float, float], ylims: tuple[float, float] = [-1., 1.],
|
|
542
|
+
) -> None:
|
|
543
|
+
'''
|
|
544
|
+
Change hopping values.
|
|
545
|
+
|
|
546
|
+
:param list_hop: List of Dictionary (see set_hopping definition).
|
|
547
|
+
:param xlims: List or Tuple. :math:`x` interval.
|
|
548
|
+
:param ylims: List or Tuple. :math:`y` interval.
|
|
549
|
+
'''
|
|
550
|
+
error_handling.empty_hop(self.hop)
|
|
551
|
+
error_handling.set_hopping(list_hop, self.nmax)
|
|
552
|
+
ind = self.find_square(xlims, ylims)
|
|
553
|
+
self.set_new_hopping(list_hop, ind)
|
|
554
|
+
|
|
555
|
+
def change_hopping_ellipse(
|
|
556
|
+
self, list_hop: list[dict], rx: float, ry: float, x0: float = 0., y0: float = 0.,
|
|
557
|
+
) -> None:
|
|
558
|
+
'''
|
|
559
|
+
Change hopping values.
|
|
560
|
+
|
|
561
|
+
:param list_hop: List of Dictionary (see set_hopping definition).
|
|
562
|
+
:param rx: Positive Float. Radius along :math:`x`.
|
|
563
|
+
:param ry: Positive Float. Radius along :math:`y`.
|
|
564
|
+
:param x0: Float. Default value 0. :math:`x` center.
|
|
565
|
+
:param y0: Float. Default value 0. :math:`y` center.
|
|
566
|
+
'''
|
|
567
|
+
error_handling.empty_hop(self.hop)
|
|
568
|
+
error_handling.set_hopping(list_hop, self.nmax)
|
|
569
|
+
error_handling.positive_real(rx, 'rx')
|
|
570
|
+
error_handling.positive_real(ry, 'rx')
|
|
571
|
+
error_handling.real_number(x0, 'x0')
|
|
572
|
+
error_handling.real_number(y0, 'y0')
|
|
573
|
+
ind = self.find_ellipse(rx, ry, x0, y0)
|
|
574
|
+
self.set_new_hopping(list_hop, ind)
|
|
575
|
+
|
|
576
|
+
def get_coor_hop(self) -> None:
|
|
577
|
+
'''
|
|
578
|
+
Get the site coordinates in hopping space
|
|
579
|
+
only considering the nearest neighbours hoppings.
|
|
580
|
+
'''
|
|
581
|
+
error_handling.empty_hop(self.hop)
|
|
582
|
+
visited = np.zeros(self.lat.sites, 'u2')
|
|
583
|
+
self.coor_hop = np.zeros(self.lat.sites, dtype=COOR_DTYPE)
|
|
584
|
+
self.coor_hop['tag'] = self.lat.coor['tag']
|
|
585
|
+
hop = self.hop[self.hop['n'] == 1]
|
|
586
|
+
hop_down = np.copy(hop)
|
|
587
|
+
hop_down['i'] = hop['j']
|
|
588
|
+
hop_down['j'] = hop[ 'i']
|
|
589
|
+
hop_down['ang'] = -180 + hop['ang']
|
|
590
|
+
hop = np.concatenate([hop, hop_down])
|
|
591
|
+
i_visit = np.min(hop['i'])
|
|
592
|
+
while True:
|
|
593
|
+
hs = hop[hop['i'] == i_visit]
|
|
594
|
+
for h in hs:
|
|
595
|
+
if visited[h['j']] == 2:
|
|
596
|
+
continue
|
|
597
|
+
self.coor_hop['x'][h['j']] = self.coor_hop['x'][i_visit] + \
|
|
598
|
+
h['t'].real*cos(PI / 180 * h['ang'])
|
|
599
|
+
self.coor_hop['y'][h['j']] = self.coor_hop['y'][i_visit] + \
|
|
600
|
+
h['t'].real*sin(PI / 180 * h['ang'])
|
|
601
|
+
visited[h['j']] = 1
|
|
602
|
+
visited[i_visit] = 2
|
|
603
|
+
explored = np.argwhere(visited == 1)
|
|
604
|
+
if not explored.any():
|
|
605
|
+
break
|
|
606
|
+
i_visit = explored[0, 0]
|
|
607
|
+
|
|
608
|
+
def get_ham(self) -> None:
|
|
609
|
+
'''
|
|
610
|
+
Get the Tight-Binding Hamiltonian using sys.hop.
|
|
611
|
+
'''
|
|
612
|
+
error_handling.empty_hop(self.hop)
|
|
613
|
+
error_handling.hop_sites(self.hop, self.lat.sites)
|
|
614
|
+
if np.all(self.hop['ang'] >= 0) or np.all(self.hop['ang'] < 0):
|
|
615
|
+
self.ham = sparse.csr_matrix((self.hop['t'], (self.hop['i'], self.hop['j'])),
|
|
616
|
+
shape=(self.lat.sites, self.lat.sites)) \
|
|
617
|
+
+ sparse.csr_matrix((self.hop['t'].conj(), (self.hop['j'], self.hop['i'])),
|
|
618
|
+
shape=(self.lat.sites, self.lat.sites))
|
|
619
|
+
else:
|
|
620
|
+
self.ham = sparse.csr_matrix((self.hop['t'], (self.hop['i'], self.hop['j'])),
|
|
621
|
+
shape=(self.lat.sites, self.lat.sites))
|
|
622
|
+
if self.onsite.size == self.lat.sites:
|
|
623
|
+
self.ham += sparse.diags(self.onsite, 0)
|
|
624
|
+
|
|
625
|
+
def get_eig(self, eigenvec: bool = False, left: bool = False) -> None:
|
|
626
|
+
'''
|
|
627
|
+
Get the eigenergies, eigenvectors and polarisation.
|
|
628
|
+
|
|
629
|
+
:param eigenvec: Boolean. Default value False.
|
|
630
|
+
If True, get the eigenvectors.
|
|
631
|
+
:param left: Boolean. Default value False.
|
|
632
|
+
If True, get the left eigenvectors too.
|
|
633
|
+
Relevant for non-Hermitian matrices.
|
|
634
|
+
'''
|
|
635
|
+
error_handling.empty_ham(self.ham)
|
|
636
|
+
error_handling.boolean(eigenvec, 'eigenvec')
|
|
637
|
+
error_handling.boolean(left, 'left')
|
|
638
|
+
if eigenvec:
|
|
639
|
+
if (self.ham.conj().T != self.ham).nnz:
|
|
640
|
+
if not left:
|
|
641
|
+
self.en, self.rn = LA.eig(self.ham.toarray())
|
|
642
|
+
else:
|
|
643
|
+
self.en, self.rn, self.ln = LA.eig(self.ham.toarray(), left=left)
|
|
644
|
+
ind = np.argsort(self.en.real)
|
|
645
|
+
self.en = self.en[ind]
|
|
646
|
+
self.rn = self.rn[:, ind]
|
|
647
|
+
if self.ln.size:
|
|
648
|
+
self.ln = self.ln[:, ind]
|
|
649
|
+
else:
|
|
650
|
+
self.en, self.rn = LA.eigh(self.ham.toarray())
|
|
651
|
+
self.intensity = np.abs(self.rn) ** 2
|
|
652
|
+
self.pola = np.zeros((self.lat.sites, len(self.lat.tags)))
|
|
653
|
+
for i, tag in enumerate(self.lat.tags):
|
|
654
|
+
self.pola[:, i] = np.sum(self.intensity[self.lat.coor['tag'] == tag, :], axis=0)
|
|
655
|
+
else:
|
|
656
|
+
if (self.ham.conj().T != self.ham).nnz:
|
|
657
|
+
self.en = LA.eigvals(self.ham.toarray())
|
|
658
|
+
ind = np.argsort(self.en.real)
|
|
659
|
+
self.en = self.en[ind]
|
|
660
|
+
else:
|
|
661
|
+
self.en = LA.eigvalsh(self.ham.toarray())
|
|
662
|
+
|
|
663
|
+
def get_ipr(self) -> None:
|
|
664
|
+
r'''
|
|
665
|
+
Get the Inverse Participation Ratio:
|
|
666
|
+
|
|
667
|
+
.. math::
|
|
668
|
+
|
|
669
|
+
IPR_n = |\sum_i\psi_i^{n}|^4\, .
|
|
670
|
+
'''
|
|
671
|
+
error_handling.empty_ndarray(self.rn, 'sys.get_eig(eigenvec=True)')
|
|
672
|
+
self.ipr = np.sum(self.intensity ** 2, axis=0)
|
|
673
|
+
|
|
674
|
+
def get_petermann(self) -> None:
|
|
675
|
+
r'''
|
|
676
|
+
Get the Petermann factor:
|
|
677
|
+
|
|
678
|
+
.. math::
|
|
679
|
+
|
|
680
|
+
K_n = \frac{\langle\psi_L^{n}|\psi_L^{n}\rangle\langle\psi_R^{n}|\psi_R^{n}\rangle}{\langle\psi_L^{n}|\psi_R^{n}\rangle}\, .
|
|
681
|
+
|
|
682
|
+
.. note::
|
|
683
|
+
|
|
684
|
+
LA.eig fixes the norm such that :math:`\langle\psi_L^{n}|\psi_L^{n}\rangle = 1` and :math:`\langle\psi_R^{n}|\psi_R^{n}\rangle = 1`.
|
|
685
|
+
'''
|
|
686
|
+
if not (self.ham.conj().T != self.ham).nnz:
|
|
687
|
+
self.petermann = np.ones(self.lat.sites)
|
|
688
|
+
return
|
|
689
|
+
error_handling.empty_ndarray(self.ln, 'sys.get_eig(eigenvec=True, left=True)')
|
|
690
|
+
left_right = np.sum(self.ln * np.conjugate(self.rn), axis=0).real
|
|
691
|
+
self.petermann = 1. / left_right ** 2
|
|
692
|
+
|
|
693
|
+
def get_intensity_pola_max(self, tag_pola: str) -> NDArray[np.float64]:
|
|
694
|
+
'''
|
|
695
|
+
Get the state with largest polarization on one sublattice.
|
|
696
|
+
|
|
697
|
+
:param tag_pola: One-character string. Sublattice tag.
|
|
698
|
+
|
|
699
|
+
:returns:
|
|
700
|
+
* **intensity** -- Intensity of max polarized state on *tag*.
|
|
701
|
+
'''
|
|
702
|
+
error_handling.empty_ndarray(self.rn, 'sys.get_eig(eigenvec=True)')
|
|
703
|
+
error_handling.tag(tag_pola, self.lat.tags)
|
|
704
|
+
i_tag = self.lat.tags == tag_pola
|
|
705
|
+
ind = np.argmax(self.pola[:, i_tag])
|
|
706
|
+
print('State with polarization: {:.5f}'.format(self.pola[ind, i_tag].item()))
|
|
707
|
+
return self.intensity[:, ind]
|
|
708
|
+
|
|
709
|
+
def get_intensity_pola_min(self, tag_pola: str) -> NDArray[np.float64]:
|
|
710
|
+
'''
|
|
711
|
+
Get the state with smallest polarization on one sublattice.
|
|
712
|
+
|
|
713
|
+
:param tag_pola: One-character string. Sublattice tag.
|
|
714
|
+
|
|
715
|
+
:returns:
|
|
716
|
+
* **intensity** -- Intensity of max polarized state on *tag*.
|
|
717
|
+
'''
|
|
718
|
+
error_handling.empty_ndarray(self.rn, 'sys.get_eig(eigenvec=True)')
|
|
719
|
+
error_handling.tag(tag_pola, self.lat.tags)
|
|
720
|
+
i_tag = self.lat.tags == tag_pola
|
|
721
|
+
ind = np.argmin(self.pola[:, i_tag])
|
|
722
|
+
print('State with polarization: {:.5f}'.format(self.pola[ind, i_tag].item()))
|
|
723
|
+
return self.intensity[:, ind]
|
|
724
|
+
|
|
725
|
+
def get_intensity_en(self, lims: tuple[float, float]) -> NDArray[np.float64]:
|
|
726
|
+
'''
|
|
727
|
+
Get, if any, the intensity of the sum of the states
|
|
728
|
+
between *lims[0]* and *lims[1]*.
|
|
729
|
+
|
|
730
|
+
:param lims: List. lims[0] energy min, lims[1] energy max.
|
|
731
|
+
|
|
732
|
+
:returns:
|
|
733
|
+
* **intensity** -- Sum of the intensities between (lims[0], lims[1]).
|
|
734
|
+
'''
|
|
735
|
+
error_handling.empty_ndarray(self.rn, 'sys.get_eig(eigenvec=True)')
|
|
736
|
+
error_handling.lims(lims)
|
|
737
|
+
ind = np.where((self.en > lims[0]) & (self.en < lims[1]))
|
|
738
|
+
ind = np.ravel(ind)
|
|
739
|
+
print('{} states between {} and {}'.format(len(ind), lims[0], lims[1]))
|
|
740
|
+
return np.sum(self.intensity[:, ind], axis=1)
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
# Backward-compatible lowercase alias (pre-0.2 API).
|
|
744
|
+
system = System
|