Source code for sisl.physics.bloch

r"""Bloch's theorem
===================

.. module:: sisl.physics.bloch
   :noindex:

Bloch's theorem is a very powerful proceduce that enables one to utilize
the periodicity of a given direction to describe the complete system.


.. autosummary::
   :toctree:

   Bloch

"""
from __future__ import print_function, division

import numpy as np
from numpy import zeros, empty
from numpy import add, multiply
from numpy import exp

from sisl._help import dtype_real_to_complex
import sisl._array as _a
from sisl._array import aranged
from ._bloch import bloch_unfold


__all__ = ['Bloch']


[docs]class Bloch(object): r""" Bloch's theorem object containing unfolding factors and unfolding algorithms This class is a wrapper for expanding *any* matrix from a smaller matrix cell into a larger, using Bloch's theorem. The general idea may be summarized in the following equation: .. math:: \mathbf M_{k}^N =\frac1N \; \sum_{ \substack{j=0\\ k_j=2\pi\frac{k+j}N } }^{N-1} \quad \begin{bmatrix} 1 & \cdots & e^{i (1-N)k_j} \\ e^{i k_j} & \cdots & e^{i (2-N)k_j} \\ \vdots & \ddots & \vdots \\ e^{i (N-1)k_j} & \cdots & 1 \end{bmatrix} \otimes \mathbf M_{k_j}^1. Parameters ---------- bloch : (3,) int Bloch repetitions along each direction Examples -------- >>> bloch = Bloch([2, 1, 2]) >>> k_unfold = bloch.unfold_points([0] * 3) >>> M = [func(*args, k=k) for k in k_unfold] >>> bloch.unfold(M, k_unfold) """ def __init__(self, *bloch): """ Create `Bloch` object """ self._bloch = _a.arrayi(bloch).ravel() self._bloch = np.where(self._bloch < 1, 1, self._bloch) if len(self._bloch) != 3: raise ValueError(self.__class__.__name__ + ' requires 3 input values') if np.any(self._bloch < 1): raise ValueError(self.__class__.__name__ + ' requires all unfoldings to be larger than 0') def __len__(self): """ Return unfolded size """ return np.prod(self.bloch) def __str__(self): """ Representation of the Bloch model """ B = self._bloch return self.__class__.__name__ + '{{{0}, {1}, {2}}}'.format(B[0], B[1], B[2]) @property def bloch(self): """ Number of Bloch expansions along each lattice vector """ return self._bloch
[docs] def unfold_points(self, k): r""" Return a list of k-points to be evaluated for this objects unfolding The k-point `k` is with respect to the unfolded geometry. The return list of `k` points are the k-points required to be sampled in the folded geometry (``this.parent``). Parameters ---------- k : (3,) of float k-point evaluation corresponding to the unfolded unit-cell Returns ------- k_unfold : a list of ``np.prod(self.bloch)`` k-points used for the unfolding """ k = _a.arrayd(k) # Create expansion points B = self._bloch unfold = _a.emptyd([B[2], B[1], B[0], 3]) # Use B-casting rules (much simpler) unfold[:, :, :, 0] = (aranged(B[0]).reshape(1, 1, -1) + k[0]) / B[0] unfold[:, :, :, 1] = (aranged(B[1]).reshape(1, -1, 1) + k[1]) / B[1] unfold[:, :, :, 2] = (aranged(B[2]).reshape(-1, 1, 1) + k[2]) / B[2] # Back-transform shape return unfold.reshape(-1, 3)
def __call__(self, func, k, *args, **kwargs): """ Return a functions return values as the Bloch unfolded equivalent according to this object Calling the `Bloch` object is a shorthand for the manual use of the `Bloch.unfold_points` and `Bloch.unfold` methods. This call structure is a shorthand for: >>> bloch = Bloch([2, 1, 2]) >>> k_unfold = bloch.unfold_points([0] * 3) >>> M = [func(*args, k=k) for k in k_unfold] >>> bloch.unfold(M, k_unfold) Notes ----- The function passed *must* have a keyword argument ``k``. Parameters ---------- func : callable method called which returns a matrix. k : (3, ) of float k-point to be unfolded *args : list arguments passed directly to `func` **kwargs : dict keyword arguments passed directly to `func` Returns ------- M : unfolded Bloch matrix """ K_unfold = self.unfold_points(k) M0 = func(*args, k=K_unfold[0, :], **kwargs) shape = (K_unfold.shape[0], M0.shape[0], M0.shape[1]) M = empty(shape, dtype=dtype_real_to_complex(M0.dtype)) M[0] = M0 del M0 for i in range(1, K_unfold.shape[0]): M[i] = func(*args, k=K_unfold[i, :], **kwargs) return bloch_unfold(_a.arrayi(self._bloch), K_unfold, M)
[docs] def unfold(self, M, k_unfold): r""" Unfold the matrix list of matrices `M` into a corresponding k-point (unfolding k-points are `k_unfold`) Parameters ---------- M : (*, :, :) an *-N-M matrix used for unfolding k_unfold : (*, 3) of float unfolding k-points as returned by `Bloch.unfold_points` Returns ------- M_unfold : unfolded matrix of size ``M[0].shape * k_unfold.shape[0] ** 2`` """ if isinstance(M, (list, tuple)): M = np.stack(M) return bloch_unfold(_a.arrayi(self._bloch), k_unfold, M)