tbkit 0.2.0__tar.gz → 0.3.0__tar.gz
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-0.2.0/tbkit.egg-info → tbkit-0.3.0}/PKG-INFO +19 -4
- {tbkit-0.2.0 → tbkit-0.3.0}/README.md +16 -2
- {tbkit-0.2.0 → tbkit-0.3.0}/pyproject.toml +10 -2
- {tbkit-0.2.0 → tbkit-0.3.0}/tbkit/__init__.py +1 -1
- {tbkit-0.2.0 → tbkit-0.3.0}/tbkit/error_handling.py +24 -34
- {tbkit-0.2.0 → tbkit-0.3.0}/tbkit/graphene.py +72 -34
- {tbkit-0.2.0 → tbkit-0.3.0}/tbkit/kspace.py +35 -12
- {tbkit-0.2.0 → tbkit-0.3.0}/tbkit/lattice.py +10 -12
- {tbkit-0.2.0 → tbkit-0.3.0}/tbkit/plot.py +41 -28
- {tbkit-0.2.0 → tbkit-0.3.0}/tbkit/propagation.py +14 -11
- {tbkit-0.2.0 → tbkit-0.3.0}/tbkit/save.py +14 -6
- {tbkit-0.2.0 → tbkit-0.3.0}/tbkit/system.py +48 -36
- {tbkit-0.2.0 → tbkit-0.3.0/tbkit.egg-info}/PKG-INFO +19 -4
- {tbkit-0.2.0 → tbkit-0.3.0}/tbkit.egg-info/SOURCES.txt +1 -0
- {tbkit-0.2.0 → tbkit-0.3.0}/tests/test_error_handling.py +0 -7
- {tbkit-0.2.0 → tbkit-0.3.0}/tests/test_kspace.py +4 -1
- {tbkit-0.2.0 → tbkit-0.3.0}/tests/test_lattice.py +4 -1
- {tbkit-0.2.0 → tbkit-0.3.0}/tests/test_plot.py +6 -3
- {tbkit-0.2.0 → tbkit-0.3.0}/tests/test_propagation.py +26 -1
- tbkit-0.3.0/tests/test_regressions.py +355 -0
- {tbkit-0.2.0 → tbkit-0.3.0}/tests/test_save.py +9 -1
- {tbkit-0.2.0 → tbkit-0.3.0}/tests/test_system.py +20 -0
- {tbkit-0.2.0 → tbkit-0.3.0}/LICENSE +0 -0
- {tbkit-0.2.0 → tbkit-0.3.0}/setup.cfg +0 -0
- {tbkit-0.2.0 → tbkit-0.3.0}/tbkit/dos.py +0 -0
- {tbkit-0.2.0 → tbkit-0.3.0}/tbkit/lattices.py +0 -0
- {tbkit-0.2.0 → tbkit-0.3.0}/tbkit.egg-info/dependency_links.txt +0 -0
- {tbkit-0.2.0 → tbkit-0.3.0}/tbkit.egg-info/requires.txt +0 -0
- {tbkit-0.2.0 → tbkit-0.3.0}/tbkit.egg-info/top_level.txt +0 -0
- {tbkit-0.2.0 → tbkit-0.3.0}/tests/test_dos.py +0 -0
- {tbkit-0.2.0 → tbkit-0.3.0}/tests/test_graphene.py +0 -0
- {tbkit-0.2.0 → tbkit-0.3.0}/tests/test_lattices.py +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tbkit
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: A Python package to build and solve Tight-Binding models
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: A Python package to build and solve Tight-Binding models.
|
|
5
5
|
Author-email: Charles Poli <cpoli374@gmail.com>
|
|
6
|
+
Maintainer-email: cpoli <cpoli374@gmail.com>
|
|
6
7
|
License-Expression: BSD-3-Clause
|
|
7
8
|
Project-URL: Homepage, https://github.com/cpoli/tbkit
|
|
8
9
|
Classifier: Intended Audience :: Education
|
|
@@ -24,14 +25,15 @@ Requires-Dist: pydata-sphinx-theme>=0.15; extra == "docs"
|
|
|
24
25
|
Requires-Dist: sphinx-gallery>=0.15; extra == "docs"
|
|
25
26
|
Dynamic: license-file
|
|
26
27
|
|
|
27
|
-
# tbkit — a Tight-Binding package
|
|
28
|
+
# tbkit — a Tight-Binding package
|
|
28
29
|
|
|
29
30
|
[](https://github.com/cpoli/tbkit/actions/workflows/tests.yml)
|
|
31
|
+
[](https://cpoli.github.io/tbkit/coverage/)
|
|
30
32
|
[](https://cpoli.github.io/tbkit/)
|
|
31
33
|
[](pyproject.toml)
|
|
32
34
|
[](LICENSE)
|
|
33
35
|
|
|
34
|
-

|
|
36
|
+

|
|
35
37
|
|
|
36
38
|
**tbkit** is a Python package to build and solve Tight-Binding models, written
|
|
37
39
|
in vectorized NumPy/SciPy. It aims to make the mechanics of Tight-Binding
|
|
@@ -85,6 +87,13 @@ research prototyping.
|
|
|
85
87
|
|
|
86
88
|
Requires Python >= 3.10.
|
|
87
89
|
|
|
90
|
+
```bash
|
|
91
|
+
pip install tbkit
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
or, for an editable install from a clone (e.g. to run the test suite or
|
|
95
|
+
work on tbkit itself):
|
|
96
|
+
|
|
88
97
|
```bash
|
|
89
98
|
git clone https://github.com/cpoli/tbkit
|
|
90
99
|
cd tbkit
|
|
@@ -98,6 +107,9 @@ pip install -e ".[test]"
|
|
|
98
107
|
pytest tests/
|
|
99
108
|
```
|
|
100
109
|
|
|
110
|
+
The test suite has 100% line coverage of the `tbkit` package; see the
|
|
111
|
+
[HTML coverage report](https://cpoli.github.io/tbkit/coverage/).
|
|
112
|
+
|
|
101
113
|
## Quick start
|
|
102
114
|
|
|
103
115
|
Real-space flake, nearest-neighbor square lattice:
|
|
@@ -204,6 +216,7 @@ thumbnailed example gallery under `docs/source/api/gallery/`.
|
|
|
204
216
|
|
|
205
217
|
| Script | What it shows |
|
|
206
218
|
|---------------------------------------------------------------------------|----------------|
|
|
219
|
+
| [`tight_binding/plot_square_lattice_bands.py`](examples/tight_binding/plot_square_lattice_bands.py) | The square lattice: Bloch's theorem at its simplest, bands along Gamma-X-M-Gamma, the nested Fermi surface, and the van Hove singularity. |
|
|
207
220
|
| [`tight_binding/plot_graphene_bands.py`](examples/tight_binding/plot_graphene_bands.py) | Real-space flake + reciprocal-space band structure; graphene's Dirac point and Wallace's 1947 linear dispersion. |
|
|
208
221
|
| [`tight_binding/plot_visualizing_a_model.py`](examples/tight_binding/plot_visualizing_a_model.py) | `tbkit.plot.Plot`: lattice, spectrum with sublattice polarization, density of states, eigenstate intensity. |
|
|
209
222
|
| [`magnetic_field/plot_magnetic_field.py`](examples/magnetic_field/plot_magnetic_field.py) | Peierls substitution; an Aharonov-Bohm ring's flux-periodic spectrum. |
|
|
@@ -217,6 +230,8 @@ thumbnailed example gallery under `docs/source/api/gallery/`.
|
|
|
217
230
|
| [`topology/plot_edge_states.py`](examples/topology/plot_edge_states.py) | Zigzag graphene ribbon edge band; Kane-Mele helical edge states. |
|
|
218
231
|
| [`dynamics/plot_bloch_oscillations.py`](examples/dynamics/plot_bloch_oscillations.py) | Wannier-Stark ladder, its localization, and Bloch oscillations under a uniform tilt. |
|
|
219
232
|
| [`topology/plot_thouless_pump.py`](examples/topology/plot_thouless_pump.py) | The Rice-Mele model as a Thouless quantum pump: quantized Chern number and polarization winding. |
|
|
233
|
+
| [`strain/plot_pseudo_magnetic_field.py`](examples/strain/plot_pseudo_magnetic_field.py) | Triaxial strain as a gauge field: pseudo-Landau levels in graphene from a purely real Hamiltonian. |
|
|
234
|
+
| [`non_hermitian/plot_pt_symmetry.py`](examples/non_hermitian/plot_pt_symmetry.py) | PT symmetry, exceptional points, the Petermann factor, and a selectively amplified topological edge mode. |
|
|
220
235
|
|
|
221
236
|
The `examples/` directory also has five older Jupyter notebooks (graphene
|
|
222
237
|
flakes, kagome/Lieb/dumbbell lattices, disorder, strain, time propagation)
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
# tbkit — a Tight-Binding package
|
|
1
|
+
# tbkit — a Tight-Binding package
|
|
2
2
|
|
|
3
3
|
[](https://github.com/cpoli/tbkit/actions/workflows/tests.yml)
|
|
4
|
+
[](https://cpoli.github.io/tbkit/coverage/)
|
|
4
5
|
[](https://cpoli.github.io/tbkit/)
|
|
5
6
|
[](pyproject.toml)
|
|
6
7
|
[](LICENSE)
|
|
7
8
|
|
|
8
|
-

|
|
9
|
+

|
|
9
10
|
|
|
10
11
|
**tbkit** is a Python package to build and solve Tight-Binding models, written
|
|
11
12
|
in vectorized NumPy/SciPy. It aims to make the mechanics of Tight-Binding
|
|
@@ -59,6 +60,13 @@ research prototyping.
|
|
|
59
60
|
|
|
60
61
|
Requires Python >= 3.10.
|
|
61
62
|
|
|
63
|
+
```bash
|
|
64
|
+
pip install tbkit
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
or, for an editable install from a clone (e.g. to run the test suite or
|
|
68
|
+
work on tbkit itself):
|
|
69
|
+
|
|
62
70
|
```bash
|
|
63
71
|
git clone https://github.com/cpoli/tbkit
|
|
64
72
|
cd tbkit
|
|
@@ -72,6 +80,9 @@ pip install -e ".[test]"
|
|
|
72
80
|
pytest tests/
|
|
73
81
|
```
|
|
74
82
|
|
|
83
|
+
The test suite has 100% line coverage of the `tbkit` package; see the
|
|
84
|
+
[HTML coverage report](https://cpoli.github.io/tbkit/coverage/).
|
|
85
|
+
|
|
75
86
|
## Quick start
|
|
76
87
|
|
|
77
88
|
Real-space flake, nearest-neighbor square lattice:
|
|
@@ -178,6 +189,7 @@ thumbnailed example gallery under `docs/source/api/gallery/`.
|
|
|
178
189
|
|
|
179
190
|
| Script | What it shows |
|
|
180
191
|
|---------------------------------------------------------------------------|----------------|
|
|
192
|
+
| [`tight_binding/plot_square_lattice_bands.py`](examples/tight_binding/plot_square_lattice_bands.py) | The square lattice: Bloch's theorem at its simplest, bands along Gamma-X-M-Gamma, the nested Fermi surface, and the van Hove singularity. |
|
|
181
193
|
| [`tight_binding/plot_graphene_bands.py`](examples/tight_binding/plot_graphene_bands.py) | Real-space flake + reciprocal-space band structure; graphene's Dirac point and Wallace's 1947 linear dispersion. |
|
|
182
194
|
| [`tight_binding/plot_visualizing_a_model.py`](examples/tight_binding/plot_visualizing_a_model.py) | `tbkit.plot.Plot`: lattice, spectrum with sublattice polarization, density of states, eigenstate intensity. |
|
|
183
195
|
| [`magnetic_field/plot_magnetic_field.py`](examples/magnetic_field/plot_magnetic_field.py) | Peierls substitution; an Aharonov-Bohm ring's flux-periodic spectrum. |
|
|
@@ -191,6 +203,8 @@ thumbnailed example gallery under `docs/source/api/gallery/`.
|
|
|
191
203
|
| [`topology/plot_edge_states.py`](examples/topology/plot_edge_states.py) | Zigzag graphene ribbon edge band; Kane-Mele helical edge states. |
|
|
192
204
|
| [`dynamics/plot_bloch_oscillations.py`](examples/dynamics/plot_bloch_oscillations.py) | Wannier-Stark ladder, its localization, and Bloch oscillations under a uniform tilt. |
|
|
193
205
|
| [`topology/plot_thouless_pump.py`](examples/topology/plot_thouless_pump.py) | The Rice-Mele model as a Thouless quantum pump: quantized Chern number and polarization winding. |
|
|
206
|
+
| [`strain/plot_pseudo_magnetic_field.py`](examples/strain/plot_pseudo_magnetic_field.py) | Triaxial strain as a gauge field: pseudo-Landau levels in graphene from a purely real Hamiltonian. |
|
|
207
|
+
| [`non_hermitian/plot_pt_symmetry.py`](examples/non_hermitian/plot_pt_symmetry.py) | PT symmetry, exceptional points, the Petermann factor, and a selectively amplified topological edge mode. |
|
|
194
208
|
|
|
195
209
|
The `examples/` directory also has five older Jupyter notebooks (graphene
|
|
196
210
|
flakes, kagome/Lieb/dumbbell lattices, disorder, strain, time propagation)
|
|
@@ -4,11 +4,12 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "tbkit"
|
|
7
|
-
version = "0.
|
|
8
|
-
description = "A Python package to build and solve Tight-Binding models
|
|
7
|
+
version = "0.3.0"
|
|
8
|
+
description = "A Python package to build and solve Tight-Binding models."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "BSD-3-Clause"
|
|
11
11
|
authors = [{ name = "Charles Poli", email = "cpoli374@gmail.com" }]
|
|
12
|
+
maintainers = [{ name = "cpoli", email = "cpoli374@gmail.com" }]
|
|
12
13
|
requires-python = ">=3.10"
|
|
13
14
|
dependencies = [
|
|
14
15
|
"numpy>=1.24",
|
|
@@ -29,6 +30,13 @@ Homepage = "https://github.com/cpoli/tbkit"
|
|
|
29
30
|
test = ["pytest>=7", "pytest-cov>=4"]
|
|
30
31
|
docs = ["sphinx>=7", "pydata-sphinx-theme>=0.15", "sphinx-gallery>=0.15"]
|
|
31
32
|
|
|
33
|
+
[tool.pytest.ini_options]
|
|
34
|
+
# The suite is warning-free; keep it that way. A warning from tbkit itself is
|
|
35
|
+
# a defect (e.g. complex energies reaching matplotlib), and one from a test is
|
|
36
|
+
# usually the test misusing an API. Add a targeted `ignore::` entry below if a
|
|
37
|
+
# dependency ever emits something genuinely out of our control.
|
|
38
|
+
filterwarnings = ["error"]
|
|
39
|
+
|
|
32
40
|
[tool.setuptools.packages.find]
|
|
33
41
|
include = ["tbkit*"]
|
|
34
42
|
|
|
@@ -47,8 +47,8 @@ def positive_int_lim(var, var_name, nmax):
|
|
|
47
47
|
if var < 1:
|
|
48
48
|
raise ValueError('\n\nParameter {} must be a positive integer.\n'.format(var_name))
|
|
49
49
|
if var > nmax:
|
|
50
|
-
raise ValueError('\n\nParameter {} must be a positive integer
|
|
51
|
-
|
|
50
|
+
raise ValueError('\n\nParameter {} must be a positive integer '
|
|
51
|
+
'smaller than {}.\n'.format(var_name, nmax))
|
|
52
52
|
|
|
53
53
|
|
|
54
54
|
def real_number(var, var_name):
|
|
@@ -84,7 +84,7 @@ def positive_real_zero(var, var_name):
|
|
|
84
84
|
if not isinstance(var, (int, float)):
|
|
85
85
|
raise TypeError('\n\nParameter {} must be a real number.\n'.format(var_name))
|
|
86
86
|
if var < 0:
|
|
87
|
-
raise ValueError('\n\nParameter {} must be a positive number
|
|
87
|
+
raise ValueError('\n\nParameter {} must be a positive number or zero.\n'.format(var_name))
|
|
88
88
|
|
|
89
89
|
|
|
90
90
|
def negative_real(var, var_name):
|
|
@@ -187,7 +187,7 @@ def ndarray_empty(var, var_name):
|
|
|
187
187
|
:raises ValueError: Parameter *var* must not be an emptynumpy ndarray.
|
|
188
188
|
'''
|
|
189
189
|
if var.size == 0:
|
|
190
|
-
raise ValueError('\n\nParameter {} must not be an
|
|
190
|
+
raise ValueError('\n\nParameter {} must not be an empty numpy ndarray.\n'.format(var_name))
|
|
191
191
|
|
|
192
192
|
|
|
193
193
|
def list_tuple_2elem(var, var_name):
|
|
@@ -200,7 +200,7 @@ def list_tuple_2elem(var, var_name):
|
|
|
200
200
|
if var is None:
|
|
201
201
|
return
|
|
202
202
|
if not isinstance(var, (list, tuple)):
|
|
203
|
-
raise TypeError('\n\nParameter {} must be a list/tuple
|
|
203
|
+
raise TypeError('\n\nParameter {} must be a list/tuple.\n'.format(var_name))
|
|
204
204
|
if len(var) != 2:
|
|
205
205
|
raise ValueError('\n\nParameter {} must be a list/tuple of length two.\n'.format(var_name))
|
|
206
206
|
|
|
@@ -215,7 +215,7 @@ def tuple_2elem(var, var_name):
|
|
|
215
215
|
if var is None:
|
|
216
216
|
return
|
|
217
217
|
if not isinstance(var, tuple):
|
|
218
|
-
raise TypeError('\n\nParameter {} must be a tuple
|
|
218
|
+
raise TypeError('\n\nParameter {} must be a tuple.\n'.format(var_name))
|
|
219
219
|
if len(var) != 2:
|
|
220
220
|
raise ValueError('\n\nParameter {} must be a list/tuple of length two.\n'.format(var_name))
|
|
221
221
|
|
|
@@ -284,18 +284,20 @@ def prim_vec(prim_vec):
|
|
|
284
284
|
if not isinstance(prim_vec, list):
|
|
285
285
|
raise TypeError('\n\nParameter prim_vec must be a list.\n')
|
|
286
286
|
if not len(prim_vec) == 1 and not len(prim_vec) == 2:
|
|
287
|
-
raise ValueError('\n\nParameter prim_vec must be a list
|
|
288
|
-
'
|
|
287
|
+
raise ValueError('\n\nParameter prim_vec must be a list of length 1 '
|
|
288
|
+
'for 1D lattices, or of length 2 for 2D lattices.\n')
|
|
289
289
|
for coor in prim_vec:
|
|
290
290
|
if not isinstance(coor, tuple):
|
|
291
|
-
raise TypeError('\n\nParameter prim_vec contain tuples
|
|
291
|
+
raise TypeError('\n\nParameter prim_vec must contain tuples.\n')
|
|
292
292
|
if len(coor) != 2:
|
|
293
|
-
raise ValueError('\n\nParameter prim_vec contain tuples of length 2.\n')
|
|
293
|
+
raise ValueError('\n\nParameter prim_vec must contain tuples of length 2.\n')
|
|
294
294
|
if (not isinstance(coor[0], (int, float))) or \
|
|
295
295
|
(not isinstance(coor[1], (int, float))):
|
|
296
|
-
raise ValueError('\n\
|
|
296
|
+
raise ValueError('\n\nParameter prim_vec must contain tuples of '
|
|
297
|
+
'real numbers.\n')
|
|
297
298
|
if coor[0] ** 2 + coor[1] ** 2 < 0.1:
|
|
298
|
-
raise ValueError('\n\
|
|
299
|
+
raise ValueError('\n\nEach primitive vector must have a norm larger '
|
|
300
|
+
'than 0.1.\n')
|
|
299
301
|
|
|
300
302
|
|
|
301
303
|
def get_lattice(prim_vec, n1, n2):
|
|
@@ -374,22 +376,6 @@ def boundary_line(cx, cy, co):
|
|
|
374
376
|
raise TypeError('\n\nParameter co must be a real number.\n')
|
|
375
377
|
|
|
376
378
|
|
|
377
|
-
def ellipse(a, b):
|
|
378
|
-
'''
|
|
379
|
-
Check *ellipse_in* and *ellipse_out*.
|
|
380
|
-
:raises TypeError: Parameter a must be a positive number.
|
|
381
|
-
:raises TypeError: Parameter b must be a positive number.
|
|
382
|
-
'''
|
|
383
|
-
if not isinstance(a, (int, float)):
|
|
384
|
-
raise TypeError('\n\nParameter a must be a positive number.\n')
|
|
385
|
-
if not isinstance(b, (int, float)):
|
|
386
|
-
raise TypeError('\n\nParameter b must be a positive number.\n')
|
|
387
|
-
if a <= 0:
|
|
388
|
-
raise ValueError('\n\nParameter a must be a positive number.\n')
|
|
389
|
-
if b <= 0:
|
|
390
|
-
raise ValueError('\n\nParameter b must be a positive number.\n')
|
|
391
|
-
|
|
392
|
-
|
|
393
379
|
def sites(sites):
|
|
394
380
|
'''
|
|
395
381
|
Check if *get_lattice* has been called (*coor* not empty).
|
|
@@ -471,7 +457,7 @@ def set_hopping(list_hop, n_max):
|
|
|
471
457
|
if not isinstance(dic['n'], int):
|
|
472
458
|
raise TypeError('\n\n"n" value must be an integer.\n')
|
|
473
459
|
if not 0 < dic['n'] <= n_max:
|
|
474
|
-
raise ValueError('\n\n"n" value must be between 1 and
|
|
460
|
+
raise ValueError('\n\n"n" value must be between 1 and {}.\n'.format(n_max))
|
|
475
461
|
if not isinstance(dic['t'], (int, float, complex)):
|
|
476
462
|
raise TypeError('\n\n"t" value must be a real or complex number.\n')
|
|
477
463
|
if len(dic) == 3:
|
|
@@ -857,7 +843,8 @@ def set_hopping_kspace(list_hop, n_sites, ndim, spin=False):
|
|
|
857
843
|
if not all(isinstance(n, int) for n in dic['R']):
|
|
858
844
|
raise TypeError('\n\n"R" must be a tuple of integers.\n')
|
|
859
845
|
if dic['i'] == dic['j'] and dic['R'] == (0,) * ndim:
|
|
860
|
-
raise ValueError('\n\nUse kspace.set_onsite for i == j and R == 0
|
|
846
|
+
raise ValueError('\n\nUse kspace.set_onsite for i == j and R == 0 '
|
|
847
|
+
'(it accepts a 2x2 spin matrix when spin=True).\n')
|
|
861
848
|
if spin:
|
|
862
849
|
spin_matrix(dic['t'], '"t"')
|
|
863
850
|
elif not isinstance(dic['t'], (int, float, complex)):
|
|
@@ -871,7 +858,7 @@ def set_onsite_kspace(dict_onsite, tags, spin=False):
|
|
|
871
858
|
:raises TypeError: Parameter *dict_onsite* must be a dictionary.
|
|
872
859
|
:raises ValueError: keys must be tags.
|
|
873
860
|
:raises TypeError: values must be real or complex numbers (or, if
|
|
874
|
-
*spin*, a pair of real/complex numbers).
|
|
861
|
+
*spin*, a pair of real/complex numbers, or a 2x2 matrix).
|
|
875
862
|
'''
|
|
876
863
|
if not isinstance(dict_onsite, dict):
|
|
877
864
|
raise TypeError('\n\nParameter dict_onsite must be a dictionary.\n')
|
|
@@ -879,10 +866,13 @@ def set_onsite_kspace(dict_onsite, tags, spin=False):
|
|
|
879
866
|
if tag not in tags:
|
|
880
867
|
raise ValueError('\n\nParameter dict_onsite keys must be a tag.\n')
|
|
881
868
|
if spin and not isinstance(val, (int, float, complex)):
|
|
882
|
-
|
|
883
|
-
|
|
869
|
+
is_pair = (isinstance(val, (tuple, list)) and len(val) == 2
|
|
870
|
+
and all(isinstance(v, (int, float, complex)) for v in val))
|
|
871
|
+
is_mat = np.ndim(val) == 2 and np.shape(val) == (2, 2)
|
|
872
|
+
if not (is_pair or is_mat):
|
|
884
873
|
raise TypeError('\n\nParameter dict_onsite values must be a number, or, '
|
|
885
|
-
'if spin, a pair of numbers (E_up, E_down)
|
|
874
|
+
'if spin, a pair of numbers (E_up, E_down) or a '
|
|
875
|
+
'2x2 matrix.\n')
|
|
886
876
|
elif not spin and not isinstance(val, (int, float, complex)):
|
|
887
877
|
raise TypeError('\n\nParameter dict_onsite values must be real and/or complex numbers.\n')
|
|
888
878
|
|
|
@@ -106,40 +106,74 @@ class GrapheneSystem(System):
|
|
|
106
106
|
def __init__(self, lat: Lattice) -> None:
|
|
107
107
|
System.__init__(self, lat)
|
|
108
108
|
|
|
109
|
-
def
|
|
109
|
+
def _strain_projection(self) -> tuple[NDArray[np.int64], NDArray[np.float64], NDArray[np.float64]]:
|
|
110
|
+
r'''
|
|
111
|
+
Private method.
|
|
112
|
+
|
|
113
|
+
Get, for every nearest-neighbor bond, the projection
|
|
114
|
+
|
|
115
|
+
.. math::
|
|
116
|
+
|
|
117
|
+
s_{ij} = \hat{\boldsymbol\delta}_{ij}\cdot\mathbf{r}_{ij}
|
|
118
|
+
|
|
119
|
+
of its midpoint :math:`\mathbf{r}_{ij}` on its (outward-oriented)
|
|
120
|
+
direction :math:`\hat{\boldsymbol\delta}_{ij}`, so that the strained
|
|
121
|
+
hopping is :math:`t_{ij} = t(1 + \tfrac14\beta s_{ij})`.
|
|
122
|
+
|
|
123
|
+
:returns:
|
|
124
|
+
* **ind_up** -- Integer ndarray, shape (nbonds, 2). Bond indices.
|
|
125
|
+
* **ang** -- Real ndarray. Bond angles, in degrees.
|
|
126
|
+
* **s** -- Real ndarray. The projections above.
|
|
110
127
|
'''
|
|
111
|
-
|
|
112
|
-
|
|
128
|
+
self.get_distances()
|
|
129
|
+
ind = np.argwhere(np.isclose(self.dist_uni[1], self.vec_hop['dis'], atol=ATOL))
|
|
130
|
+
ind_up = ind[ind[:, 1] > ind[:, 0]]
|
|
131
|
+
ang = self.vec_hop['ang'][ind_up[:, 0], ind_up[:, 1]].copy()
|
|
132
|
+
# orient the three bond families consistently outwards
|
|
133
|
+
ang[np.isclose(30., ang, atol=ATOL)] = -150.
|
|
134
|
+
ang[np.isclose(150., ang, atol=ATOL)] = -30.
|
|
135
|
+
x_center = .5 * (self.lat.coor['x'][ind_up[:, 0]] + self.lat.coor['x'][ind_up[:, 1]])
|
|
136
|
+
y_center = .5 * (self.lat.coor['y'][ind_up[:, 0]] + self.lat.coor['y'][ind_up[:, 1]])
|
|
137
|
+
s = (np.cos(PI / 180 * ang) * x_center + np.sin(PI / 180 * ang) * y_center)
|
|
138
|
+
return ind_up, ang, s
|
|
139
|
+
|
|
140
|
+
def set_hop_linear_strain(self, t: complex, beta: float) -> None:
|
|
141
|
+
r'''
|
|
142
|
+
Set nearest-neighbor hoppings according to a linear triaxial strain:
|
|
143
|
+
|
|
144
|
+
.. math::
|
|
145
|
+
|
|
146
|
+
t_{ij} = t\left(1 + \tfrac14\beta\,
|
|
147
|
+
\hat{\boldsymbol\delta}_{ij}\cdot\mathbf{r}_{ij}\right)
|
|
148
|
+
|
|
149
|
+
with :math:`\hat{\boldsymbol\delta}_{ij}` the bond direction and
|
|
150
|
+
:math:`\mathbf{r}_{ij}` its midpoint. The strain is measured from the
|
|
151
|
+
coordinate origin, so centre the flake on it (see *lattice.center*)
|
|
152
|
+
before calling this.
|
|
153
|
+
|
|
113
154
|
:param t: Hopping value without strain.
|
|
114
|
-
:param beta: Strength of the strain.
|
|
155
|
+
:param beta: Strength of the strain. See *get_beta_lims* for the
|
|
156
|
+
range that keeps every hopping positive.
|
|
115
157
|
'''
|
|
116
158
|
error_handling.number(t, 't')
|
|
117
159
|
error_handling.real_number(beta, 'beta')
|
|
118
|
-
self.
|
|
119
|
-
ind = np.argwhere(np.isclose(self.dist_uni[1], self.vec_hop['dis'], atol=ATOL))
|
|
120
|
-
ind_up = ind[ind[:, 1] > ind[:, 0]]
|
|
160
|
+
ind_up, ang, s = self._strain_projection()
|
|
121
161
|
self.hop = np.zeros(len(ind_up), dtype=HOP_DTYPE)
|
|
122
162
|
self.hop['n'] = 1
|
|
123
163
|
self.hop['i'] = ind_up[:, 0]
|
|
124
164
|
self.hop['j'] = ind_up[:, 1]
|
|
125
165
|
self.hop['ang'] = self.vec_hop['ang'][ind_up[:, 0], ind_up[:, 1]]
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
self.hop['
|
|
129
|
-
x_center = .5 * (self.lat.coor['x'][ind_up[:, 0]] + self.lat.coor['x'][ind_up[:, 1]])
|
|
130
|
-
y_center = .5 * (self.lat.coor['y'][ind_up[:, 0]] + self.lat.coor['y'][ind_up[:, 1]])
|
|
131
|
-
self.hop['t'] = t * (1. + 0.25 * beta * (np.cos(PI / 180 * self.hop['ang']) * x_center +
|
|
132
|
-
np.sin(PI / 180 * self.hop['ang']) * y_center))
|
|
133
|
-
# back to the former angle
|
|
134
|
-
self.hop['ang'][np.isclose(-150., self.hop['ang'])] = 30.
|
|
135
|
-
self.hop['ang'][np.isclose(-30., self.hop['ang'])] = 150.
|
|
166
|
+
self.hop['tag'] = npc.add(self.lat.coor['tag'][ind_up[:, 0]],
|
|
167
|
+
self.lat.coor['tag'][ind_up[:, 1]])
|
|
168
|
+
self.hop['t'] = t * (1. + 0.25 * beta * s)
|
|
136
169
|
|
|
137
170
|
def get_butterfly(self, t: complex, N: int) -> None:
|
|
138
|
-
'''
|
|
171
|
+
'''
|
|
139
172
|
Get energies depending on strain.
|
|
140
173
|
|
|
141
174
|
:param t: Unstrained hopping value.
|
|
142
|
-
:param N:
|
|
175
|
+
:param N: Positive integer. Number of strain values between the
|
|
176
|
+
minimal and maximal strains given by *get_beta_lims*.
|
|
143
177
|
'''
|
|
144
178
|
error_handling.number(t, 't')
|
|
145
179
|
error_handling.positive_int(N, 'N')
|
|
@@ -147,25 +181,29 @@ class GrapheneSystem(System):
|
|
|
147
181
|
self.betas = np.linspace(beta_lims[0], beta_lims[1], N)
|
|
148
182
|
self.butterfly = np.zeros((N, self.lat.sites))
|
|
149
183
|
for i, beta in enumerate(self.betas):
|
|
150
|
-
self.set_hop_linear_strain(t=
|
|
184
|
+
self.set_hop_linear_strain(t=t, beta=beta)
|
|
151
185
|
self.get_ham()
|
|
152
186
|
self.butterfly[i] = LA.eigvalsh(self.ham.toarray())
|
|
153
187
|
|
|
154
188
|
def get_beta_lims(self) -> NDArray[np.float64]:
|
|
155
|
-
'''
|
|
156
|
-
Get the extremal values
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
189
|
+
r'''
|
|
190
|
+
Get the extremal strain values keeping every hopping positive.
|
|
191
|
+
|
|
192
|
+
Under *set_hop_linear_strain* a bond's amplitude is
|
|
193
|
+
:math:`t(1+\tfrac14\beta s_{ij})`, so it stays positive for every bond
|
|
194
|
+
iff :math:`-4/\max_{ij} s_{ij} < \beta < -4/\min_{ij} s_{ij}`.
|
|
195
|
+
|
|
196
|
+
:returns:
|
|
197
|
+
* **beta_lims** -- Real ndarray of length 2, ``[beta_min, beta_max]``
|
|
198
|
+
(ascending). A bound is infinite if the corresponding
|
|
199
|
+
:math:`s_{ij}` never takes that sign.
|
|
200
|
+
'''
|
|
201
|
+
_, _, s = self._strain_projection()
|
|
202
|
+
eps = 1e-6
|
|
203
|
+
s_min, s_max = s.min(), s.max()
|
|
204
|
+
beta_min = -4. / s_max + eps if s_max > 0 else -np.inf
|
|
205
|
+
beta_max = -4. / s_min - eps if s_min < 0 else np.inf
|
|
206
|
+
return np.array([beta_min, beta_max])
|
|
169
207
|
|
|
170
208
|
|
|
171
209
|
# Backward-compatible camelCase aliases (pre-0.2 API).
|
|
@@ -97,6 +97,9 @@ class KSpace():
|
|
|
97
97
|
self.norb = 2*self.n_sites if spin else self.n_sites
|
|
98
98
|
self.tags = np.array([dic['tag'] for dic in lat.unit_cell])
|
|
99
99
|
self.onsite = np.zeros(self.norb, 'c16')
|
|
100
|
+
# spin-off-diagonal onsite terms (in-plane Zeeman, onsite Rashba):
|
|
101
|
+
# they have no place on the diagonal `onsite` array.
|
|
102
|
+
self._onsite_offdiag = np.zeros((self.norb, self.norb), 'c16')
|
|
100
103
|
self._hop = [] # list of (i, j, R_cartesian (np.ndarray), t)
|
|
101
104
|
self.rec_vec = reciprocal_vectors(lat.prim_vec)
|
|
102
105
|
self.ks = np.array([]) # k-points of the last band-structure calculation
|
|
@@ -109,24 +112,35 @@ class KSpace():
|
|
|
109
112
|
Set the onsite energies, by sublattice tag.
|
|
110
113
|
|
|
111
114
|
:param dict_onsite: Dictionary. key: tag, val: onsite energy
|
|
112
|
-
(a plain number), or, if ``spin=True``,
|
|
113
|
-
|
|
114
|
-
|
|
115
|
+
(a plain number), or, if ``spin=True``, a plain number (applied
|
|
116
|
+
equally to both spins), a pair ``(E_up, E_down)`` of numbers (a
|
|
117
|
+
spin splitting along z), or a 2x2 complex matrix (a general spin
|
|
118
|
+
structure, e.g. an in-plane Zeeman field built from :data:`PAULI`).
|
|
115
119
|
|
|
116
120
|
Example usage::
|
|
117
121
|
|
|
118
122
|
kag.set_onsite({'a': 1., 'b': -1.})
|
|
119
123
|
# spinful: same onsite energy for both spins on 'a', a Zeeman
|
|
120
|
-
# splitting on 'b':
|
|
124
|
+
# splitting along z on 'b':
|
|
121
125
|
kag_spin.set_onsite({'a': 1., 'b': (1., -1.)})
|
|
126
|
+
# spinful: an in-plane Zeeman field on 'a':
|
|
127
|
+
kag_spin.set_onsite({'a': Bx*PAULI['x']})
|
|
122
128
|
'''
|
|
123
129
|
error_handling.set_onsite_kspace(dict_onsite, self.lat.tags, self.spin)
|
|
124
130
|
for tag, val in dict_onsite.items():
|
|
125
131
|
sites = np.where(self.tags == tag)[0]
|
|
126
132
|
if self.spin:
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
133
|
+
if isinstance(val, (int, float, complex)):
|
|
134
|
+
block = val * PAULI['0']
|
|
135
|
+
elif np.ndim(val) == 2:
|
|
136
|
+
block = np.asarray(val, 'c16')
|
|
137
|
+
else:
|
|
138
|
+
block = np.diag(np.asarray(val, 'c16'))
|
|
139
|
+
self.onsite[2*sites] = block[0, 0]
|
|
140
|
+
self.onsite[2*sites + 1] = block[1, 1]
|
|
141
|
+
for site in sites:
|
|
142
|
+
self._onsite_offdiag[2*site, 2*site + 1] = block[0, 1]
|
|
143
|
+
self._onsite_offdiag[2*site + 1, 2*site] = block[1, 0]
|
|
130
144
|
else:
|
|
131
145
|
self.onsite[sites] = val
|
|
132
146
|
|
|
@@ -187,16 +201,23 @@ class KSpace():
|
|
|
187
201
|
r'''
|
|
188
202
|
Get the dense Bloch Hamiltonian :math:`H(\mathbf{k})`.
|
|
189
203
|
|
|
190
|
-
:param k: Tuple/list/ndarray of one/two real numbers.
|
|
191
|
-
in the same Cartesian frame as *prim_vec*.
|
|
204
|
+
:param k: Tuple/list/ndarray of one/two real numbers. In 2D, the
|
|
205
|
+
:math:`\mathbf{k}` point in the same Cartesian frame as *prim_vec*.
|
|
206
|
+
In 1D, the crystal momentum *along* the primitive vector (so the
|
|
207
|
+
Brillouin zone spans :math:`2\pi/|\mathbf{a}_1|`), which
|
|
208
|
+
coincides with :math:`k_x` for a chain aligned with :math:`x`.
|
|
192
209
|
|
|
193
210
|
:returns:
|
|
194
211
|
* **ham** -- Complex ndarray, shape (norb, norb).
|
|
195
212
|
'''
|
|
196
213
|
error_handling.k_vector(k, 'k', self.dim)
|
|
197
214
|
k_cart = np.zeros(2)
|
|
198
|
-
|
|
199
|
-
|
|
215
|
+
if self.dim == 1:
|
|
216
|
+
a1 = np.asarray(self.lat.prim_vec[0], dtype='f8')
|
|
217
|
+
k_cart = np.ravel(np.asarray(k, dtype='f8'))[0] * a1 / np.linalg.norm(a1)
|
|
218
|
+
else:
|
|
219
|
+
k_cart[:self.dim] = k
|
|
220
|
+
ham = np.diag(self.onsite).astype('c16') + self._onsite_offdiag
|
|
200
221
|
for i, j, R_cart, t in self._hop:
|
|
201
222
|
ham[i, j] += t * np.exp(1j * np.dot(k_cart, R_cart))
|
|
202
223
|
return ham
|
|
@@ -283,7 +304,9 @@ class KSpace():
|
|
|
283
304
|
rec_vec = [np.array(b) for b in self.rec_vec]
|
|
284
305
|
if self.dim == 1:
|
|
285
306
|
f1 = np.arange(nk[0]) / nk[0]
|
|
286
|
-
|
|
307
|
+
# k is the crystal momentum along the chain (see get_ham), so the
|
|
308
|
+
# zone spans |b1| = 2*pi/|a1|.
|
|
309
|
+
ks = f1[:, None] * np.linalg.norm(rec_vec[0])
|
|
287
310
|
return [f1], ks
|
|
288
311
|
f1, f2 = np.meshgrid(np.arange(nk[0])/nk[0], np.arange(nk[1])/nk[1], indexing='ij')
|
|
289
312
|
ks = (f1.ravel()[:, None] * rec_vec[0][None, :]
|
|
@@ -177,7 +177,7 @@ class Lattice():
|
|
|
177
177
|
|
|
178
178
|
def shift_y(self, shift: float) -> None:
|
|
179
179
|
'''
|
|
180
|
-
Shift
|
|
180
|
+
Shift the y coordinates.
|
|
181
181
|
|
|
182
182
|
:param shift: Real number. Shift value.
|
|
183
183
|
'''
|
|
@@ -222,7 +222,6 @@ class Lattice():
|
|
|
222
222
|
|
|
223
223
|
(x-x_0)^2/a^2+(y-y_0)^2/b^2 < 1\, .
|
|
224
224
|
|
|
225
|
-
:param list_hop: List of Dictionary (see set_hopping definition).
|
|
226
225
|
:param rx: Positive Real number. Radius along :math:`x`.
|
|
227
226
|
:param ry: Positive Real number. Radius along :math:`y`.
|
|
228
227
|
:param x0: Real number. :math:`x` center.
|
|
@@ -246,7 +245,6 @@ class Lattice():
|
|
|
246
245
|
(x-x_0)^2/a^2+(y-y_0)^2/b^2 > 1\, .
|
|
247
246
|
|
|
248
247
|
|
|
249
|
-
:param list_hop: List of Dictionary (see set_hopping definition).
|
|
250
248
|
:param rx: Positive Real number. Radius along :math:`x`.
|
|
251
249
|
:param ry: Positive Real number. Radius along :math:`y`.
|
|
252
250
|
:param x0: Real number. :math:`x` center.
|
|
@@ -271,18 +269,18 @@ class Lattice():
|
|
|
271
269
|
|
|
272
270
|
def rotation(self, theta: float) -> None:
|
|
273
271
|
r'''
|
|
274
|
-
Rotate the lattice structure by the angle
|
|
272
|
+
Rotate the lattice structure about the origin by the angle
|
|
273
|
+
:math:`\theta`.
|
|
275
274
|
|
|
276
275
|
:param theta: Rotation angle in degrees.
|
|
277
276
|
'''
|
|
278
277
|
error_handling.empty_coor(self.coor)
|
|
279
278
|
error_handling.real_number(theta, 'theta')
|
|
280
|
-
theta *= PI /
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
self.coor['y'] = y * np.cos(theta) + x* np.sin(theta) + dic['r0'][1]
|
|
279
|
+
theta *= PI / 180
|
|
280
|
+
x = self.coor['x'].copy()
|
|
281
|
+
y = self.coor['y'].copy()
|
|
282
|
+
self.coor['x'] = x * np.cos(theta) - y * np.sin(theta)
|
|
283
|
+
self.coor['y'] = y * np.cos(theta) + x * np.sin(theta)
|
|
286
284
|
|
|
287
285
|
def clean_coor(self) -> None:
|
|
288
286
|
'''
|
|
@@ -370,10 +368,10 @@ class Lattice():
|
|
|
370
368
|
figsize: tuple[float, float] | None = None,
|
|
371
369
|
) -> Figure:
|
|
372
370
|
'''
|
|
373
|
-
Plot lattice in
|
|
371
|
+
Plot the lattice in real space.
|
|
374
372
|
|
|
375
373
|
:param ms: Positive number. Default value 20. Markersize.
|
|
376
|
-
:param fs:
|
|
374
|
+
:param fs: Positive number. Default value 20. Fontsize.
|
|
377
375
|
:param plt_index: Boolean. Default value False. Plot site labels.
|
|
378
376
|
:param axis: Boolean. Default value False. Plot axis.
|
|
379
377
|
:param figsize: Tuple. Default value None. Figsize.
|