"""This module implements classes related to the brillouinzone."""
from abc import ABC, abstractproperty
from dataclasses import dataclass
from typing import List, Tuple, Union
import numpy as np
from numpy.linalg import norm
@dataclass
class BlochVectors:
"""Represents a data structure of Bloch wave vectors."""
values: np.ndarray
cumsum: np.ndarray
[docs]class SymmetryPoint:
"""Model of a symmetry point at the brillouinzone.
Parameters
----------
point : Tuple[float, ...]
A point in the reciprocal domain.
name : str
A label to describe the point.
"""
def __init__(self, point: Tuple[float, ...], name: str):
"""Initialize a SymmetryPoint."""
self.point = np.array(point)
self.name = name
[docs]class BrillouinZonePathBase(ABC):
"""BrillouinZoneBase class."""
def __init__(
self, path: List[SymmetryPoint], n_points: int = 50, strategy: str = "linear"
):
self.path_points = path
self.n_points = n_points
self.strat = strategy
self._path: np.ndarray
@abstractproperty
def betas(self) -> BlochVectors:
"""Return bloch wave vectors."""
@staticmethod
def _interpolate_beta(beta_cs, path, n_points):
"""Perform linear interpolation."""
beta_csi = np.linspace(0, beta_cs[-1], n_points)
beta_vi = np.vstack(
[
np.interp(beta_csi, beta_cs, path[i, :]).flatten()
for i in range(path.shape[0])
]
)
return BlochVectors(beta_vi, beta_csi)
@property
def point_locations(self):
"""Return cumulative sum of vectors' length."""
return np.concatenate(
(np.zeros(1), np.cumsum(norm(np.diff(self._path, axis=1), axis=0)))
)
@property
def point_names(self):
"""Return list of symmetry point names."""
return [p.name for p in self.path_points]
[docs]class BrillouinZonePath1D(BrillouinZonePathBase):
"""BrillouinZonePath1D.
Parameters
----------
a1 : Tuple[float]
Direct lattice vector a1.
path : List[SymmetryPoint]
List of symmetry points.
n_points : int
Number of vectors.
strategy : str = 'linear'
Strategy to interpolate.
"""
def __init__(
self,
a1: Tuple[float],
path: List[SymmetryPoint],
n_points: int = 50,
strategy: str = "linear",
):
"""Initialize a BrillouinZonePath."""
super().__init__(path, n_points, strategy)
self.a1 = np.array(a1)
self.dim = 1
@property
def _path(self) -> np.ndarray:
"""Return the path matrix."""
path = np.stack([p.point for p in self.path_points], axis=1)
return path[0, :] * self.b1[:, None]
@property
def betas(self) -> BlochVectors:
"""Return beta vector values and cumsum."""
beta_cs = np.cumsum(norm(np.diff(self._path, axis=1), axis=0))
beta_cs = np.pad(beta_cs, (1, 0), "constant")
if self.strat == "linear":
return self._interpolate_beta(beta_cs, self._path, self.n_points)
raise NotImplementedError
@property
def b1(self) -> np.ndarray:
"""Return reciprocal lattice vector b1."""
return 2 * np.pi / self.a1
[docs]class BrillouinZonePath2D(BrillouinZonePathBase):
"""BrillouinZonePath2D.
Parameters
----------
a1 : Tuple[float, float]
Direct lattice vector a1.
a2 : Tuple[float, float]
Direct lattice vector a2.
path : List[SymmetryPoint]
List of symmetry points.
n_points : int
Number of vectors.
strategy : str = 'linear'
Strategy to interpolate.
"""
def __init__(
self,
a1: Tuple[float, float],
a2: Tuple[float, float],
path: List[SymmetryPoint],
n_points: int = 50,
strategy: str = "linear",
):
"""Initialize a BrillouinZonePath."""
super().__init__(path, n_points, strategy)
self.a1 = np.array(a1)
self.a2 = np.array(a2)
self.dim = 2
@property
def _path(self) -> np.ndarray:
"""Return the path matrix."""
path = np.stack([p.point for p in self.path_points], axis=1)
return path[0, :] * self.b1[:, None] + path[1, :] * self.b2[:, None]
@property
def betas(self) -> BlochVectors:
"""Return beta vector values and cumsum."""
beta_cs = np.cumsum(norm(np.diff(self._path, axis=1), axis=0))
beta_cs = np.pad(beta_cs, (1, 0), "constant")
if self.strat == "linear":
return self._interpolate_beta(beta_cs, self._path, self.n_points)
raise NotImplementedError
@property
def b1(self) -> np.ndarray:
"""Return reciprocal lattice vector b1."""
Q = np.array([[0, -1], [1, 0]])
return 2 * np.pi * Q @ self.a2 / np.dot(self.a1, Q @ self.a2)
@property
def b2(self) -> np.ndarray:
"""Return reciprocal lattice vector b2."""
Q = np.array([[0, -1], [1, 0]])
return 2 * np.pi * Q @ self.a1 / np.dot(self.a2, Q @ self.a1)
[docs]class BrillouinZonePath3D(BrillouinZonePathBase):
"""BrillouinZonePath3D.
Parameters
----------
a1 : Tuple[float, float, float]
Direct lattice vector a1.
a2 : Tuple[float, float, float]
Direct lattice vector a2.
a3 : Tuple[float, float, float]
Direct lattice vector a3.
path : List[SymmetryPoint]
List of symmetry points.
n_points : int
Number of vectors.
strategy : str = 'linear'
Strategy to interpolate.
"""
def __init__(
self,
a1: Tuple[float, float, float],
a2: Tuple[float, float, float],
a3: Tuple[float, float, float],
path: List[SymmetryPoint],
n_points: int = 50,
strategy: str = "interpolate",
):
"""Initialize a BrillouinZonePath."""
super().__init__(path, n_points, strategy)
self.a1 = np.array(a1)
self.a2 = np.array(a2)
self.a3 = np.array(a3)
self.dim = 3
@property
def _path(self) -> np.ndarray:
"""Return the path matrix."""
path = np.stack([p.point for p in self.path_points], axis=1)
return (
path[0, :] * self.b1[:, None]
+ path[1, :] * self.b2[:, None]
+ path[2, :] * self.b3[:, None]
)
@property
def betas(self) -> BlochVectors:
"""Return beta vector values and cumsum."""
beta_cs = np.cumsum(norm(np.diff(self._path, axis=1), axis=0))
beta_cs = np.pad(beta_cs, (1, 0), "constant")
if self.strat == "interpolate":
return self._interpolate_beta(beta_cs, self._path, self.n_points)
raise NotImplementedError
@property
def b1(self) -> np.ndarray:
"""Return reciprocal lattice vector b1."""
return (
2
* np.pi
* np.cross(self.a2, self.a3)
/ np.dot(self.a1, np.cross(self.a2, self.a3))
)
@property
def b2(self) -> np.ndarray:
"""Return reciprocal lattice vector b2."""
return (
2
* np.pi
* np.cross(self.a3, self.a1)
/ np.dot(self.a1, np.cross(self.a2, self.a3))
)
@property
def b3(self) -> np.ndarray:
"""Return reciprocal lattice vector b3."""
return (
2
* np.pi
* np.cross(self.a1, self.a2)
/ np.dot(self.a1, np.cross(self.a2, self.a3))
)
[docs]def BrillouinZonePath(
a1, *args, **kwargs
) -> Union[BrillouinZonePath1D, BrillouinZonePath2D, BrillouinZonePath3D]:
"""BrillouinZonePath factory."""
dim_obj = {1: BrillouinZonePath1D, 2: BrillouinZonePath2D, 3: BrillouinZonePath3D}
return dim_obj[len(a1)](a1, *args, **kwargs)