From 0f60badf2d0c01562e791cb53e0706b9b1b82430 Mon Sep 17 00:00:00 2001
From: Pablo Piskunow <pablo.perez.piskunow@gmail.com>
Date: Wed, 28 Nov 2018 17:19:58 +0100
Subject: [PATCH] update kpm tutorial

---
 .../figure/kernel_polynomial_method.py.diff   | 154 +++++++++++--
 doc/source/tutorial/kpm.rst                   | 208 ++++++++++++++----
 2 files changed, 309 insertions(+), 53 deletions(-)

diff --git a/doc/source/code/figure/kernel_polynomial_method.py.diff b/doc/source/code/figure/kernel_polynomial_method.py.diff
index 8c28b5e8..b12ebf52 100644
--- a/doc/source/code/figure/kernel_polynomial_method.py.diff
+++ b/doc/source/code/figure/kernel_polynomial_method.py.diff
@@ -40,36 +40,89 @@
      return syst
  #HIDDEN_END_sys1
  
+ #HIDDEN_BEGIN_sys2
+ # define a Haldane system
+ def make_syst_topo(r=30, a=1, t=1, t2=0.5):
+     syst = kwant.Builder()
+     lat = kwant.lattice.honeycomb(a, norbs=1, name=['a', 'b'])
+ 
+     def circle(pos):
+         x, y = pos
+         return x ** 2 + y ** 2 < r ** 2
+ 
+     syst[lat.shape(circle, (0, 0))] = 0.
+     syst[lat.neighbors()] = t
+     # add second neighbours hoppings
+     syst[lat.a.neighbors()] = 1j * t2
+     syst[lat.b.neighbors()] = -1j * t2
+     syst.eradicate_dangling()
+ 
+     return lat, syst.finalized()
+ #HIDDEN_END_sys2
+ 
+ 
+ #HIDDEN_BEGIN_sys3
+ # define the system
+ def make_syst_staggered(r=30, t=-1, a=1, m=0.1):
+     syst = kwant.Builder()
+     lat = kwant.lattice.honeycomb(a, norbs=1)
+ 
+     def circle(pos):
+         x, y = pos
+         return x ** 2 + y ** 2 < r ** 2
+ 
+     syst[lat.a.shape(circle, (0, 0))] = m
+     syst[lat.b.shape(circle, (0, 0))] = -m
+     syst[lat.neighbors()] = t
+     syst.eradicate_dangling()
+ 
+     return syst
+ #HIDDEN_END_sys3
  
  # Plot several density of states curves on the same axes.
 -def plot_dos(labels_to_data):
-+def plot_dos(labels_to_data, file_name=None):
++def plot_dos(labels_to_data, file_name=None, ylabel="DoS [a.u.]"):
+     plt.figure(figsize=(5,4))
      for label, (x, y) in labels_to_data:
-         plt.plot(x, y, label=label, linewidth=2)
+         plt.plot(x, y.real, label=label, linewidth=2)
      plt.legend(loc=2, framealpha=0.5)
      plt.xlabel("energy [t]")
-     plt.ylabel("DoS [a.u.]")
+     plt.ylabel(ylabel)
 -    plt.show()
 +    save_figure(file_name)
      plt.clf()
  
  
+ # Plot fill density of states plus curves on the same axes.
+-def plot_dos_and_curves(dos labels_to_data):
++def plot_dos_and_curves(dos, labels_to_data, file_name=None, ylabel="DoS [a.u.]"):
+     plt.figure(figsize=(5,4))
+     plt.fill_between(dos[0], dos[1], label="DoS [a.u.]",
+                      alpha=0.5, color='gray')
+     for label, (x, y) in labels_to_data:
+         plt.plot(x, y, label=label, linewidth=2)
+     plt.legend(loc=2, framealpha=0.5)
+     plt.xlabel("energy [t]")
+     plt.ylabel(ylabel)
+-    plt.show()
++    save_figure(file_name)
+     plt.clf()
+ 
  def site_size_conversion(densities):
      return 3 * np.abs(densities) / max(densities)
  
  
  # Plot several local density of states maps in different subplots
- def plot_ldos(fsyst, axes, titles_to_data, file_name=None):
-     for ax, (title, ldos) in zip(axes, titles_to_data):
-         site_size = site_size_conversion(ldos)  # convert LDoS to sizes
-         kwant.plot(fsyst, site_size=site_size, site_color=(0, 0, 1, 0.3), ax=ax)
+ def plot_ldos(syst, densities, file_name=None):
+     fig, axes = plt.subplots(1, len(densities), figsize=(7*len(densities), 7))
+     for ax, (title, rho) in zip(axes, densities):
+         kwant.plotter.density(syst, rho.real, ax=ax)
          ax.set_title(title)
          ax.set(adjustable='box', aspect='equal')
 -    plt.show()
 +    save_figure(file_name)
      plt.clf()
  
- 
 +def save_figure(file_name):
 +    if not file_name:
 +        return
@@ -187,11 +240,9 @@
  #HIDDEN_BEGIN_op4
      zero_energy_ldos = local_dos(energy=0)
      finite_energy_ldos = local_dos(energy=1)
- 
-     _, axes = plt.subplots(1, 2, figsize=(12, 7))
-     plot_ldos(fsyst, axes,[
+     plot_ldos(fsyst, [
          ('energy = 0', zero_energy_ldos),
-         ('energy = 1', finite_energy_ldos),
+         ('energy = 1', finite_energy_ldos)
 -    ])
 +     ],
 +     file_name='kpm_ldos'
@@ -199,15 +250,43 @@
  #HIDDEN_END_op4
  
  
+ def ldos_sites_example():
+     fsyst = make_syst_staggered().finalized()
+ #HIDDEN_BEGIN_op5
+     # find 'A' and 'B' sites in the unit cell at the center of the disk
+     center_tag = np.array([0, 0])
+     where = lambda s: s.tag == center_tag
+     # make local vectors
+     vector_factory = kwant.kpm.LocalVectors(fsyst, where)
+ #HIDDEN_END_op5
+ 
+ #HIDDEN_BEGIN_op6
+     # 'num_vectors' can be unspecified when using 'LocalVectors'
+     local_dos = kwant.kpm.SpectralDensity(fsyst, num_vectors=None,
+                                           vector_factory=vector_factory,
+                                           mean=False)
+     energies, densities = local_dos()
+     plot_dos([
+         ('A sublattice', (energies, densities[:, 0])),
+         ('B sublattice', (energies, densities[:, 1])),
+-    ])
++     ],
++     file_name='kpm_ldos_sites'
++    )
+ #HIDDEN_END_op6
+ 
+ 
  def vector_factory_example(fsyst):
      spectrum = kwant.kpm.SpectralDensity(fsyst)
  #HIDDEN_BEGIN_fact1
-     # construct vectors with n random elements -1 or +1.
-     def binary_vectors(n):
-         return np.rint(np.random.random_sample(n)) * 2 - 1
+     # construct a generator of vectors with n random elements -1 or +1.
+     n = fsyst.hamiltonian_submatrix(sparse=True).shape[0]
+     def binary_vectors():
+         while True:
+            yield np.rint(np.random.random_sample(n)) * 2 - 1
  
      custom_factory = kwant.kpm.SpectralDensity(fsyst,
-                                                vector_factory=binary_vectors)
+                                                vector_factory=binary_vectors())
  #HIDDEN_END_fact1
      plot_dos([
          ('default vector factory', spectrum()),
@@ -232,6 +311,47 @@
          ('bilinear operator', rho_alt_spectrum()),
      ])
  
+ def conductivity_example():
+ #HIDDEN_BEGIN_cond
+     # construct the Haldane model
+     lat, fsyst = make_syst_topo()
+     # find 'A' and 'B' sites in the unit cell at the center of the disk
+     where = lambda s: np.linalg.norm(s.pos) < 3
+ 
+     # component 'xx'
+     s_factory = kwant.kpm.LocalVectors(fsyst, where)
+     cond_xx = kwant.kpm.conductivity(fsyst, alpha='x', beta='x', mean=True,
+                                      num_vectors=None, vector_factory=s_factory)
+     # component 'xy'
+     s_factory = kwant.kpm.LocalVectors(fsyst, where)
+     cond_xy = kwant.kpm.conductivity(fsyst, alpha='x', beta='y', mean=True,
+                                      num_vectors=None, vector_factory=s_factory)
+ 
+     energies = cond_xx.energies
+     cond_array_xx = np.array([cond_xx(e, temp=0.01) for e in energies])
+     cond_array_xy = np.array([cond_xy(e, temp=0.01) for e in energies])
+ 
+     # area of the unit cell per site
+     area_per_site = np.abs(np.cross(*lat.prim_vecs)) / len(lat.sublattices)
+     cond_array_xx /= area_per_site
+     cond_array_xy /= area_per_site
+ #HIDDEN_END_cond
+     # ldos
+     s_factory = kwant.kpm.LocalVectors(fsyst, where)
+     spectrum = kwant.kpm.SpectralDensity(fsyst, num_vectors=None,
+                                          vector_factory=s_factory)
+ 
+     plot_dos_and_curves(
+     (spectrum.energies, spectrum.densities * 8),
+     [
+         (r'Longitudinal conductivity $\sigma_{xx} / 4$',
+          (energies, cond_array_xx / 4)),
+         (r'Hall conductivity $\sigma_{xy}$',
+          (energies, cond_array_xy))],
+         ylabel=r'$\sigma [e^2/h]$',
+         file_name='kpm_cond'
+     )
+ 
  
  def main():
      simple_dos_example()
@@ -242,8 +362,10 @@
      increasing_accuracy_example(fsyst)
      operator_example(fsyst)
      ldos_example(fsyst)
+     ldos_sites_example()
      vector_factory_example(fsyst)
      bilinear_map_operator_example(fsyst)
+     conductivity_example()
  
  
  # Call the main function if the script gets executed (as opposed to imported).
diff --git a/doc/source/tutorial/kpm.rst b/doc/source/tutorial/kpm.rst
index ec9b2724..b8e7bd23 100644
--- a/doc/source/tutorial/kpm.rst
+++ b/doc/source/tutorial/kpm.rst
@@ -13,19 +13,49 @@ eigenenergies and eigenstates, but more in the *density of states*.
 The kernel polynomial method (KPM), is an algorithm to obtain a polynomial
 expansion of the density of states. It can also be used to calculate the
 spectral density of arbitrary operators.  Kwant has an implementation of the
-KPM method that is based on the algorithms presented in Ref. [1]_.
-
-Roughly speaking, KPM approximates the density of states (or any other spectral
-density) by expanding the action of the Hamiltonian (and operator of interest)
-on a (small) set of *random vectors* as a sum of Chebyshev polynomials up to
-some order, and then averaging. The accuracy of the method can be tuned by
-modifying the order of the Chebyshev expansion and the number of random
-vectors.  See notes on accuracy_ below for details.
+KPM method `kwant.kpm`, that is based on the algorithms presented in Ref. [1]_.
 
 .. seealso::
     The complete source code of this example can be found in
     :download:`kernel_polynomial_method.py </code/download/kernel_polynomial_method.py>`
 
+
+Introduction
+************
+
+Our aim is to use the kernel polynomial method to obtain the spectral density
+:math:`ρ_A(E)`, as a function of the energy :math:`E`, of some Hilbert space
+operator :math:`A`.  We define
+
+.. math::
+
+    ρ_A(E) = ρ(E) A(E),
+
+where :math:`A(E)` is the expectation value of :math:`A` for all the
+eigenstates of the Hamiltonian with energy :math:`E`,  and the density of
+states is
+
+.. math::
+
+  ρ(E) = \sum_{k=0}^{D-1} δ(E-E_k) = \mathrm{Tr}\left(\delta(E-H)\right),
+
+where :math:`H` is the Hamiltonian of the system, :math:`D` the
+Hilbert space dimension, and :math:`E_k` the eigenvalues.
+
+In the special case when :math:`A` is the identity, then :math:`ρ_A(E)` is
+simply :math:`ρ(E)`, the density of states.
+
+
+Calculating the density of states
+*********************************
+
+Roughly speaking, KPM approximates the density of states, or any other spectral
+density, by expanding the action of the Hamiltonian and operator of interest
+on a small set of *random vectors* (or *local vectors* for local density of
+states), as a sum of Chebyshev polynomials up to some order, and then averaging.
+The accuracy of the method can be tuned by modifying the order of the Chebyshev
+expansion and the number of vectors. See notes on accuracy_ below for details.
+
 .. _accuracy:
 .. specialnote:: Performance and accuracy
 
@@ -48,37 +78,28 @@ vectors.  See notes on accuracy_ below for details.
     number of random vectors is in general enough, and increasing this number
     will not result in a visible improvement of the approximation.
 
-
-Introduction
-************
-
-Our aim is to use the kernel polynomial method to obtain the spectral density
-:math:`ρ_A(E)`, as a function of the energy :math:`E`, of some Hilbert space
-operator :math:`A`.  We define
+The global *spectral density* :math:`ρ_A(E)` is approximated by the stochastic
+trace, the average expectation value of random vectors :math:`r`
 
 .. math::
 
-    ρ_A(E) = ρ(E) A(E),
+    ρ_A(E) = \mathrm{Tr}\left(A\delta(E-H)\right) \sim \frac{1}{R}
+    \sum_r \langle r \lvert A \delta(E-H) \rvert r \rangle,
 
-where :math:`A(E)` is the expectation value of :math:`A` for all the
-eigenstates of the Hamiltonian with energy :math:`E`,  and the density of
-states is
+while the *local spectral density* for a site :math:`i` is
 
 .. math::
 
-  ρ(E) = \sum_{k=0}^{D-1} δ(E-E_k),
+    ρ^i_A(E) = \langle i \lvert A \delta(E-H) \rvert i \rangle,
 
-:math:`D` being the Hilbert space dimension, and :math:`E_k` the eigenvalues.
+which is an exact expression.
 
-In the special case when :math:`A` is the identity, then :math:`ρ_A(E)` is
-simply :math:`ρ(E)`, the density of states.
 
-
-Calculating the density of states
-*********************************
+Global spectral densities using random vectors
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 In the following example, we will use the KPM implementation in Kwant
-to obtain the density of states of a graphene disk.
+to obtain the (global) density of states of a graphene disk.
 
 We start by importing kwant and defining our system.
 
@@ -138,7 +159,7 @@ respect to a Fermi-Dirac distribution:
 .. specialnote:: Stability and performance: spectral bounds
 
     The KPM method internally rescales the spectrum of the Hamiltonian to the
-    interval ``(-1, 1)`` (see Ref [1]_ for details), which requires calculating
+    interval ``(-1, 1)`` (see Ref. [1]_ for details), which requires calculating
     the boundaries of the spectrum (using ``scipy.sparse.linalg.eigsh``). This
     can be very costly for large systems, so it is possible to pass this
     explicitly as via the ``bounds`` parameter when instantiating the
@@ -151,6 +172,49 @@ respect to a Fermi-Dirac distribution:
     ``scipy.sparse.linalg.eigsh`` is set to ``epsilon/2``.
 
 
+Local spectral densities using local vectors
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The *local density of states* can be obtained without
+using random vectors, and using local vectors instead. This approach is best
+when we want to estimate the local density on a small number of sites of
+the system. The accuracy of this approach depends only on the number of
+moments, but the computational cost increases linearly with the number of
+sites sampled.
+
+To output local densities for each local vector, and not the average,
+we set the parameter ``mean=False``, and the local vectors will be created
+with the `~kwant.kpm.LocalVectors` generator (see advanced_topics_ for details).
+
+The spectral density can be restricted to the expectation value inside
+a region of the system by passing a ``where`` function or list of sites
+to the `~kwant.kpm.RandomVectors` or `~kwant.kpm.LocalVectors` generators.
+
+In the following example, we compute the local density of states at the center
+of the graphene disk, and we add a staggering potential between the two
+sublattices.
+
+.. literalinclude:: /code/include/kernel_polynomial_method.py
+    :start-after: #HIDDEN_BEGIN_sys3
+    :end-before: #HIDDEN_END_sys3
+
+Next, we choose one site of each sublattice "A" and "B",
+
+.. literalinclude:: /code/include/kernel_polynomial_method.py
+    :start-after: #HIDDEN_BEGIN_op5
+    :end-before: #HIDDEN_END_op5
+
+and plot their respective local density of states.
+
+.. literalinclude:: /code/include/kernel_polynomial_method.py
+    :start-after: #HIDDEN_BEGIN_op6
+    :end-before: #HIDDEN_END_op6
+
+.. image:: /code/figure/kpm_ldos_sites.*
+
+Note that there is no noise comming from the random vectors.
+
+
 Increasing the accuracy of the approximation
 ********************************************
 
@@ -169,10 +233,9 @@ This will update the number of calculated moments and also the default
 number of sampling points such that the maximum distance between successive
 energy points is ``energy_resolution`` (see notes on accuracy_).
 
-.. image:: /code/figure/kpm_dos_acc.*
-
 Alternatively, you can directly increase the number of moments
 with ``add_moments``, or the number of random vectors with ``add_vectors``.
+The added vectors will be generated with the ``vector_factory``.
 
 .. literalinclude:: /code/include/kernel_polynomial_method.py
     :start-after: #HIDDEN_BEGIN_acc2
@@ -183,6 +246,7 @@ with ``add_moments``, or the number of random vectors with ``add_vectors``.
 
 .. _operator_spectral_density:
 
+
 Calculating the spectral density of an operator
 ***********************************************
 
@@ -211,6 +275,10 @@ Or, to do the same calculation using `kwant.operator.Density`:
     :start-after: #HIDDEN_BEGIN_op2
     :end-before: #HIDDEN_END_op2
 
+
+Spectral density with random vectors
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
 Using operators from `kwant.operator` allows us to calculate quantities
 such as the *local* density of states by telling the operator not to
 sum over all the sites of the system:
@@ -220,8 +288,11 @@ sum over all the sites of the system:
     :end-before: #HIDDEN_END_op3
 
 `~kwant.kpm.SpectralDensity` will properly handle this vector output,
-which allows us to plot the local density of states at different
-point in the spectrum:
+and will average the local density obtained with random vectors.
+
+The accuracy of this approximation depends on the number of random vectors used.
+This allows us to plot an approximate local density of states at different
+points in the spectrum:
 
 .. literalinclude:: /code/include/kernel_polynomial_method.py
     :start-after: #HIDDEN_BEGIN_op4
@@ -229,18 +300,69 @@ point in the spectrum:
 
 .. image:: /code/figure/kpm_ldos.*
 
-This nicely illustrates the edge states of the graphene dot at zero
-energy, and the bulk states at higher energy.
+
+Calculating Kubo conductivity
+*****************************
+
+The Kubo conductivity can be calculated for a closed system with two
+KPM expansions. In `~kwant.kpm.Conductivity` we implemented the
+Kubo-Bastin formula of the conductivity and any temperature (see Ref. [2]_).
+With the help of `~kwant.kpm.Conductivity`,
+we can calculate any element of the conductivity tensor :math:`σ_{αβ}`,
+that relates the applied electric field to the expected current.
+
+.. math::
+
+    j_α = σ_{α, β} E_β
+
+In the following example, we will calculate the longitudinal
+conductivity :math:`σ_{xx}` and the Hall conductivity
+:math:`σ_{xy}`, for the Haldane model. This model is the first
+and one of the most simple ones for a topological insulator.
+
+The Haldane model consist of a honeycomb lattice, similar to graphene,
+with nearest neigbours hoppings. To turn it into a topological
+insulator we add imaginary second nearest neigbours hoppings, where
+the sign changes for each sublattice.
+
+.. literalinclude:: /code/include/kernel_polynomial_method.py
+    :start-after: #HIDDEN_BEGIN_sys2
+    :end-before: #HIDDEN_END_sys2
+
+To calculate the bulk conductivity, we will select sites in the unit cell
+in the middle of the sample, and create a vector factory that outputs local
+vectors
+
+.. literalinclude:: /code/include/kernel_polynomial_method.py
+    :start-after: #HIDDEN_BEGIN_cond
+    :end-before: #HIDDEN_END_cond
+
+Note that the Kubo conductivity must be normalized with the area covered
+by the vectors. In this case, each local vector represents a site, and
+covers an area of half a unit cell, while the sum covers one unit cell.
+It is possible to use random vectors to get an average spectation
+value of the conductivity over large parts of the system. In this
+case, the area that normalizes the result, is the area covered by
+the random vectors.
+
+.. image:: /code/figure/kpm_cond.*
+
+
+.. _advanced_topics:
 
 
 Advanced topics
 ***************
 
-Custom distributions for random vectors
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Custom distributions of vectors
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 By default `~kwant.kpm.SpectralDensity` will use random vectors
 whose components are unit complex numbers with phases drawn
-from a uniform distribution. There are several reasons why you may
+from a uniform distribution. The generator is accesible through
+`~kwant.kpm.RandomVectors`.
+
+For large systems, one will generally resort to random vectors to sample the
+Hilbert space of the system. There are several reasons why you may
 wish to make a different choice of distribution for your random vectors,
 for example to enforce certain symmetries or to only use real-valued vectors.
 
@@ -252,6 +374,16 @@ and which returns a vector in that Hilbert space:
     :start-after: #HIDDEN_BEGIN_fact1
     :end-before: #HIDDEN_END_fact1
 
+Aditionally, a `~kwant.kpm.LocalVectors` generator is also available, that
+returns local vectors that correspond to the sites passed. Note that
+the vectors generated match the sites in order, and will be exhausted
+after all vectors are drawn from the factory.
+
+Both `~kwant.kpm.RandomVectors` and `~kwant.kpm.LocalVectors` take the
+argument ``where``, that restricts the non zero values of the vectors
+generated to the sites in ``where``.
+
+
 Reproducible calculations
 ^^^^^^^^^^^^^^^^^^^^^^^^^
 Because KPM internally uses random vectors, running the same calculation
@@ -286,3 +418,5 @@ __ operator_spectral_density_
 
 .. [1] `Rev. Mod. Phys., Vol. 78, No. 1 (2006)
     <https://arxiv.org/abs/cond-mat/0504627>`_.
+.. [2] `Phys. Rev. Lett. 114, 116602 (2015)
+    <https://arxiv.org/abs/1410.8140>`_.
-- 
GitLab