# #
# This file is distributed as part of the WannierBerri code #
# under the terms of the GNU General Public License. See the #
# file `LICENSE' in the root directory of the WannierBerri #
# distribution, or http://www.gnu.org/copyleft/gpl.txt #
# #
# The WannierBerri code is hosted on GitHub: #
# https://github.com/stepan-tsirkin/wannier-berri #
# written by #
# Stepan Tsirkin, University of Zurich #
# #
# ------------------------------------------------------------
import numpy as np
import warnings
from .Kpoint_tetra import KpointBZtetra
from .grid import GridAbstract, determineNK
from ..utility import angle_vectors_deg
[docs]
class GridTetra(GridAbstract):
""" A class containing information about the k-grid.konsisting of tetrahedra
Parameters
-----------
system : :class:`~wannierberri.system.System`
which the calculations will be made
length : float
(angstroms) -- in this case the grid is NK[i]=length*||B[i]||/2pi B- reciprocal lattice
length_FFT : float
(angstroms) -- in this case the FFT grid is NKFFT[i]=length_FFT*||B[i]||/2pi B- reciprocal lattice
NKFFT : int
number of k-points in the FFT grid along each directions
IBZ_tetra : list
list of tetrahedra describing the irreducible wedge of the Brillouin zone. By default, the stace is just divided into 5 tetrahedra
Notes
-----
`NKFFT` may be given as size-3 integer arrays or lists. Also may be just numbers -- in that case the number of kppoints is the same in all directions
either lewngth_FFT of NKFFT should be provided
"""
def __init__(self, system, length, NKFFT=None, length_FFT=None, IBZ_tetra=None, weights=None,
refine_by_volume=True,
refine_by_size=True,
length_size=None
):
_, self.FFT = determineNK(periodic=system.periodic, NK=None,
NKdiv=1, NKFFT=NKFFT, NKFFT_recommended=system.NKFFT_recommended,
pointgroup=system.pointgroup, length=None, length_FFT=length_FFT)
self.recip_lattice_reduced = system.recip_lattice / self.FFT[:, None]
print("reduced reciprocal lattice : \n", self.recip_lattice_reduced)
if IBZ_tetra is None: # divide the full reciprocal unit cell into 5 tetrahedra -
warnings.warn("irreducible wedge not provided, no use of symmetries")
tetrahedra = np.array([[[0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1]],
[[1, 0, 1], [0, 0, 1], [1, 0, 0], [1, 1, 1]],
[[1, 1, 0], [1, 0, 0], [0, 1, 0], [1, 1, 1]],
[[0, 1, 1], [0, 0, 1], [0, 1, 0], [1, 1, 1]],
[[0, 0, 1], [0, 1, 0], [1, 0, 0], [1, 1, 1]]
]) - np.array([0.5, 0.5, 0.5])[None, None, :]
else:
tetrahedra = np.array(IBZ_tetra)
print("using starting tetrahedra with vertices \n", tetrahedra)
_weights = np.array([tetra_volume(t) for t in tetrahedra])
print(f"volumes of tetrahedra are {_weights}, total = {sum(_weights)} ")
if weights is None:
weights = _weights / sum(_weights)
else:
weights = np.array(weights) * _weights
print(f"weights of tetrahedra are {weights}, total = {sum(weights)} ")
self.K_list = []
print("generating starting K_list")
for tetr, w in zip(tetrahedra, weights):
K = KpointBZtetra(vertices=tetr, K=0, NKFFT=self.FFT, factor=w, basis=self.recip_lattice_reduced,
refinement_level=0, split_level=0)
print(K)
print(K.size)
self.K_list.append(K)
if refine_by_volume:
dkmax = 2 * np.pi / length
vmax = dkmax ** 3 / np.linalg.det(self.recip_lattice_reduced)
self.split_tetra_volume(vmax)
print("refinement by volume done")
if refine_by_size:
if length_size is None:
length_size = 0.5 * length
dkmax = (2 * np.pi / length_size) * np.sqrt(2)
self.split_tetra_size(dkmax)
print("refinement by size done")
def split_tetra_size(self, dkmax):
"""split tetrahedra that have at lkeast one edge larger than dkmax"""
while True:
print(
f"maximal tetrahedron size for now is {self.size_max} ({len(self.K_list)}), we need to refine down to size {dkmax}")
volumes = [tetra_volume(K.vertices) for K in self.K_list]
print("the volume is ", sum(volumes), min(volumes), max(volumes), np.mean(volumes))
# print ("sizes now are ",self.sizes)
if self.size_max < dkmax:
break
klist = []
for K in self.K_list:
if K.size > dkmax:
klist += K.divide(ndiv=2, refine=False)
else:
klist.append(K)
self.K_list = klist
def split_tetra_volume(self, vmax):
"""split tetrahedra that have at least one edge larger than dkmax"""
while True:
volumes = [tetra_volume(K.vertices) for K in self.K_list]
print(
f"maximal tetrahedron size for now is {max(volumes)} ({len(self.K_list)}), we need to refine down to size {vmax}")
print("the volume is ", sum(volumes), min(volumes), max(volumes), np.mean(volumes))
if max(volumes) < vmax:
break
klist = []
for K, v in zip(self.K_list, volumes):
if v > vmax:
klist += K.divide(ndiv=2, refine=False)
else:
klist.append(K)
self.K_list = klist
@property
def size_max(self):
return self.sizes.max()
@property
def sizes(self):
return np.array([K.size for K in self.K_list])
@property
def str_short(self):
return f"GridTetra() with {len(self.K_list)} tetrahedrons, " \
f"NKFFT={self.FFT}, NKtot={np.prod(self.FFT) * len(self.K_list)}"
def get_K_list(self, use_symmetry=True):
""" returns the list of Symmetry-irreducible K-points"""
return [K.copy() for K in self.K_list]
def tetra_volume(vortices):
return abs(np.linalg.det(vortices[1:] - vortices[0][None, :])) / 6.
[docs]
class GridTrigonal(GridTetra):
""" good choice for Tellurium"""
def __init__(self, system, length, **kwargs):
# these ones are for the case when the reciprocal lattice vectors form a 120deg angle
IBZ_tetra = np.array([
[[0, 0, 0], [1 / 3, 2 / 3, 0.0], [2 / 3, 1 / 3, 0.0], [1 / 3, 2 / 3, 0.5]],
[[0, 0, 0], [2 / 3, 1 / 3, 0.5], [2 / 3, 1 / 3, 0.0], [1 / 3, 2 / 3, 0.5]],
[[0, 0, 0], [2 / 3, 1 / 3, 0.5], [0, 0, 0.5], [1 / 3, 2 / 3, 0.5]],
])
b1, b2, b3 = system.recip_lattice
assert angle_vectors_deg(b1, b3) == 90
assert angle_vectors_deg(b2, b3) == 90
assert angle_vectors_deg(b1, b2) in (60, 120)
if angle_vectors_deg(b1, b2) == 60:
IBZ_tetra[:, :, 0] = IBZ_tetra[:, :, 0] - IBZ_tetra[:, :, 1]
super().__init__(system, length, IBZ_tetra=IBZ_tetra, **kwargs)
[docs]
class GridTrigonalH(GridTetra):
""" good choice for Tellurium conduction/valence band, only a small part near the H-point is considered,
always use NKFFT=1"""
def __init__(self, system, length, x=0.5, NKFFT=1, **kwargs):
# these ones are for the case when the reciprocal lattice vectors form a 120deg angle
H = np.array([2 / 3, 1 / 3, 1 / 2])
K = np.array([2 / 3, 1 / 3, 0])
H1 = np.array([1 / 3, -1 / 3, 1 / 2])
A = (H - K)
IBZ_tetra = np.array([[H, H + x * (K - H), H + x * (A - H), H + x * (H1 - H)],
[H, H - x * (K - H), H + x * (A - H), H + x * (H1 - H)]])
b1, b2, b3 = system.recip_lattice
assert angle_vectors_deg(b1, b3) == 90
assert angle_vectors_deg(b2, b3) == 90
assert angle_vectors_deg(b1, b2) in (60, 120)
if angle_vectors_deg(b1, b2) == 60:
IBZ_tetra[:, :, 0] = IBZ_tetra[:, :, 0] - IBZ_tetra[:, :, 1]
super().__init__(system, length, IBZ_tetra=IBZ_tetra, weights=[12, 12], NKFFT=1, **kwargs)