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.
Files changed (47) hide show
  1. quant_met/__init__.py +2 -7
  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 -5
  8. quant_met/cli/crit_temp.py +18 -16
  9. quant_met/cli/main.py +8 -5
  10. quant_met/cli/q_analysis.py +60 -0
  11. quant_met/cli/q_loop.py +95 -0
  12. quant_met/cli/scf.py +44 -23
  13. quant_met/parameters/__init__.py +0 -26
  14. quant_met/parameters/control.py +57 -0
  15. quant_met/parameters/main.py +2 -55
  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 -48
  22. quant_met/{mean_field → routines}/self_consistency.py +32 -28
  23. quant_met/utils.py +1 -6
  24. {quant_met-0.0.27.dist-info → quant_met-0.1.1.dist-info}/METADATA +5 -11
  25. quant_met-0.1.1.dist-info/RECORD +28 -0
  26. quant_met/cli/_utils.py +0 -32
  27. quant_met/geometry/__init__.py +0 -36
  28. quant_met/geometry/base_lattice.py +0 -100
  29. quant_met/geometry/bz_path.py +0 -90
  30. quant_met/geometry/graphene.py +0 -48
  31. quant_met/geometry/square.py +0 -47
  32. quant_met/mean_field/__init__.py +0 -38
  33. quant_met/mean_field/_utils.py +0 -17
  34. quant_met/mean_field/hamiltonians/__init__.py +0 -34
  35. quant_met/mean_field/hamiltonians/base_hamiltonian.py +0 -793
  36. quant_met/mean_field/hamiltonians/dressed_graphene.py +0 -118
  37. quant_met/mean_field/hamiltonians/graphene.py +0 -95
  38. quant_met/mean_field/hamiltonians/one_band_tight_binding.py +0 -70
  39. quant_met/mean_field/hamiltonians/three_band_tight_binding.py +0 -85
  40. quant_met/mean_field/hamiltonians/two_band_tight_binding.py +0 -76
  41. quant_met/parameters/hamiltonians.py +0 -182
  42. quant_met/plotting/__init__.py +0 -31
  43. quant_met/plotting/plotting.py +0 -215
  44. quant_met-0.0.27.dist-info/RECORD +0 -33
  45. {quant_met-0.0.27.dist-info → quant_met-0.1.1.dist-info}/WHEEL +0 -0
  46. {quant_met-0.0.27.dist-info → quant_met-0.1.1.dist-info}/entry_points.txt +0 -0
  47. {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