Source code for sisl.physics.brillouinzone

""" Different tools for contsructing k-points and paths in the Brillouin zone """

from __future__ import print_function, division

import warnings
from numbers import Integral

from numpy import pi
import numpy as np
from numpy import sum, dot

from sisl.supercell import SuperCell, SuperCellChild


__all__ = ['BrillouinZone', 'PathBZ']


[docs]class BrillouinZone(SuperCellChild): """ A class to construct Brillouin zone related quantities It takes a super-cell as an argument and can then return the k-points in non-reduced units from reduced units. """ def __init__(self, sc): """ Initialize a `BrillouinZone` object from a given `SuperCell` Parameters ---------- sc : SuperCell or array_like the attached supercell """ self.set_supercell(sc)
[docs] def kb(self, k): """ Return the k-point in reduced coordinates """ return sum(dot(k, self.cell) * 0.5 / pi, axis=0)
[docs] def k(self, kb): """ Return the k-point in 1/Ang coordinates """ return dot(self.rcell, kb)
def __call__(self, k, reduced=True): """ Return the k-point in 1/Ang coordinates Parameters ---------- k : array_like of float the input k-point (in units determined by `reduced`) reduced : bool whether the input k-point is in reduced coordinates If `True` it returns the k-points according to the supercell, else it returns the reduced k-point. """ if reduced: return self.k(k) return self.kb(k) def __iter__(self): """ Returns all k-points associated with this Brillouin zone object The default `BrillouinZone` class only has the Gamma point """ yield np.zeros([3], np.float64) def __len__(self): return 1
[docs]class PathBZ(BrillouinZone): """ Create a path in the Brillouin zone for plotting band-structures etc. """ def __init__(self, sc, points, divisions): """ Instantiate the `PathBZ` by a set of special `points` separated in `divisions` Parameters ---------- sc : SuperCell or array_like the unit-cell of the Brillouin zone points : array_like of float a list of points that are the *corners* of the path divisions : int or array_like of int number of divisions in each segment. If a single integer is passed it is the total number of points on the path (equally separated). If it is an array_like input it must have length one less than `points`. """ self.set_supercell(sc) # Copy over points self.points = np.array(points, dtype=np.float64) nk = len(self.points) # If the array has fewer points we try and determine if self.points.shape[1] < 3: if self.points.shape[1] != np.sum(self.nsc > 1): raise ValueError('Could not determine the non-periodic direction') # fix the points where there are no periodicity for i in [0, 1, 2]: if self.nsc[i] == 1: self.points = np.insert(self.points, i, 0., axis=1) # Ensure the shape is correct self.points.shape = (-1, 3) # Now figure out what to do with the divisions if isinstance(divisions, Integral): # Calculate points (we need correct units for distance) kpts = [self(point) for point in self.points] if len(kpts) == 2: dists = [sum(np.diff(kpts, axis=0) ** 2) ** .5] else: dists = sum(np.diff(kpts, axis=0)**2, axis=1) ** .5 dist = sum(dists) div = np.array(np.floor(dists / dist * divisions), np.int32) n = sum(div) if n < divisions: div[-1] +=1 n = sum(div) while n < divisions: # Get the separation of k-points delta = dist / n idx = np.amin(dists - delta * div) div[idx] += 1 n = sum(div) divisions = div[:] self.divisions = np.array(divisions, np.int32) self.divisions.shape = (-1,) def __iter__(self): """ Iterate through the path """ # Calculate points dk = np.diff(self.points, axis=0) for i in range(len(dk)): # Calculate this delta if i == len(dk) - 1: # to get end-point delta = dk[i, :] / (self.divisions[i] - 1) else: delta = dk[i, :] / self.divisions[i] for j in range(self.divisions[i]): yield self.points[i] + j * delta
[docs] def lineark(self): """ A 1D array which corresponds to the delta-k values of the path This is meant for plotting Examples -------- >>> p = PathBZ(...) >>> eigs = Hamiltonian.eigh(p) >>> for i in range(len(Hamiltonian)): >>> pyplot.plot(p.lineark(), eigs[:, i]) """ nk = len(self) # Calculate points k = [self(point) for point in self.points] dk = np.diff(k, axis=0) # Prepare output array dK = np.empty(nk, np.float64) ii, add = 0, 0. for i in range(len(dk)): n = self.divisions[i] # Calculate the delta along this segment delta = sum(dk[i, :] ** 2) ** .5 if i == len(dk) - 1: # to get end-point correctly delta /= n - 1 else: delta /= n dK[ii:ii+n] = np.linspace(add, add + delta * n, n, dtype=np.float64) ii += n # Calculate the next separation # The addition is the latest delta point plus the # missing delta to reach the starting point for the # next point in the BZ add = dK[ii-1] + delta return dK
def __len__(self): return sum(self.divisions)