quant-met 0.0.7__py3-none-any.whl → 0.0.9__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 (31) hide show
  1. quant_met/cli/__init__.py +22 -0
  2. quant_met/cli/main.py +34 -0
  3. quant_met/cli/scf.py +40 -0
  4. quant_met/geometry/__init__.py +12 -3
  5. quant_met/geometry/base_lattice.py +18 -4
  6. quant_met/geometry/graphene.py +11 -3
  7. quant_met/geometry/square.py +11 -3
  8. quant_met/mean_field/__init__.py +9 -32
  9. quant_met/mean_field/_utils.py +0 -11
  10. quant_met/mean_field/hamiltonians/__init__.py +31 -0
  11. quant_met/mean_field/{base_hamiltonian.py → hamiltonians/base_hamiltonian.py} +88 -77
  12. quant_met/mean_field/{eg_x.py → hamiltonians/dressed_graphene.py} +26 -58
  13. quant_met/mean_field/{graphene.py → hamiltonians/graphene.py} +20 -45
  14. quant_met/mean_field/{one_band_tight_binding.py → hamiltonians/one_band_tight_binding.py} +20 -48
  15. quant_met/mean_field/hamiltonians/three_band_tight_binding.py +116 -0
  16. quant_met/mean_field/hamiltonians/two_band_tight_binding.py +107 -0
  17. quant_met/mean_field/quantum_metric.py +4 -3
  18. quant_met/mean_field/self_consistency.py +15 -14
  19. quant_met/mean_field/superfluid_weight.py +7 -4
  20. quant_met/parameters/__init__.py +36 -0
  21. quant_met/parameters/hamiltonians.py +147 -0
  22. quant_met/parameters/main.py +37 -0
  23. quant_met/utils.py +1 -1
  24. {quant_met-0.0.7.dist-info → quant_met-0.0.9.dist-info}/METADATA +5 -3
  25. quant_met-0.0.9.dist-info/RECORD +33 -0
  26. quant_met-0.0.9.dist-info/entry_points.txt +3 -0
  27. quant_met/mean_field/free_energy.py +0 -130
  28. quant_met-0.0.7.dist-info/RECORD +0 -24
  29. {quant_met-0.0.7.dist-info → quant_met-0.0.9.dist-info}/LICENSE.txt +0 -0
  30. {quant_met-0.0.7.dist-info → quant_met-0.0.9.dist-info}/LICENSES/MIT.txt +0 -0
  31. {quant_met-0.0.7.dist-info → quant_met-0.0.9.dist-info}/WHEEL +0 -0
@@ -0,0 +1,22 @@
1
+ # SPDX-FileCopyrightText: 2024 Tjark Sievers
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ """
6
+ Command-Line-Interface (:mod:`quant_met.cli`)
7
+ =============================================
8
+
9
+ Functions
10
+ ---------
11
+
12
+ .. autosummary::
13
+ :toctree: generated/
14
+
15
+ cli
16
+ """ # noqa: D205, D400
17
+
18
+ from .main import cli
19
+
20
+ __all__ = [
21
+ "cli",
22
+ ]
quant_met/cli/main.py ADDED
@@ -0,0 +1,34 @@
1
+ # SPDX-FileCopyrightText: 2024 Tjark Sievers
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ """Command line interface."""
6
+
7
+ import sys
8
+ from typing import TextIO
9
+
10
+ import click
11
+ import yaml
12
+
13
+ from quant_met.parameters import Parameters
14
+
15
+ from .scf import scf
16
+
17
+
18
+ @click.command()
19
+ @click.argument("input-file", type=click.File("r"))
20
+ def cli(input_file: TextIO) -> None:
21
+ """Command line interface for quant-met.
22
+
23
+ Parameters
24
+ ----------
25
+ input_file
26
+ """
27
+ params = Parameters(**yaml.safe_load(input_file))
28
+
29
+ match params.control.calculation:
30
+ case "scf":
31
+ scf(params)
32
+ case _:
33
+ print(f"Calculation {params.control.calculation} not found.")
34
+ sys.exit(1)
quant_met/cli/scf.py ADDED
@@ -0,0 +1,40 @@
1
+ # SPDX-FileCopyrightText: 2024 Tjark Sievers
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ """Functions to run self-consistent calculation for the order parameter."""
6
+
7
+ from pathlib import Path
8
+
9
+ from quant_met import mean_field
10
+ from quant_met.mean_field.hamiltonians import BaseHamiltonian
11
+ from quant_met.parameters import Parameters
12
+ from quant_met.parameters.hamiltonians import HamiltonianParameters
13
+
14
+
15
+ def _hamiltonian_factory(
16
+ classname: str, parameters: HamiltonianParameters
17
+ ) -> BaseHamiltonian[HamiltonianParameters]:
18
+ """Create a hamiltonian by its class name."""
19
+ from quant_met.mean_field import hamiltonians
20
+
21
+ cls = getattr(hamiltonians, classname)
22
+ h: BaseHamiltonian[HamiltonianParameters] = cls(parameters)
23
+ return h
24
+
25
+
26
+ def scf(parameters: Parameters) -> None:
27
+ """Self-consistent calculation for the order parameter."""
28
+ result_path = Path(parameters.control.outdir)
29
+ result_path.mkdir(exist_ok=True, parents=True)
30
+ h = _hamiltonian_factory(parameters=parameters.model, classname=parameters.model.name)
31
+ solved_h = mean_field.self_consistency_loop(
32
+ h=h,
33
+ k_space_grid=h.lattice.generate_bz_grid(
34
+ ncols=parameters.k_points.nk1, nrows=parameters.k_points.nk2
35
+ ),
36
+ epsilon=parameters.control.conv_treshold,
37
+ )
38
+ print(solved_h.delta_orbital_basis)
39
+ result_file = result_path / f"{parameters.control.prefix}.hdf5"
40
+ solved_h.save(filename=result_file)
@@ -15,12 +15,21 @@ Functions
15
15
  :toctree: generated/
16
16
 
17
17
  generate_bz_path
18
- Graphene
18
+
19
+ Classes
20
+ -------
21
+
22
+ .. autosummary::
23
+ :toctree: generated/
24
+
25
+ BaseLattice
26
+ GrapheneLattice
27
+ SquareLattice
19
28
  """ # noqa: D205, D400
20
29
 
21
30
  from .base_lattice import BaseLattice
22
31
  from .bz_path import generate_bz_path
23
- from .graphene import Graphene
32
+ from .graphene import GrapheneLattice
24
33
  from .square import SquareLattice
25
34
 
26
- __all__ = ["generate_bz_path", "BaseLattice", "Graphene", "SquareLattice"]
35
+ __all__ = ["generate_bz_path", "BaseLattice", "GrapheneLattice", "SquareLattice"]
@@ -19,19 +19,29 @@ class BaseLattice(ABC):
19
19
 
20
20
  @property
21
21
  @abstractmethod
22
- def lattice_constant(self) -> np.float64:
22
+ def lattice_constant(self) -> float: # pragma: no cover
23
23
  """Lattice constant."""
24
24
  raise NotImplementedError
25
25
 
26
26
  @property
27
27
  @abstractmethod
28
- def bz_corners(self) -> npt.NDArray[np.float64]:
28
+ def bz_corners(self) -> npt.NDArray[np.float64]: # pragma: no cover
29
29
  """Corners of the BZ."""
30
30
  raise NotImplementedError
31
31
 
32
32
  @property
33
33
  @abstractmethod
34
- def high_symmetry_points(self) -> tuple[tuple[npt.NDArray[np.float64], str], ...]:
34
+ def reciprocal_basis(
35
+ self,
36
+ ) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]: # pragma: no cover
37
+ """Reciprocal basis vectors."""
38
+ raise NotImplementedError
39
+
40
+ @property
41
+ @abstractmethod
42
+ def high_symmetry_points(
43
+ self,
44
+ ) -> tuple[tuple[npt.NDArray[np.float64], str], ...]: # pragma: no cover
35
45
  """Tuple of high symmetry points and names."""
36
46
  raise NotImplementedError
37
47
 
@@ -52,7 +62,11 @@ class BaseLattice(ABC):
52
62
 
53
63
  """
54
64
  return generate_uniform_grid(
55
- ncols, nrows, self.bz_corners[0], self.bz_corners[1], origin=np.array([0, 0])
65
+ ncols,
66
+ nrows,
67
+ self.reciprocal_basis[0],
68
+ self.reciprocal_basis[1],
69
+ origin=np.array([0, 0]),
56
70
  )
57
71
 
58
72
  def generate_high_symmetry_path(
@@ -10,10 +10,10 @@ import numpy.typing as npt
10
10
  from .base_lattice import BaseLattice
11
11
 
12
12
 
13
- class Graphene(BaseLattice):
13
+ class GrapheneLattice(BaseLattice):
14
14
  """Lattice geometry for Graphene."""
15
15
 
16
- def __init__(self, lattice_constant: np.float64) -> None:
16
+ def __init__(self, lattice_constant: float) -> None:
17
17
  self._lattice_constant = lattice_constant
18
18
  self._bz_corners = (
19
19
  4
@@ -25,15 +25,23 @@ class Graphene(BaseLattice):
25
25
  self.M = np.pi / self.lattice_constant * np.array([1, 1 / np.sqrt(3)])
26
26
  self.K = 4 * np.pi / (3 * self.lattice_constant) * np.array([1, 0])
27
27
  self._high_symmetry_points = ((self.M, "M"), (self.Gamma, r"\Gamma"), (self.K, "K"))
28
+ self._reciprocal_basis = (
29
+ 2 * np.pi / self.lattice_constant * np.array([1, 1 / np.sqrt(3)]),
30
+ 2 * np.pi / self.lattice_constant * np.array([1, -1 / np.sqrt(3)]),
31
+ )
28
32
 
29
33
  @property
30
- def lattice_constant(self) -> np.float64: # noqa: D102
34
+ def lattice_constant(self) -> float: # noqa: D102
31
35
  return self._lattice_constant
32
36
 
33
37
  @property
34
38
  def bz_corners(self) -> npt.NDArray[np.float64]: # noqa: D102
35
39
  return self._bz_corners
36
40
 
41
+ @property
42
+ def reciprocal_basis(self) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]: # noqa: D102
43
+ return self._reciprocal_basis
44
+
37
45
  @property
38
46
  def high_symmetry_points(self) -> tuple[tuple[npt.NDArray[np.float64], str], ...]: # noqa: D102
39
47
  return self._high_symmetry_points
@@ -13,26 +13,34 @@ from .base_lattice import BaseLattice
13
13
  class SquareLattice(BaseLattice):
14
14
  """Lattice geometry for Square Lattice."""
15
15
 
16
- def __init__(self, lattice_constant: np.float64) -> None:
16
+ def __init__(self, lattice_constant: float) -> None:
17
17
  self._lattice_constant = lattice_constant
18
18
  self._bz_corners = (
19
19
  np.pi
20
20
  / lattice_constant
21
21
  * np.array([np.array([1, 1]), np.array([-1, 1]), np.array([1, -1]), np.array([-1, -1])])
22
22
  )
23
+ self._reciprocal_basis = (
24
+ 2 * np.pi / self.lattice_constant * np.array([1, 0]),
25
+ 2 * np.pi / self.lattice_constant * np.array([0, 1]),
26
+ )
23
27
  self.Gamma = np.array([0, 0])
24
28
  self.M = np.pi / lattice_constant * np.array([1, 1])
25
29
  self.X = np.pi / lattice_constant * np.array([1, 0])
26
30
  self._high_symmetry_points = ((self.Gamma, r"\Gamma"), (self.M, "M"))
27
31
 
28
32
  @property
29
- def lattice_constant(self) -> np.float64: # noqa: D102
33
+ def lattice_constant(self) -> float: # noqa: D102
30
34
  return self._lattice_constant
31
35
 
32
36
  @property
33
- def bz_corners(self) -> npt.NDArray[np.float64]: # noqa: D102
37
+ def bz_corners(self) -> npt.NDArray[np.float64]: # noqa: D102 # pragma: no cover
34
38
  return self._bz_corners
35
39
 
40
+ @property
41
+ def reciprocal_basis(self) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]: # noqa: D102
42
+ return self._reciprocal_basis
43
+
36
44
  @property
37
45
  def high_symmetry_points(self) -> tuple[tuple[npt.NDArray[np.float64], str], ...]: # noqa: D102
38
46
  return self._high_symmetry_points
@@ -6,21 +6,13 @@
6
6
  Mean field treatment (:mod:`quant_met.mean_field`)
7
7
  ==================================================
8
8
 
9
- Hamiltonians
10
- ------------
11
-
12
- Base
9
+ Submodules
10
+ ----------
13
11
 
14
12
  .. autosummary::
15
- :toctree: generated/
13
+ :toctree: generated/
16
14
 
17
- BaseHamiltonian
18
-
19
- .. autosummary::
20
- :toctree: generated/
21
-
22
- GrapheneHamiltonian
23
- EGXHamiltonian
15
+ hamiltonians
24
16
 
25
17
 
26
18
  Functions
@@ -31,20 +23,12 @@ Functions
31
23
 
32
24
  superfluid_weight
33
25
  quantum_metric
34
- free_energy
35
- free_energy_uniform_pairing
26
+ quantum_metric_bdg
27
+ self_consistency_loop
36
28
  """ # noqa: D205, D400
37
29
 
38
- from .base_hamiltonian import BaseHamiltonian
39
- from .eg_x import EGXHamiltonian
40
- from .free_energy import (
41
- free_energy,
42
- free_energy_complex_gap,
43
- free_energy_real_gap,
44
- free_energy_uniform_pairing,
45
- )
46
- from .graphene import GrapheneHamiltonian
47
- from .one_band_tight_binding import OneBandTightBindingHamiltonian
30
+ from quant_met.mean_field import hamiltonians
31
+
48
32
  from .quantum_metric import quantum_metric, quantum_metric_bdg
49
33
  from .self_consistency import self_consistency_loop
50
34
  from .superfluid_weight import superfluid_weight
@@ -53,13 +37,6 @@ __all__ = [
53
37
  "superfluid_weight",
54
38
  "quantum_metric",
55
39
  "quantum_metric_bdg",
56
- "free_energy",
57
- "free_energy_complex_gap",
58
- "free_energy_real_gap",
59
- "free_energy_uniform_pairing",
60
40
  "self_consistency_loop",
61
- "BaseHamiltonian",
62
- "GrapheneHamiltonian",
63
- "EGXHamiltonian",
64
- "OneBandTightBindingHamiltonian",
41
+ "hamiltonians",
65
42
  ]
@@ -14,14 +14,3 @@ def _check_valid_array(array_in: npt.NDArray[Any]) -> bool:
14
14
  raise ValueError(msg)
15
15
 
16
16
  return True
17
-
18
-
19
- def _validate_float(float_in: float, parameter_name: str) -> float:
20
- if np.isinf(float_in):
21
- msg = f"{parameter_name} must not be Infinity"
22
- raise ValueError(msg)
23
- if np.isnan(float_in):
24
- msg = f"{parameter_name} must not be NaN"
25
- raise ValueError(msg)
26
-
27
- return float_in
@@ -0,0 +1,31 @@
1
+ # SPDX-FileCopyrightText: 2024 Tjark Sievers
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ """
6
+ Hamiltonians (:mod:`quant_met.mean_field.hamiltonians`)
7
+ =======================================================
8
+
9
+ Base
10
+
11
+ .. autosummary::
12
+ :toctree: hamiltonians/
13
+
14
+ BaseHamiltonian
15
+
16
+ .. autosummary::
17
+ :toctree: hamiltonians/
18
+
19
+ Graphene
20
+ DressedGraphene
21
+ OneBand
22
+ """ # noqa: D205, D400
23
+
24
+ from .base_hamiltonian import BaseHamiltonian
25
+ from .dressed_graphene import DressedGraphene
26
+ from .graphene import Graphene
27
+ from .one_band_tight_binding import OneBand
28
+ from .three_band_tight_binding import ThreeBand
29
+ from .two_band_tight_binding import TwoBand
30
+
31
+ __all__ = ["BaseHamiltonian", "Graphene", "DressedGraphene", "OneBand", "TwoBand", "ThreeBand"]
@@ -6,55 +6,44 @@
6
6
 
7
7
  import pathlib
8
8
  from abc import ABC, abstractmethod
9
+ from typing import Generic
9
10
 
10
11
  import h5py
11
12
  import numpy as np
12
13
  import numpy.typing as npt
13
14
  import pandas as pd
14
15
 
15
- from ._utils import _check_valid_array
16
+ from quant_met.geometry import BaseLattice
17
+ from quant_met.mean_field._utils import _check_valid_array
18
+ from quant_met.parameters.hamiltonians import GenericParameters
16
19
 
17
20
 
18
- class BaseHamiltonian(ABC):
21
+ class BaseHamiltonian(Generic[GenericParameters], ABC):
19
22
  """Base class for Hamiltonians."""
20
23
 
21
- @property
22
- @abstractmethod
23
- def number_of_bands(self) -> int:
24
- """Number of bands in the model."""
25
- raise NotImplementedError
26
-
27
- @property
28
- def hubbard_int_orbital_basis(self) -> npt.NDArray[np.float64]:
29
- """
30
- hubbard_int interaction split up in orbitals.
31
-
32
- Returns
33
- -------
34
- :class:`numpy.ndarray`
24
+ def __init__(self, parameters: GenericParameters) -> None:
25
+ self.name = parameters.name
26
+ self.beta = parameters.beta if parameters.beta else 1000.0
27
+ self.q = parameters.q if parameters.q is not None else np.zeros(2)
35
28
 
36
- """
37
- raise NotImplementedError
38
-
39
- @property
40
- def delta_orbital_basis(self) -> npt.NDArray[np.complex64]:
41
- """
42
- Order parameter in orbital basis.
43
-
44
- Returns
45
- -------
46
- :class:`numpy.ndarray`
29
+ self.lattice = self.setup_lattice(parameters)
30
+ self.hubbard_int_orbital_basis = parameters.hubbard_int_orbital_basis
31
+ self.number_of_bands = len(self.hubbard_int_orbital_basis)
32
+ self.delta_orbital_basis = np.zeros(self.number_of_bands, dtype=np.complex64)
47
33
 
48
- """
49
- raise NotImplementedError
34
+ @abstractmethod
35
+ def setup_lattice(self, parameters: GenericParameters) -> BaseLattice: # pragma: no cover
36
+ """Set up lattice based on parameters."""
50
37
 
51
- @delta_orbital_basis.setter
38
+ @classmethod
52
39
  @abstractmethod
53
- def delta_orbital_basis(self, new_delta: npt.NDArray[np.complex64]) -> None:
54
- raise NotImplementedError
40
+ def get_parameters_model(cls) -> type[GenericParameters]: # pragma: no cover
41
+ """Return the specific parameters model for the subclass."""
55
42
 
56
43
  @abstractmethod
57
- def hamiltonian(self, k: npt.NDArray[np.float64]) -> npt.NDArray[np.complex64]:
44
+ def hamiltonian(
45
+ self, k: npt.NDArray[np.float64]
46
+ ) -> npt.NDArray[np.complex64]: # pragma: no cover
58
47
  """
59
48
  Return the normal state Hamiltonian in orbital basis.
60
49
 
@@ -69,12 +58,11 @@ class BaseHamiltonian(ABC):
69
58
  Hamiltonian in matrix form.
70
59
 
71
60
  """
72
- raise NotImplementedError
73
61
 
74
62
  @abstractmethod
75
63
  def hamiltonian_derivative(
76
64
  self, k: npt.NDArray[np.float64], direction: str
77
- ) -> npt.NDArray[np.complex64]:
65
+ ) -> npt.NDArray[np.complex64]: # pragma: no cover
78
66
  """
79
67
  Deriative of the Hamiltonian.
80
68
 
@@ -91,7 +79,6 @@ class BaseHamiltonian(ABC):
91
79
  Derivative of Hamiltonian.
92
80
 
93
81
  """
94
- raise NotImplementedError
95
82
 
96
83
  def save(self, filename: pathlib.Path) -> None:
97
84
  """
@@ -103,28 +90,23 @@ class BaseHamiltonian(ABC):
103
90
  Filename to save the Hamiltonian to, should end in .hdf5
104
91
 
105
92
  """
106
- with h5py.File(f"{filename}", "w") as f:
93
+ with h5py.File(f"{filename.absolute()}", "w") as f:
107
94
  f.create_dataset("delta", data=self.delta_orbital_basis)
108
95
  for key, value in vars(self).items():
109
- if not key.startswith("_"):
110
- f.attrs[key] = value
96
+ if key != "lattice":
97
+ f.attrs[key.strip("_")] = value
98
+ f.attrs["lattice_constant"] = self.lattice.lattice_constant
111
99
 
112
100
  @classmethod
113
- def from_file(cls, filename: pathlib.Path) -> "BaseHamiltonian":
114
- """
115
- Initialise a Hamiltonian from a HDF5 file.
116
-
117
- Parameters
118
- ----------
119
- filename : :class:`pathlib.Path`
120
- File to load the Hamiltonian from.
121
-
122
- """
123
- with h5py.File(f"{filename}", "r") as f:
101
+ def from_file(cls, filename: pathlib.Path) -> "BaseHamiltonian[GenericParameters]":
102
+ """Initialize a Hamiltonian from an HDF5 file."""
103
+ with h5py.File(str(filename), "r") as f:
124
104
  config_dict = dict(f.attrs.items())
125
105
  config_dict["delta"] = f["delta"][()]
126
106
 
127
- return cls(**config_dict)
107
+ parameters_model = cls.get_parameters_model()
108
+ parameters = parameters_model.model_validate(config_dict)
109
+ return cls(parameters=parameters)
128
110
 
129
111
  def bdg_hamiltonian(self, k: npt.NDArray[np.float64]) -> npt.NDArray[np.complex64]:
130
112
  """
@@ -154,10 +136,11 @@ class BaseHamiltonian(ABC):
154
136
  :,
155
137
  self.number_of_bands : 2 * self.number_of_bands,
156
138
  self.number_of_bands : 2 * self.number_of_bands,
157
- ] = -self.hamiltonian(-k).conjugate()
139
+ ] = -self.hamiltonian(self.q - k).conjugate()
158
140
 
159
141
  for i in range(self.number_of_bands):
160
142
  h[:, self.number_of_bands + i, i] = self.delta_orbital_basis[i]
143
+
161
144
  h[:, 0 : self.number_of_bands, self.number_of_bands : self.number_of_bands * 2] = (
162
145
  h[:, self.number_of_bands : self.number_of_bands * 2, 0 : self.number_of_bands]
163
146
  .copy()
@@ -240,7 +223,8 @@ class BaseHamiltonian(ABC):
240
223
  return band_energies.squeeze(), bloch_wavefunctions.squeeze()
241
224
 
242
225
  def diagonalize_bdg(
243
- self, k: npt.NDArray[np.float64]
226
+ self,
227
+ k: npt.NDArray[np.float64],
244
228
  ) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.complex64]]:
245
229
  """
246
230
  Diagonalize the BdG Hamiltonian.
@@ -258,7 +242,7 @@ class BaseHamiltonian(ABC):
258
242
  Diagonalising matrix of the BdG Hamiltonian.
259
243
 
260
244
  """
261
- bdg_matrix = self.bdg_hamiltonian(k)
245
+ bdg_matrix = self.bdg_hamiltonian(k=k)
262
246
  if bdg_matrix.ndim == 2:
263
247
  bdg_matrix = np.expand_dims(bdg_matrix, axis=0)
264
248
  k = np.expand_dims(k, axis=0)
@@ -275,13 +259,13 @@ class BaseHamiltonian(ABC):
275
259
  return bdg_energies.squeeze(), bdg_wavefunctions.squeeze()
276
260
 
277
261
  def gap_equation(
278
- self, k: npt.NDArray[np.float64], beta: np.float64
262
+ self,
263
+ k: npt.NDArray[np.float64],
279
264
  ) -> npt.NDArray[np.complex64]:
280
265
  """Gap equation.
281
266
 
282
267
  Parameters
283
268
  ----------
284
- beta
285
269
  k
286
270
 
287
271
  Returns
@@ -291,28 +275,23 @@ class BaseHamiltonian(ABC):
291
275
 
292
276
 
293
277
  """
294
- bdg_energies, bdg_wavefunctions = self.diagonalize_bdg(k)
295
- bdg_energies_minus_k, _ = self.diagonalize_bdg(-k)
278
+ bdg_energies, bdg_wavefunctions = self.diagonalize_bdg(k=k)
296
279
  delta = np.zeros(self.number_of_bands, dtype=np.complex64)
297
280
 
298
281
  for i in range(self.number_of_bands):
299
282
  sum_tmp = 0
300
- for j in range(self.number_of_bands):
283
+ for j in range(2 * self.number_of_bands):
301
284
  for k_index in range(len(k)):
302
- sum_tmp += np.conjugate(bdg_wavefunctions[k_index, i, j]) * bdg_wavefunctions[
303
- k_index, i + self.number_of_bands, j
304
- ] * _fermi_dirac(
305
- bdg_energies[k_index, j + self.number_of_bands].item(), beta
306
- ) + np.conjugate(
307
- bdg_wavefunctions[k_index, i, j + self.number_of_bands]
308
- ) * bdg_wavefunctions[
309
- k_index, i + self.number_of_bands, j + self.number_of_bands
310
- ] * _fermi_dirac(
311
- -bdg_energies_minus_k[k_index, j + self.number_of_bands].item(), beta
285
+ sum_tmp += (
286
+ np.conjugate(bdg_wavefunctions[k_index, i, j])
287
+ * bdg_wavefunctions[k_index, i + self.number_of_bands, j]
288
+ * _fermi_dirac(bdg_energies[k_index, j].item(), self.beta)
312
289
  )
313
290
  delta[i] = (-self.hubbard_int_orbital_basis[i] * sum_tmp / len(k)).conjugate()
314
291
 
315
- delta_without_phase: npt.NDArray[np.complex64] = delta * np.exp(-1j * np.angle(delta[0]))
292
+ delta_without_phase: npt.NDArray[np.complex64] = delta * np.exp(
293
+ -1j * np.angle(delta[np.argmax(np.abs(delta))])
294
+ )
316
295
  return delta_without_phase
317
296
 
318
297
  def calculate_bandstructure(
@@ -358,27 +337,59 @@ class BaseHamiltonian(ABC):
358
337
  return results
359
338
 
360
339
  def calculate_density_of_states(
361
- self, k: npt.NDArray[np.float64], energies: npt.NDArray[np.float64]
362
- ) -> npt.NDArray[np.float64]:
340
+ self,
341
+ k: npt.NDArray[np.float64],
342
+ ) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]:
363
343
  """Calculate the density of states.
364
344
 
365
345
  Parameters
366
346
  ----------
367
347
  k
368
- energies
369
348
 
370
349
  Returns
371
350
  -------
372
351
  Density of states.
373
352
 
374
353
  """
375
- density_of_states = np.zeros(shape=energies.shape, dtype=np.float64)
376
354
  bands, _ = self.diagonalize_bdg(k=k)
355
+ energies = np.linspace(start=np.min(bands), stop=np.max(bands), num=5000)
356
+ density_of_states = np.zeros(shape=energies.shape, dtype=np.float64)
357
+
377
358
  for i, energy in enumerate(energies):
378
359
  density_of_states[i] = np.sum(
379
360
  _gaussian(x=(energy - bands.flatten()), sigma=1e-2)
380
361
  ) / len(k)
381
- return density_of_states
362
+ return energies, density_of_states
363
+
364
+ def calculate_spectral_gap(self, k: npt.NDArray[np.float64]) -> float:
365
+ """Calculate the spectral gap.
366
+
367
+ Parameters
368
+ ----------
369
+ k
370
+
371
+ Returns
372
+ -------
373
+ Spectral gap
374
+
375
+ """
376
+ energies, density_of_states = self.calculate_density_of_states(k=k)
377
+
378
+ coherence_peaks = np.where(np.isclose(density_of_states, np.max(density_of_states)))[0]
379
+
380
+ gap_region = density_of_states[coherence_peaks[0] : coherence_peaks[1] + 1] / np.max(
381
+ density_of_states
382
+ )
383
+ energies_gap_region = energies[coherence_peaks[0] : coherence_peaks[1] + 1]
384
+ zero_indeces = np.where(gap_region <= 1e-10)[0]
385
+ if len(zero_indeces) == 0:
386
+ gap = 0
387
+ else:
388
+ gap = (
389
+ energies_gap_region[zero_indeces[-1]] - energies_gap_region[zero_indeces[0]]
390
+ ).item()
391
+
392
+ return gap
382
393
 
383
394
 
384
395
  def _gaussian(x: npt.NDArray[np.float64], sigma: float) -> npt.NDArray[np.float64]:
@@ -388,6 +399,6 @@ def _gaussian(x: npt.NDArray[np.float64], sigma: float) -> npt.NDArray[np.float6
388
399
  return gaussian
389
400
 
390
401
 
391
- def _fermi_dirac(energy: np.float64, beta: np.float64) -> np.float64:
392
- fermi_dirac: np.float64 = 1 / (1 + np.exp(beta * energy))
402
+ def _fermi_dirac(energy: float, beta: float) -> float:
403
+ fermi_dirac: float = 1 / (1 + np.exp(beta * energy))
393
404
  return fermi_dirac