Commit d358f668 authored by Kloss's avatar Kloss Committed by Joseph Weston

change matrix computation depending on size, change variable names to be more explicit

parent 5aa6d068
......@@ -6,6 +6,8 @@
# the file AUTHORS.rst at the top-level directory of this distribution and at
# http://kwant-project.org/authors.
"""Band structure calculation for the leads"""
import math
import numpy as np
from .. import system
......@@ -35,8 +37,8 @@ class Bands:
An instance of this class can be called like a function. Given a momentum
(currently this must be a scalar as all infinite systems are quasi-1-d), it
returns a NumPy array containing the eigenenergies of all modes at this
momentum. Velocities and velocity derivatives are calculated if the flag
``derivative_order`` is set.
momentum. If `derivative_order > 0` or `return_eigenvectors = True`,
additional arrays are returned returned.
Examples
--------
......@@ -58,15 +60,24 @@ class Bands:
self.hop[:, : hop.shape[1]] = hop
self.hop[:, hop.shape[1]:] = 0
def __call__(self, k, *, derivative_order=0, return_eigenvectors=False):
"""Calculate all energies :math:`E`, velocities :math:`v`
and velocity derivatives `v'` for a given momentum :math:`k`
# Switch computation from diag(a^\dagger @ b) to sum(a.T * b, axis=0),
# which is computationally more efficient for larger matrices.
# a and b are NxN matrices, and we switch for N > _crossover_size.
# Both ways are numerically (but not necessarily bitwise) equivalent.
self._crossover_size = 8
def __call__(self, k, derivative_order=0, return_eigenvectors=False):
"""Calculate all energies :math:`E` (optionally higher derivatives
:math:`E', E''` and eigenvectors) for a given momentum :math:`k`
:math:`E_n, \quad v_n = dE_n / dk, \quad v'_n = d^2E_n / dk^2,
:math:`E_n, \quad E'_n = dE_n / dk, \quad E''_n = d^2E_n / dk^2,
\quad n \in \{0, nbands - 1\}`
:math:`nbands` is the number of open modes
Eigenvectors are orthonormal.
Parameters
----------
k : float
......@@ -75,17 +86,27 @@ class Bands:
derivative_order : {0, 1, 2}, optional
Maximal derivative order to calculate. Default is zero
return_eigenvectors : bool, optional
if set to `True` return the eigenvectors as last tuple element
Returns
----------
ener : numpy float array
energies (and optionally also higher momentum derivatives)
if derivative_order = 0
numpy float array of the energies :math:`E`, shape (nbands,)
if derivative_order > 0
numpy float array, shape (derivative_order + 1, nbands) of
energies and derivatives :math:`(E, E', E'')`
energies : numpy float array, size `nbands`
energies :math:`E`
velocities : numpy float array, size `nbands`
velocities (first order energy derivatives :math:`E'`)
curvatures : numpy float array, size `nbands`
curvatures (second energy derivatives :math:`E''`)
eigenvectors : numpy float array, size `nbands`
eigenvectors
The number of retured elements varies. If `derivative_order > 0`
we return all derivatives up to the order `derivative_order`,
and total the number of returned elements is `derivative_order + 1`
If 'return_eigenvectors=True', in addition the eigenvectors are
returned as the last element. In that case, the total number of
returned elements is `derivative_order + 2`.
Notes
-----
......@@ -98,33 +119,43 @@ class Bands:
# Equation to solve is
# (V^\dagger e^{ik} + H + V e^{-ik}) \psi = E \psi
# we obtain the derivatives by perturbing around momentum k
# The perturbed Hamiltonian H(k) is
# H(k+dk) = H_0(k) + H_1(k) dk + H_2(k) dk^2 + O(dk^3)
# and we use h0, h1 and h2 in the code to refer to above terms
mat = self.hop * complex(math.cos(k), -math.sin(k))
ham = mat + mat.conjugate().transpose() + self.ham
h0 = mat + mat.conjugate().transpose() + self.ham
if derivative_order == 0:
if return_eigenvectors:
ener, psis = np.linalg.eigh(ham)
return ener, psis
return np.sort(np.linalg.eigvalsh(ham).real)
energies, eigenvectors = np.linalg.eigh(h0)
return energies, eigenvectors
return np.sort(np.linalg.eigvalsh(h0).real)
ener, psis = np.linalg.eigh(ham)
energies, eigenvectors = np.linalg.eigh(h0)
h1 = 1j*(- mat + mat.conjugate().transpose())
ph1p = np.dot(psis.conjugate().transpose(), np.dot(h1, psis))
velo = np.real(np.diag(ph1p))
if derivative_order == 1 and len(energies) > self._crossover_size:
velocities = np.sum(eigenvectors.conjugate() * (h1 @ eigenvectors),
axis=0).real
else:
ph1p = eigenvectors.conjugate().transpose() @ h1 @ eigenvectors
velocities = np.diag(ph1p).real
if derivative_order == 1:
if return_eigenvectors:
return ener, velo, psis
return ener, velo
return energies, velocities, eigenvectors
return energies, velocities
ediff = ener.reshape(-1, 1) - ener.reshape(1, -1)
# ediff_{i,j} = 1 / (E_i - E_j) if i != j, else 0
ediff = energies.reshape(-1, 1) - energies.reshape(1, -1)
ediff = np.divide(1, ediff, out=np.zeros_like(ediff), where=ediff != 0)
h2 = - (mat + mat.conjugate().transpose())
curv = (np.real(np.diag(
np.dot(psis.conjugate().transpose(), np.dot(h2, psis)))) +
2 * np.sum(ediff * np.abs(ph1p)**2, axis=1))
curvatures = np.sum(eigenvectors.conjugate() * (h2 @ eigenvectors)
- 2 * ediff * np.abs(ph1p)**2, axis=0).real
# above expression is similar to: curvatures =
# np.diag(eigenvectors.conjugate().transpose() @ h2 @ eigenvectors
# + 2 * ediff @ np.abs(ph1p)**2).real
if derivative_order == 2:
if return_eigenvectors:
return ener, velo, curv, psis
return ener, velo, curv
return energies, velocities, curvatures, eigenvectors
return energies, velocities, curvatures
raise NotImplementedError('derivative_order= {} not implemented'
.format(derivative_order))
......@@ -99,7 +99,8 @@ def test_band_velocity_derivative():
def test_eigenvector_calculation(k=1.2, lead=make_lead()): # k-point arbitrary
bands = kwant.physics.Bands(lead.finalized())
lead = lead.finalized()
bands = kwant.physics.Bands(lead)
# check if eigenvalues are sorted and always sorted in the same way
energies = bands(k)
......@@ -134,6 +135,14 @@ def test_eigenvector_calculation(k=1.2, lead=make_lead()): # k-point arbitrary
energies = np.asarray([energies])
assert_array_almost_equal(hamiltonian @ psi, energies * psi)
# check that algorithm switch is correct
bands_switch = kwant.physics.Bands(lead)
bands_switch._crossover_size = 0
for order in [1, 2]:
e_1, v_1, *_ = bands(k, order)
e_2, v_2, *_ = bands_switch(k, order)
assert_array_almost_equal(v_1, v_2)
def test_raise_implemented(k=1, lead=make_lead()): # k-point arbitrary
bands = kwant.physics.Bands(lead.finalized())
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment