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