quant-met 0.0.27__py3-none-any.whl → 0.1.1__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.
- quant_met/__init__.py +2 -7
- quant_met/bdg/__init__.py +26 -0
- quant_met/bdg/bdg_hamiltonian.py +97 -0
- quant_met/bdg/gap_equation.py +127 -0
- quant_met/bdg/sc_current.py +60 -0
- quant_met/bdg/superfluid_weight.py +110 -0
- quant_met/cli/__init__.py +0 -5
- quant_met/cli/crit_temp.py +18 -16
- quant_met/cli/main.py +8 -5
- quant_met/cli/q_analysis.py +60 -0
- quant_met/cli/q_loop.py +95 -0
- quant_met/cli/scf.py +44 -23
- quant_met/parameters/__init__.py +0 -26
- quant_met/parameters/control.py +57 -0
- quant_met/parameters/main.py +2 -55
- quant_met/quantum_geometry/__init__.py +13 -0
- quant_met/quantum_geometry/qgt.py +37 -0
- quant_met/routines/__init__.py +22 -0
- quant_met/routines/analyse_q_data.py +226 -0
- quant_met/routines/loop_over_q.py +154 -0
- quant_met/{mean_field → routines}/search_crit_temp.py +71 -48
- quant_met/{mean_field → routines}/self_consistency.py +32 -28
- quant_met/utils.py +1 -6
- {quant_met-0.0.27.dist-info → quant_met-0.1.1.dist-info}/METADATA +5 -11
- quant_met-0.1.1.dist-info/RECORD +28 -0
- quant_met/cli/_utils.py +0 -32
- quant_met/geometry/__init__.py +0 -36
- quant_met/geometry/base_lattice.py +0 -100
- quant_met/geometry/bz_path.py +0 -90
- quant_met/geometry/graphene.py +0 -48
- quant_met/geometry/square.py +0 -47
- quant_met/mean_field/__init__.py +0 -38
- quant_met/mean_field/_utils.py +0 -17
- quant_met/mean_field/hamiltonians/__init__.py +0 -34
- quant_met/mean_field/hamiltonians/base_hamiltonian.py +0 -793
- quant_met/mean_field/hamiltonians/dressed_graphene.py +0 -118
- quant_met/mean_field/hamiltonians/graphene.py +0 -95
- quant_met/mean_field/hamiltonians/one_band_tight_binding.py +0 -70
- quant_met/mean_field/hamiltonians/three_band_tight_binding.py +0 -85
- quant_met/mean_field/hamiltonians/two_band_tight_binding.py +0 -76
- quant_met/parameters/hamiltonians.py +0 -182
- quant_met/plotting/__init__.py +0 -31
- quant_met/plotting/plotting.py +0 -215
- quant_met-0.0.27.dist-info/RECORD +0 -33
- {quant_met-0.0.27.dist-info → quant_met-0.1.1.dist-info}/WHEEL +0 -0
- {quant_met-0.0.27.dist-info → quant_met-0.1.1.dist-info}/entry_points.txt +0 -0
- {quant_met-0.0.27.dist-info → quant_met-0.1.1.dist-info}/licenses/LICENSE.txt +0 -0
@@ -1,793 +0,0 @@
|
|
1
|
-
# SPDX-FileCopyrightText: 2024 Tjark Sievers
|
2
|
-
# SPDX-FileCopyrightText: 2025 Tjark Sievers
|
3
|
-
#
|
4
|
-
# SPDX-License-Identifier: MIT
|
5
|
-
|
6
|
-
"""Provides the base class for Hamiltonians."""
|
7
|
-
|
8
|
-
import pathlib
|
9
|
-
from abc import ABC, abstractmethod
|
10
|
-
from typing import Generic, Self, TypeVar
|
11
|
-
|
12
|
-
import h5py
|
13
|
-
import numpy as np
|
14
|
-
import numpy.typing as npt
|
15
|
-
import pandas as pd
|
16
|
-
from numba import jit
|
17
|
-
|
18
|
-
from quant_met.geometry import BaseLattice
|
19
|
-
from quant_met.mean_field._utils import _check_valid_array
|
20
|
-
from quant_met.parameters.hamiltonians import GenericParameters, HamiltonianParameters
|
21
|
-
from quant_met.utils import fermi_dirac
|
22
|
-
|
23
|
-
GenericHamiltonian = TypeVar("GenericHamiltonian", bound="BaseHamiltonian[HamiltonianParameters]")
|
24
|
-
|
25
|
-
|
26
|
-
class BaseHamiltonian(Generic[GenericParameters], ABC):
|
27
|
-
"""Base class for Hamiltonians.
|
28
|
-
|
29
|
-
This abstract class provides the essential framework for defining various
|
30
|
-
Hamiltonians used in solid-state physics. It includes methods for constructing
|
31
|
-
the Hamiltonian based on a set of parameters, calculating properties such as
|
32
|
-
energy bands, conducting derivatives, and diagonalizing the Hamiltonian to
|
33
|
-
obtain eigenstates and eigenvalues. Subclasses should implement methods to
|
34
|
-
provide specific Hamiltonian forms.
|
35
|
-
|
36
|
-
Parameters
|
37
|
-
----------
|
38
|
-
parameters : :class:`quant_met.parameters.hamiltonians.GenericParameters`
|
39
|
-
An object containing the necessary parameters to define the Hamiltonian,
|
40
|
-
including lattice parameters, critical constants, and Hubbard interaction
|
41
|
-
strengths.
|
42
|
-
|
43
|
-
Attributes
|
44
|
-
----------
|
45
|
-
name : str
|
46
|
-
Name or identifier of the Hamiltonian.
|
47
|
-
beta : float
|
48
|
-
Inverse temperature (related to thermal excitations).
|
49
|
-
q : :class:`numpy.ndarray`
|
50
|
-
A two-dimensional array defining a momentum offset, typically in
|
51
|
-
reciprocal space.
|
52
|
-
lattice : :class:`quant_met.geometry.BaseLattice`
|
53
|
-
The lattice structure in which the Hamiltonian is defined.
|
54
|
-
hubbard_int_orbital_basis : :class:`numpy.ndarray`
|
55
|
-
Interaction terms for Hubbard-type models represented in orbital basis.
|
56
|
-
number_of_bands : int
|
57
|
-
The total number of bands calculated based on the orbital basis provided.
|
58
|
-
delta_orbital_basis : :class:`numpy.ndarray`
|
59
|
-
An array initialized for the order parameter or pairing potentials.
|
60
|
-
"""
|
61
|
-
|
62
|
-
def __init__(self, parameters: GenericParameters) -> None:
|
63
|
-
self.name = parameters.name
|
64
|
-
self.beta = parameters.beta
|
65
|
-
self.q = parameters.q if parameters.q is not None else np.zeros(2)
|
66
|
-
|
67
|
-
self.lattice = self.setup_lattice(parameters)
|
68
|
-
self.hubbard_int_orbital_basis = parameters.hubbard_int_orbital_basis
|
69
|
-
self.number_of_bands = len(self.hubbard_int_orbital_basis)
|
70
|
-
self.delta_orbital_basis = np.zeros(self.number_of_bands, dtype=np.complex128)
|
71
|
-
|
72
|
-
@abstractmethod
|
73
|
-
def setup_lattice(self, parameters: GenericParameters) -> BaseLattice: # pragma: no cover
|
74
|
-
"""Set up the lattice based on the provided parameters.
|
75
|
-
|
76
|
-
Parameters
|
77
|
-
----------
|
78
|
-
parameters : GenericParameters
|
79
|
-
Input parameters containing necessary information for lattice construction.
|
80
|
-
|
81
|
-
Returns
|
82
|
-
-------
|
83
|
-
BaseLattice
|
84
|
-
An instance of a lattice object configured according to the input parameters.
|
85
|
-
"""
|
86
|
-
|
87
|
-
@classmethod
|
88
|
-
@abstractmethod
|
89
|
-
def get_parameters_model(cls) -> type[GenericParameters]: # pragma: no cover
|
90
|
-
"""Return the specific parameters model for the subclass.
|
91
|
-
|
92
|
-
This method should provide the structure of parameters required by
|
93
|
-
subclasses to initialize the Hamiltonian.
|
94
|
-
|
95
|
-
Returns
|
96
|
-
-------
|
97
|
-
type
|
98
|
-
The parameters model class type specific to the Hamiltonian subclass.
|
99
|
-
"""
|
100
|
-
|
101
|
-
@abstractmethod
|
102
|
-
def hamiltonian(
|
103
|
-
self, k: npt.NDArray[np.floating]
|
104
|
-
) -> npt.NDArray[np.complexfloating]: # pragma: no cover
|
105
|
-
"""Return the normal state Hamiltonian.
|
106
|
-
|
107
|
-
Parameters
|
108
|
-
----------
|
109
|
-
k : numpy.ndarray
|
110
|
-
List of k points in reciprocal space.
|
111
|
-
|
112
|
-
Returns
|
113
|
-
-------
|
114
|
-
class `numpy.ndarray`
|
115
|
-
The Hamiltonian matrix evaluated at the provided k points.
|
116
|
-
"""
|
117
|
-
|
118
|
-
@abstractmethod
|
119
|
-
def hamiltonian_derivative(
|
120
|
-
self, k: npt.NDArray[np.floating], direction: str
|
121
|
-
) -> npt.NDArray[np.complexfloating]: # pragma: no cover
|
122
|
-
"""Calculate the spatial derivative of the Hamiltonian.
|
123
|
-
|
124
|
-
Parameters
|
125
|
-
----------
|
126
|
-
k : numpy.ndarray
|
127
|
-
List of k points in reciprocal space.
|
128
|
-
direction : str
|
129
|
-
Direction for the derivative, either 'x' or 'y'.
|
130
|
-
|
131
|
-
Returns
|
132
|
-
-------
|
133
|
-
:class: `numpy.ndarray`
|
134
|
-
The derivative of the Hamiltonian matrix in the specified direction.
|
135
|
-
"""
|
136
|
-
|
137
|
-
def save(self, filename: pathlib.Path) -> None:
|
138
|
-
"""Save the Hamiltonian configuration as an HDF5 file.
|
139
|
-
|
140
|
-
This method stores Hamiltonian parameters and the delta orbital basis in
|
141
|
-
a specified HDF5 file format for later retrieval.
|
142
|
-
|
143
|
-
Parameters
|
144
|
-
----------
|
145
|
-
filename : class:`pathlib.Path`
|
146
|
-
The file path where the Hamiltonian will be saved. Must end with .hdf5.
|
147
|
-
"""
|
148
|
-
with h5py.File(f"{filename.absolute()}", "w") as f:
|
149
|
-
f.create_dataset("delta", data=self.delta_orbital_basis)
|
150
|
-
for key, value in vars(self).items():
|
151
|
-
if key != "lattice":
|
152
|
-
f.attrs[key.strip("_")] = value
|
153
|
-
f.attrs["lattice_constant"] = self.lattice.lattice_constant
|
154
|
-
|
155
|
-
@classmethod
|
156
|
-
def from_file(cls, filename: pathlib.Path) -> Self:
|
157
|
-
"""Initialize a Hamiltonian from a previously saved HDF5 file.
|
158
|
-
|
159
|
-
This class method allows users to reconstruct a Hamiltonian object
|
160
|
-
from saved attributes and matrix configurations stored in an HDF5 file.
|
161
|
-
|
162
|
-
Parameters
|
163
|
-
----------
|
164
|
-
filename : :class:`pathlib.Path`
|
165
|
-
The file path to the HDF5 file containing Hamiltonian data.
|
166
|
-
|
167
|
-
Returns
|
168
|
-
-------
|
169
|
-
class:`BaseHamiltonian[GenericParameters]`
|
170
|
-
An instance of the Hamiltonian initialized with data from the file.
|
171
|
-
"""
|
172
|
-
with h5py.File(str(filename), "r") as f:
|
173
|
-
config_dict = dict(f.attrs.items())
|
174
|
-
config_dict["delta"] = f["delta"][()]
|
175
|
-
|
176
|
-
parameters_model = cls.get_parameters_model()
|
177
|
-
parameters = parameters_model.model_validate(config_dict)
|
178
|
-
return cls(parameters=parameters)
|
179
|
-
|
180
|
-
def bdg_hamiltonian(self, k: npt.NDArray[np.floating]) -> npt.NDArray[np.complexfloating]:
|
181
|
-
"""Generate the Bogoliubov-de Gennes (BdG) Hamiltonian.
|
182
|
-
|
183
|
-
The BdG Hamiltonian incorporates pairing interactions and is used to
|
184
|
-
study superfluid and superconducting phases. This method constructs a
|
185
|
-
2x2 block Hamiltonian based on the normal state Hamiltonian and the
|
186
|
-
pairing terms.
|
187
|
-
|
188
|
-
Parameters
|
189
|
-
----------
|
190
|
-
k : :class:`numpy.ndarray`
|
191
|
-
List of k points in reciprocal space.
|
192
|
-
|
193
|
-
Returns
|
194
|
-
-------
|
195
|
-
:class:`numpy.ndarray`
|
196
|
-
The BdG Hamiltonian matrix evaluated at the specified k points.
|
197
|
-
"""
|
198
|
-
assert _check_valid_array(k)
|
199
|
-
if k.ndim == 1:
|
200
|
-
k = np.expand_dims(k, axis=0)
|
201
|
-
|
202
|
-
h = np.zeros(
|
203
|
-
(k.shape[0], 2 * self.number_of_bands, 2 * self.number_of_bands),
|
204
|
-
dtype=np.complex128,
|
205
|
-
)
|
206
|
-
|
207
|
-
h[:, 0 : self.number_of_bands, 0 : self.number_of_bands] = self.hamiltonian(k)
|
208
|
-
h[
|
209
|
-
:,
|
210
|
-
self.number_of_bands : 2 * self.number_of_bands,
|
211
|
-
self.number_of_bands : 2 * self.number_of_bands,
|
212
|
-
] = -self.hamiltonian(self.q - k).conjugate()
|
213
|
-
|
214
|
-
for i in range(self.number_of_bands):
|
215
|
-
h[:, self.number_of_bands + i, i] = self.delta_orbital_basis[i]
|
216
|
-
|
217
|
-
h[:, 0 : self.number_of_bands, self.number_of_bands : self.number_of_bands * 2] = (
|
218
|
-
h[:, self.number_of_bands : self.number_of_bands * 2, 0 : self.number_of_bands]
|
219
|
-
.copy()
|
220
|
-
.conjugate()
|
221
|
-
)
|
222
|
-
|
223
|
-
return h.squeeze()
|
224
|
-
|
225
|
-
def bdg_hamiltonian_derivative(
|
226
|
-
self, k: npt.NDArray[np.floating], direction: str
|
227
|
-
) -> npt.NDArray[np.complexfloating]:
|
228
|
-
"""Calculate the derivative of the BdG Hamiltonian.
|
229
|
-
|
230
|
-
This method computes the spatial derivative of the Bogoliubov-de Gennes
|
231
|
-
Hamiltonian with respect to the specified direction.
|
232
|
-
|
233
|
-
Parameters
|
234
|
-
----------
|
235
|
-
k : :class:`numpy.ndarray`
|
236
|
-
List of k points in reciprocal space.
|
237
|
-
direction : str
|
238
|
-
Direction for the derivative, either 'x' or 'y'.
|
239
|
-
|
240
|
-
Returns
|
241
|
-
-------
|
242
|
-
:class:`numpy.ndarray`
|
243
|
-
The derivative of the BdG Hamiltonian matrix in the specified direction.
|
244
|
-
"""
|
245
|
-
assert _check_valid_array(k)
|
246
|
-
if k.ndim == 1:
|
247
|
-
k = np.expand_dims(k, axis=0)
|
248
|
-
|
249
|
-
h = np.zeros(
|
250
|
-
(k.shape[0], 2 * self.number_of_bands, 2 * self.number_of_bands),
|
251
|
-
dtype=np.complex128,
|
252
|
-
)
|
253
|
-
|
254
|
-
h[:, 0 : self.number_of_bands, 0 : self.number_of_bands] = self.hamiltonian_derivative(
|
255
|
-
k, direction
|
256
|
-
)
|
257
|
-
h[
|
258
|
-
:,
|
259
|
-
self.number_of_bands : 2 * self.number_of_bands,
|
260
|
-
self.number_of_bands : 2 * self.number_of_bands,
|
261
|
-
] = -self.hamiltonian_derivative(-k, direction).conjugate()
|
262
|
-
|
263
|
-
return h.squeeze()
|
264
|
-
|
265
|
-
def diagonalize_nonint(
|
266
|
-
self, k: npt.NDArray[np.floating]
|
267
|
-
) -> tuple[npt.NDArray[np.floating], npt.NDArray[np.floating]]:
|
268
|
-
"""Diagonalizes the normal state Hamiltonian.
|
269
|
-
|
270
|
-
This method computes the eigenvalues and eigenvectors of the normal state
|
271
|
-
Hamiltonian for the given k points. It is essential for analyzing the
|
272
|
-
electronic properties and band structure of materials.
|
273
|
-
|
274
|
-
Parameters
|
275
|
-
----------
|
276
|
-
k : :class:`numpy.ndarray`
|
277
|
-
List of k points in reciprocal space.
|
278
|
-
|
279
|
-
Returns
|
280
|
-
-------
|
281
|
-
tuple
|
282
|
-
- :class:`numpy.ndarray`: Eigenvalues of the normal state Hamiltonian.
|
283
|
-
- :class:`numpy.ndarray`: Eigenvectors (Bloch wavefunctions) corresponding to
|
284
|
-
the eigenvalues.
|
285
|
-
"""
|
286
|
-
k_point_matrix = self.hamiltonian(k)
|
287
|
-
if k_point_matrix.ndim == 2:
|
288
|
-
k_point_matrix = np.expand_dims(k_point_matrix, axis=0)
|
289
|
-
k = np.expand_dims(k, axis=0)
|
290
|
-
|
291
|
-
bloch_wavefunctions = np.zeros(
|
292
|
-
(len(k), self.number_of_bands, self.number_of_bands),
|
293
|
-
dtype=complex,
|
294
|
-
)
|
295
|
-
band_energies = np.zeros((len(k), self.number_of_bands))
|
296
|
-
|
297
|
-
for i in range(len(k)):
|
298
|
-
band_energies[i], bloch_wavefunctions[i] = np.linalg.eigh(k_point_matrix[i])
|
299
|
-
|
300
|
-
return band_energies.squeeze(), bloch_wavefunctions.squeeze()
|
301
|
-
|
302
|
-
def diagonalize_bdg(
|
303
|
-
self,
|
304
|
-
k: npt.NDArray[np.floating],
|
305
|
-
) -> tuple[npt.NDArray[np.floating], npt.NDArray[np.complexfloating]]:
|
306
|
-
"""Diagonalizes the BdG Hamiltonian.
|
307
|
-
|
308
|
-
This method computes the eigenvalues and eigenvectors of the Bogoliubov-de
|
309
|
-
Gennes Hamiltonian, providing insight into the quasiparticle excitations in
|
310
|
-
superconducting states.
|
311
|
-
|
312
|
-
Parameters
|
313
|
-
----------
|
314
|
-
k : :class:`numpy.ndarray`
|
315
|
-
List of k points in reciprocal space.
|
316
|
-
|
317
|
-
Returns
|
318
|
-
-------
|
319
|
-
tuple
|
320
|
-
- :class:`numpy.ndarray`: Eigenvalues of the BdG Hamiltonian.
|
321
|
-
- :class:`numpy.ndarray`: Eigenvectors corresponding to the eigenvalues of the
|
322
|
-
BdG Hamiltonian.
|
323
|
-
"""
|
324
|
-
bdg_matrix = self.bdg_hamiltonian(k=k)
|
325
|
-
if bdg_matrix.ndim == 2:
|
326
|
-
bdg_matrix = np.expand_dims(bdg_matrix, axis=0)
|
327
|
-
k = np.expand_dims(k, axis=0)
|
328
|
-
|
329
|
-
bdg_wavefunctions = np.zeros(
|
330
|
-
(len(k), 2 * self.number_of_bands, 2 * self.number_of_bands),
|
331
|
-
dtype=np.complex128,
|
332
|
-
)
|
333
|
-
bdg_energies = np.zeros((len(k), 2 * self.number_of_bands))
|
334
|
-
|
335
|
-
for i in range(len(k)):
|
336
|
-
bdg_energies[i], bdg_wavefunctions[i] = np.linalg.eigh(bdg_matrix[i])
|
337
|
-
|
338
|
-
return bdg_energies.squeeze(), bdg_wavefunctions.squeeze()
|
339
|
-
|
340
|
-
def gap_equation(self, k: npt.NDArray[np.floating]) -> npt.NDArray[np.complexfloating]:
|
341
|
-
"""Gap equation.
|
342
|
-
|
343
|
-
Parameters
|
344
|
-
----------
|
345
|
-
k : :class:`numpy.ndarray`
|
346
|
-
k grid
|
347
|
-
|
348
|
-
Returns
|
349
|
-
-------
|
350
|
-
New delta
|
351
|
-
"""
|
352
|
-
bdg_energies, bdg_wavefunctions = self.diagonalize_bdg(k=k)
|
353
|
-
delta = np.zeros(self.number_of_bands, dtype=np.complex128)
|
354
|
-
return self.gap_equation_loop(
|
355
|
-
bdg_energies, bdg_wavefunctions, delta, self.beta, self.hubbard_int_orbital_basis, k
|
356
|
-
)
|
357
|
-
|
358
|
-
@staticmethod
|
359
|
-
@jit
|
360
|
-
def gap_equation_loop(
|
361
|
-
bdg_energies: npt.NDArray[np.float64],
|
362
|
-
bdg_wavefunctions: npt.NDArray[np.complex128],
|
363
|
-
delta: npt.NDArray[np.complex128],
|
364
|
-
beta: float,
|
365
|
-
hubbard_int_orbital_basis: npt.NDArray[np.float64],
|
366
|
-
k: npt.NDArray[np.floating],
|
367
|
-
) -> npt.NDArray[np.complexfloating]:
|
368
|
-
"""Calculate the gap equation.
|
369
|
-
|
370
|
-
The gap equation determines the order parameter for superconductivity by
|
371
|
-
relating the pairings to the spectral properties of the BdG Hamiltonian.
|
372
|
-
|
373
|
-
Parameters
|
374
|
-
----------
|
375
|
-
bdg_energies : :class:`numpy.ndarray`
|
376
|
-
BdG energies
|
377
|
-
bdg_wavefunctions : :class:`numpy.ndarray`
|
378
|
-
BdG wavefunctions
|
379
|
-
delta : :class:`numpy.ndarray`
|
380
|
-
Delta
|
381
|
-
beta : :class:`float`
|
382
|
-
Beta
|
383
|
-
hubbard_int_orbital_basis : :class:`numpy.ndarray`
|
384
|
-
Hubard interaction in orbital basis
|
385
|
-
k : :class:`numpy.ndarray`
|
386
|
-
List of k points in reciprocal space.
|
387
|
-
|
388
|
-
Returns
|
389
|
-
-------
|
390
|
-
:class:`numpy.ndarray`
|
391
|
-
New pairing gap in orbital basis, adjusted to remove global phase.
|
392
|
-
"""
|
393
|
-
number_of_bands = len(delta)
|
394
|
-
for i in range(number_of_bands):
|
395
|
-
sum_tmp = 0
|
396
|
-
for j in range(2 * number_of_bands):
|
397
|
-
for k_index in range(len(k)):
|
398
|
-
sum_tmp += (
|
399
|
-
np.conjugate(bdg_wavefunctions[k_index, i, j])
|
400
|
-
* bdg_wavefunctions[k_index, i + number_of_bands, j]
|
401
|
-
* fermi_dirac(bdg_energies[k_index, j].item(), beta)
|
402
|
-
)
|
403
|
-
delta[i] = (-hubbard_int_orbital_basis[i] * sum_tmp / len(k)).conjugate()
|
404
|
-
|
405
|
-
delta_without_phase: npt.NDArray[np.complexfloating] = delta * np.exp(
|
406
|
-
-1j * np.angle(delta[np.argmax(np.abs(delta))])
|
407
|
-
)
|
408
|
-
return delta_without_phase
|
409
|
-
|
410
|
-
def calculate_bandstructure(
|
411
|
-
self,
|
412
|
-
k: npt.NDArray[np.floating],
|
413
|
-
overlaps: tuple[npt.NDArray[np.floating], npt.NDArray[np.floating]] | None = None,
|
414
|
-
) -> pd.DataFrame:
|
415
|
-
"""Calculate the band structure.
|
416
|
-
|
417
|
-
This method computes the energy bands of the system by diagonalizing
|
418
|
-
the normal state Hamiltonian over the provided k points. It can also
|
419
|
-
calculate overlaps with provided wavefunctions if available.
|
420
|
-
|
421
|
-
Parameters
|
422
|
-
----------
|
423
|
-
k : :class:`numpy.ndarray`
|
424
|
-
List of k points in reciprocal space.
|
425
|
-
overlaps : tuple(:class:`numpy.ndarray`, :class:`numpy.ndarray`), optional
|
426
|
-
A tuple containing two sets of wavefunctions for overlap calculations.
|
427
|
-
|
428
|
-
Returns
|
429
|
-
-------
|
430
|
-
`pandas.DataFrame`
|
431
|
-
A DataFrame containing the calculated band energies with optional
|
432
|
-
overlap information.
|
433
|
-
"""
|
434
|
-
results = pd.DataFrame(
|
435
|
-
index=range(len(k)),
|
436
|
-
dtype=float,
|
437
|
-
)
|
438
|
-
energies, wavefunctions = self.diagonalize_nonint(k)
|
439
|
-
|
440
|
-
for i, (energy_k, wavefunction_k) in enumerate(zip(energies, wavefunctions, strict=False)):
|
441
|
-
if np.ndim(energy_k) == 0:
|
442
|
-
results.loc[i, "band"] = energy_k
|
443
|
-
else:
|
444
|
-
for band_index in range(self.number_of_bands):
|
445
|
-
results.loc[i, f"band_{band_index}"] = energy_k[band_index] # type: ignore[index]
|
446
|
-
|
447
|
-
if overlaps is not None:
|
448
|
-
results.loc[i, f"wx_{band_index}"] = (
|
449
|
-
np.abs(np.dot(wavefunction_k[:, band_index], overlaps[0])) ** 2 # type: ignore[index]
|
450
|
-
- np.abs(np.dot(wavefunction_k[:, band_index], overlaps[1])) ** 2 # type: ignore[index]
|
451
|
-
)
|
452
|
-
|
453
|
-
return results
|
454
|
-
|
455
|
-
def calculate_density_of_states(
|
456
|
-
self,
|
457
|
-
k: npt.NDArray[np.floating],
|
458
|
-
) -> tuple[npt.NDArray[np.floating], npt.NDArray[np.floating]]:
|
459
|
-
"""Calculate the density of states (DOS).
|
460
|
-
|
461
|
-
This method computes the density of states by evaluating the eigenvalues
|
462
|
-
of the BdG Hamiltonian over a specified energy range. The DOS provides
|
463
|
-
insights into the allowed energy levels of the system.
|
464
|
-
|
465
|
-
Parameters
|
466
|
-
----------
|
467
|
-
k : :class:`numpy.ndarray`
|
468
|
-
List of k points in reciprocal space.
|
469
|
-
|
470
|
-
Returns
|
471
|
-
-------
|
472
|
-
tuple
|
473
|
-
- numpy.ndarray: Energy levels over which the density of states is calculated.
|
474
|
-
- numpy.ndarray: The density of states corresponding to each energy level.
|
475
|
-
"""
|
476
|
-
bands, _ = self.diagonalize_bdg(k=k)
|
477
|
-
gap_fraction = 10
|
478
|
-
energies = np.concatenate(
|
479
|
-
[
|
480
|
-
np.linspace(
|
481
|
-
start=np.min(bands),
|
482
|
-
stop=-gap_fraction * np.max(np.abs(self.delta_orbital_basis)),
|
483
|
-
num=100,
|
484
|
-
),
|
485
|
-
np.linspace(
|
486
|
-
start=-gap_fraction * np.max(np.abs(self.delta_orbital_basis)),
|
487
|
-
stop=gap_fraction * np.max(np.abs(self.delta_orbital_basis)),
|
488
|
-
num=200,
|
489
|
-
),
|
490
|
-
np.linspace(
|
491
|
-
start=gap_fraction * np.max(np.abs(self.delta_orbital_basis)),
|
492
|
-
stop=np.max(bands),
|
493
|
-
num=100,
|
494
|
-
),
|
495
|
-
]
|
496
|
-
)
|
497
|
-
density_of_states = np.zeros(shape=energies.shape, dtype=np.float64)
|
498
|
-
|
499
|
-
for i, energy in enumerate(energies):
|
500
|
-
density_of_states[i] = np.sum(
|
501
|
-
_gaussian(x=(energy - bands.flatten()), sigma=0.01)
|
502
|
-
) / len(k)
|
503
|
-
return energies, density_of_states
|
504
|
-
|
505
|
-
def calculate_spectral_gap(self, k: npt.NDArray[np.floating]) -> float:
|
506
|
-
"""Calculate the spectral gap.
|
507
|
-
|
508
|
-
This method evaluates the spectral gap of the system by examining the
|
509
|
-
density of states. It identifies the range of energy where there are no
|
510
|
-
states and thus determines the energy difference between the highest
|
511
|
-
occupied and lowest unoccupied states.
|
512
|
-
|
513
|
-
Parameters
|
514
|
-
----------
|
515
|
-
k : :class:`numpy.ndarray`
|
516
|
-
List of k points in reciprocal space.
|
517
|
-
|
518
|
-
Returns
|
519
|
-
-------
|
520
|
-
float
|
521
|
-
The calculated spectral gap.
|
522
|
-
"""
|
523
|
-
energies, density_of_states = self.calculate_density_of_states(k=k)
|
524
|
-
|
525
|
-
coherence_peaks = np.where(
|
526
|
-
np.isclose(density_of_states, np.max(density_of_states), rtol=1e-2)
|
527
|
-
)[0]
|
528
|
-
|
529
|
-
gap_region = density_of_states[coherence_peaks[0] : coherence_peaks[-1] + 1] / np.max(
|
530
|
-
density_of_states
|
531
|
-
)
|
532
|
-
energies_gap_region = energies[coherence_peaks[0] : coherence_peaks[-1] + 1]
|
533
|
-
zero_indeces = np.where(gap_region <= 1e-6)[0]
|
534
|
-
if len(zero_indeces) == 0:
|
535
|
-
gap = 0
|
536
|
-
else:
|
537
|
-
gap = (
|
538
|
-
energies_gap_region[zero_indeces[-1]] - energies_gap_region[zero_indeces[0]]
|
539
|
-
).item()
|
540
|
-
|
541
|
-
return gap
|
542
|
-
|
543
|
-
def calculate_free_energy(self, k: npt.NDArray[np.floating]) -> float:
|
544
|
-
"""Calculate the free energy for the Hamiltonian.
|
545
|
-
|
546
|
-
Parameters
|
547
|
-
----------
|
548
|
-
k
|
549
|
-
|
550
|
-
Returns
|
551
|
-
-------
|
552
|
-
free_energy
|
553
|
-
|
554
|
-
"""
|
555
|
-
number_k_points = len(k)
|
556
|
-
bdg_energies, _ = self.diagonalize_bdg(k)
|
557
|
-
integral: float = 0
|
558
|
-
|
559
|
-
k_array = (
|
560
|
-
1
|
561
|
-
/ self.beta
|
562
|
-
* np.array(
|
563
|
-
[
|
564
|
-
np.sum(np.log(1 + np.exp(-self.beta * bdg_energies[k_index])))
|
565
|
-
for k_index in range(number_k_points)
|
566
|
-
]
|
567
|
-
)
|
568
|
-
)
|
569
|
-
|
570
|
-
integral += -np.sum(k_array, axis=-1) / number_k_points + 0.5 * np.sum(
|
571
|
-
np.power(np.abs(self.delta_orbital_basis), 2) / self.hubbard_int_orbital_basis
|
572
|
-
)
|
573
|
-
|
574
|
-
return integral
|
575
|
-
|
576
|
-
def calculate_current_density(self, k: npt.NDArray[np.floating]) -> npt.NDArray[np.floating]:
|
577
|
-
"""Calculate the current density.
|
578
|
-
|
579
|
-
Parameters
|
580
|
-
----------
|
581
|
-
k
|
582
|
-
|
583
|
-
Returns
|
584
|
-
-------
|
585
|
-
current_density
|
586
|
-
|
587
|
-
"""
|
588
|
-
bdg_energies, bdg_wavefunctions = self.diagonalize_bdg(k=k)
|
589
|
-
h_der_x = self.hamiltonian_derivative(k=k, direction="x")
|
590
|
-
h_der_y = self.hamiltonian_derivative(k=k, direction="y")
|
591
|
-
|
592
|
-
current = np.zeros(2, dtype=np.complex128)
|
593
|
-
|
594
|
-
matrix_x = np.zeros((3, 3), dtype=np.complex128)
|
595
|
-
matrix_y = np.zeros((3, 3), dtype=np.complex128)
|
596
|
-
|
597
|
-
for k_index in range(len(k)):
|
598
|
-
for i in range(self.number_of_bands):
|
599
|
-
for j in range(self.number_of_bands):
|
600
|
-
for n in range(2 * self.number_of_bands):
|
601
|
-
matrix_x[i, j] += (
|
602
|
-
h_der_x[k_index, i, j]
|
603
|
-
* np.conjugate(bdg_wavefunctions[k_index, i, n])
|
604
|
-
* bdg_wavefunctions[k_index, j, n]
|
605
|
-
* fermi_dirac(bdg_energies[k_index, n].item(), self.beta)
|
606
|
-
)
|
607
|
-
matrix_y[i, j] += (
|
608
|
-
h_der_y[k_index, i, j]
|
609
|
-
* np.conjugate(bdg_wavefunctions[k_index, i, n])
|
610
|
-
* bdg_wavefunctions[k_index, j, n]
|
611
|
-
* fermi_dirac(bdg_energies[k_index, n].item(), self.beta)
|
612
|
-
)
|
613
|
-
|
614
|
-
current[0] = np.sum(matrix_x, axis=None)
|
615
|
-
current[1] = np.sum(matrix_y, axis=None)
|
616
|
-
assert np.allclose(np.imag(current), 0, atol=1e-12)
|
617
|
-
|
618
|
-
return (2 * np.real(current)) / len(k)
|
619
|
-
|
620
|
-
def calculate_superfluid_weight(
|
621
|
-
self,
|
622
|
-
k: npt.NDArray[np.floating],
|
623
|
-
) -> tuple[npt.NDArray[np.complexfloating], npt.NDArray[np.complexfloating]]:
|
624
|
-
"""Calculate the superfluid weight.
|
625
|
-
|
626
|
-
Parameters
|
627
|
-
----------
|
628
|
-
h : :class:`~quant_met.mean_field.Hamiltonian`
|
629
|
-
Hamiltonian.
|
630
|
-
k : :class:`numpy.ndarray`
|
631
|
-
List of k points.
|
632
|
-
|
633
|
-
Returns
|
634
|
-
-------
|
635
|
-
:class:`numpy.ndarray`
|
636
|
-
Conventional contribution to the superfluid weight.
|
637
|
-
:class:`numpy.ndarray`
|
638
|
-
Geometric contribution to the superfluid weight.
|
639
|
-
|
640
|
-
"""
|
641
|
-
s_weight_conv = np.zeros(shape=(2, 2), dtype=np.complex128)
|
642
|
-
s_weight_geom = np.zeros(shape=(2, 2), dtype=np.complex128)
|
643
|
-
|
644
|
-
c_mnpq_cache = {}
|
645
|
-
|
646
|
-
for i, direction_1 in enumerate(["x", "y"]):
|
647
|
-
for j, direction_2 in enumerate(["x", "y"]):
|
648
|
-
for k_point in k:
|
649
|
-
k_tuple = tuple(k_point)
|
650
|
-
|
651
|
-
if k_tuple not in c_mnpq_cache:
|
652
|
-
c_mnpq_cache[k_tuple] = self._c_factor(k_point)
|
653
|
-
c_mnpq = c_mnpq_cache[k_tuple]
|
654
|
-
|
655
|
-
j_up = self._current_operator(direction_1, k_point)
|
656
|
-
j_down = self._current_operator(direction_2, -k_point)
|
657
|
-
|
658
|
-
for m in range(self.number_of_bands):
|
659
|
-
for n in range(self.number_of_bands):
|
660
|
-
for p in range(self.number_of_bands):
|
661
|
-
for q in range(self.number_of_bands):
|
662
|
-
s_weight = c_mnpq[m, n, p, q] * j_up[m, n] * j_down[q, p]
|
663
|
-
if m == n and p == q:
|
664
|
-
s_weight_conv[i, j] += s_weight
|
665
|
-
else:
|
666
|
-
s_weight_geom[i, j] += s_weight
|
667
|
-
|
668
|
-
return s_weight_conv, s_weight_geom
|
669
|
-
|
670
|
-
def _current_operator(
|
671
|
-
self, direction: str, k: npt.NDArray[np.floating]
|
672
|
-
) -> npt.NDArray[np.complexfloating]:
|
673
|
-
j = np.zeros(shape=(self.number_of_bands, self.number_of_bands), dtype=np.complex128)
|
674
|
-
|
675
|
-
_, bloch = self.diagonalize_nonint(k=k)
|
676
|
-
|
677
|
-
for m in range(self.number_of_bands):
|
678
|
-
for n in range(self.number_of_bands):
|
679
|
-
j[m, n] = (
|
680
|
-
bloch[:, m].conjugate()
|
681
|
-
@ self.hamiltonian_derivative(direction=direction, k=k)
|
682
|
-
@ bloch[:, n]
|
683
|
-
)
|
684
|
-
|
685
|
-
return j
|
686
|
-
|
687
|
-
def _c_factor(self, k: npt.NDArray[np.floating]) -> npt.NDArray[np.complexfloating]:
|
688
|
-
bdg_energies, bdg_functions = self.diagonalize_bdg(k)
|
689
|
-
c_mnpq = np.zeros(
|
690
|
-
shape=(
|
691
|
-
self.number_of_bands,
|
692
|
-
self.number_of_bands,
|
693
|
-
self.number_of_bands,
|
694
|
-
self.number_of_bands,
|
695
|
-
),
|
696
|
-
dtype=np.complex128,
|
697
|
-
)
|
698
|
-
|
699
|
-
for m in range(self.number_of_bands):
|
700
|
-
for n in range(self.number_of_bands):
|
701
|
-
for p in range(self.number_of_bands):
|
702
|
-
for q in range(self.number_of_bands):
|
703
|
-
c_tmp: float = 0
|
704
|
-
for i in range(2 * self.number_of_bands):
|
705
|
-
for j in range(2 * self.number_of_bands):
|
706
|
-
if bdg_energies[i] != bdg_energies[j]:
|
707
|
-
c_tmp += (
|
708
|
-
fermi_dirac(bdg_energies[i], self.beta)
|
709
|
-
- fermi_dirac(bdg_energies[j], self.beta)
|
710
|
-
) / (bdg_energies[i] - bdg_energies[j])
|
711
|
-
else:
|
712
|
-
c_tmp -= _fermi_dirac_derivative()
|
713
|
-
|
714
|
-
c_tmp *= (
|
715
|
-
bdg_functions[i, m].conjugate()
|
716
|
-
* bdg_functions[j, n]
|
717
|
-
* bdg_functions[j, p].conjugate()
|
718
|
-
* bdg_functions[i, q]
|
719
|
-
)
|
720
|
-
|
721
|
-
c_mnpq[m, n, p, q] = 2 * c_tmp
|
722
|
-
|
723
|
-
return c_mnpq
|
724
|
-
|
725
|
-
def calculate_quantum_metric(
|
726
|
-
self, k: npt.NDArray[np.floating], bands: list[int]
|
727
|
-
) -> npt.NDArray[np.floating]:
|
728
|
-
"""Calculate the quantum metric (geometric tensor) for specified bands.
|
729
|
-
|
730
|
-
This function computes the quantum geometric tensor associated with
|
731
|
-
the specified bands of a given Hamiltonian over a grid of k-points.
|
732
|
-
The output is a 2x2 matrix representing the quantum metric.
|
733
|
-
|
734
|
-
Parameters
|
735
|
-
----------
|
736
|
-
h : BaseHamiltonian
|
737
|
-
Hamiltonian object used to compute Bloch states and their derivatives.
|
738
|
-
k : numpy.ndarray
|
739
|
-
Array of k points in the Brillouin zone.
|
740
|
-
bands : list of int
|
741
|
-
Indices of the bands for which the quantum metric is to be calculated.
|
742
|
-
|
743
|
-
Returns
|
744
|
-
-------
|
745
|
-
:class:`numpy.ndarray`
|
746
|
-
A 2x2 matrix representing the quantum metric.
|
747
|
-
|
748
|
-
Raises
|
749
|
-
------
|
750
|
-
ValueError
|
751
|
-
If `bands` contains invalid indices or `k_grid` is empty.
|
752
|
-
"""
|
753
|
-
energies, bloch = self.diagonalize_nonint(k)
|
754
|
-
|
755
|
-
number_k_points = len(k)
|
756
|
-
|
757
|
-
quantum_geom_tensor = np.zeros(shape=(2, 2), dtype=np.complex128)
|
758
|
-
|
759
|
-
for band in bands:
|
760
|
-
for i, direction_1 in enumerate(["x", "y"]):
|
761
|
-
h_derivative_direction_1 = self.hamiltonian_derivative(k=k, direction=direction_1)
|
762
|
-
for j, direction_2 in enumerate(["x", "y"]):
|
763
|
-
h_derivative_direction_2 = self.hamiltonian_derivative(
|
764
|
-
k=k, direction=direction_2
|
765
|
-
)
|
766
|
-
for k_index in range(len(k)):
|
767
|
-
for n in [m for m in range(self.number_of_bands) if m != band]:
|
768
|
-
quantum_geom_tensor[i, j] += (
|
769
|
-
(
|
770
|
-
bloch[k_index][:, band].conjugate()
|
771
|
-
@ h_derivative_direction_1[k_index]
|
772
|
-
@ bloch[k_index][:, n]
|
773
|
-
)
|
774
|
-
* (
|
775
|
-
bloch[k_index][:, n].conjugate()
|
776
|
-
@ h_derivative_direction_2[k_index]
|
777
|
-
@ bloch[k_index][:, band]
|
778
|
-
)
|
779
|
-
/ (energies[k_index][band] - energies[k_index][n]) ** 2
|
780
|
-
)
|
781
|
-
|
782
|
-
return np.real(quantum_geom_tensor) / number_k_points
|
783
|
-
|
784
|
-
|
785
|
-
def _fermi_dirac_derivative() -> float:
|
786
|
-
return 0
|
787
|
-
|
788
|
-
|
789
|
-
def _gaussian(x: npt.NDArray[np.floating], sigma: float) -> npt.NDArray[np.floating]:
|
790
|
-
gaussian: npt.NDArray[np.floating] = np.exp(-(x**2) / (2 * sigma**2)) / np.sqrt(
|
791
|
-
2 * np.pi * sigma**2
|
792
|
-
)
|
793
|
-
return gaussian
|