Hi Sergey,

For method 2, I'm assuming you do a shift-invert around each energy.
If you need the LDOS for every site, then this is a good method.
If you want just a few elements, you can calculate the Green's function as
a continued fraction from the Lanczos tridiagonalization.
https://www.cond-mat.de/events/correl11/manuscript/Koch.pdf
This is an iterative method like KPM but I find that this method converges
faster sometimes.
If you're not happy with the energy resolution of KPM, you may have the
same issue here.
I sometimes need many thousands of iterations.

Here is my implementation.
I don't have any leads in my system.

def lanczos(H, i, N):
    """Calculate Lanczos tridiagonalization for the starting vector |i>.

    Algorithm from
    https://www.cond-mat.de/events/correl11/manuscript/Koch.pdf

    Parameters
    ----------
    H : Sparse array
        Hamiltonian.
    i : int
        Starting element.
    N : int
        Number of iterations.

    Returns
    -------
    a, b : array(N)
        Tridiagonal elements.
    """
    b = np.zeros(N)
    a = np.zeros(N)

    v = np.zeros(H.shape[0], H.dtype)
    v[i] = 1

    Hv = H.dot(v)
    a[0] = Hv[i].real             # a_0 = v.H.v
    w = Hv - a[0]*v               # w = H |v_0> - a_0 |v_0>

    for n in range(1, N):
        b[n] = np.linalg.norm(w)
        w /= b[n]                 # w = |v_n>
        v *= -b[n]                # v = -b_n |v_{n-1}>
        v, w = w, v               # swap(v, w)

        w += H.dot(v)             # w = H |v_n> - b_n |v_{n-1}>
        a[n] = np.vdot(v, w).real # a_n = <v_n|H|v_n> - b_n <v_n|v_{n-1}>
        w -= a[n]*v               # w = b_{n+1} |v_{n+1}>

    return a, b


def greens_function(z, a, b):
    """Calculate the Green's function G = (z-H)^{-1} from Lanczos
tridiagonalization.

    Algorithm from
    https://www.cond-mat.de/events/correl11/manuscript/Koch.pdf

    Parameters
    ----------
    z : array(nz)
        Energies to evaluate.
        For retarded Green's function, z = ω+iδ.
    a, b : array(N)
        Tridiagonal elements.

    Returns
    -------
    G : array(nz)
        Green's function.
    """
    G = z - a[-1]
    for n in range(len(a)-2, -1, -1):
        G = z - a[n] - b[n+1]**2/G
    return 1 / G

a, b = lanczos(H, 0, 500)
omega = np.linspace(-4, 4, 401)
G = greens_function(omega+0.1j, a, b)
ldos = -G.imag/np.pi

Thanks,
Eric


On Thu, Aug 20, 2020 at 10:48 AM Sergey Slizovskiy <ser...@gmail.com> wrote:

> Dear Colleagues,   (sorry, this is a repost of the same question to a new
> thread)
>
>   I have a taks to compute local density of states at a single point for a
> large homogeneous sample with narrow STS tip potential and in magnetic
> field.  I have tried several methods in Kwant to do this:
>
> 1)  Naive diagonalization of H for system with no leads and direct
> calculation of LDOS.  It works, but not for large systems, so, I see strong
> finite-size effects.
>
> 2)  Sparse Lanczos method with several eigenvalues/functions extracted
> near each point in the grid of energy values.
>
> 3)  KPM method.  I am not happy with the energy resolution I can get with
> it.
>
> 4)  Take a smaller main sample (with the size of the tip potential),  but
> avoid the finite-size effects by attaching a lot of leads pointing in ,
> e.g. 6 directions.   Then, use the kwant ldos  function to scan LDOS  over
> a grid of energies.  Use magnetic_gauge to implement B field in both sample
> and scattering region.
>
>   I see the last possibility as the quickest,  but it gives me something
> that seems wrong to me.
>
> Does kwant ldos  function work correctly if all the leads are insulating
> for a given energy?  Does it correctly catch the localized states in the
> scattering region?
>
> Thank you for any feedback,
>
> Sergey
>
>
> Below is a copy of a reply by Pablo Piskunow
>
> Dear Sergey,
>
> In terms of scaling, the most convenient is the KPM method, since it scales 
> linearly with the
> inverse energy resolution (that is, the number of moments), and linearly with 
> the system size.
>
> Could you clarify what is the issue with the approach 3)?
>
> I am not sure what is the energy resolution that you want to achieve. You can 
> conveniently set
> this value when creating a `kwant.kpm.SpectralDensity` instance via the 
> arguments
> `energy_resolution` or (exclusive) `num_moments`. The latter is related to 
> the energy resoution
> by `\delta \pi / N`, where `\delta` is the full width of the spectrum, and 
> `N` the size of your system.
> In the case of a 2D, that will be similar to `L^2`, where `L` is the linear 
> size.
>
> Furthermore, you could progressively increase the energy resolution by adding 
> moments:
> ```
> spectrum = kwant.kpm.SpectralDensity(...)
> spectrum.add_moments(...)
> ```
>
> If you want to resolve individual energy levels then the approach 1) and 3) 
> will have the same scaling,
> however, the KPM method will not give you eigenvalues.
>
> Finally, since you want to get the LDOS at a single site or a small set of 
> sites near the STS tip, you should
> use the `kwant.kpm.LocalVectors` vector factory, setting the `where` argument 
> to the desired spot.
>
> ```
> def center(site):
>     return site.tag == (0, 0)
> vector_factory = kwant.kpm.LocalVectors(syst, where=center)
> spectrum = kwant.kpm.SpectralDensity(syst, num_moments=100, num_vectors=None, 
> vector_factory=vector_factory)
> ```
> Note that `num_vectors=None` will let the `kwant.kpm` module to figure out 
> how many vectors does the vector factory produce.
> Otherwise, it should be at most the **total number of orbitals** defined by 
> the `where` function, that is, sites times orbitals per site.
>
> As you can see, the `kwant.kpm` module is the most suited for this problem, 
> specially when the sample is very large.
> I hope this helps.
>
> Best regards,
> Pablo
>
>
>
> n the meantime, you can create a `spectrum` instance, and evaluate the KPM
> expansion at any energy,
> for example by
>
> ```
> spectrum = kwant.kpm.SpectralDensity(...)
> energy_array = np.linspace(0, 1, 200)
> density_array = spectrum(energy_array)
> ```
>
>
> Another note, if the Hamiltonian is huge, you will save some overhead by
> passing the bounds of the spectrum explicitly:
> `bounds=(e_min, e_max)`. These values must be strictly below and above the
> edges of the spectrum, otherwise
> the KPM expansion diverges.
>
>
> ah, I forgot.
>
> The automatic energy points (`spectrum.energies`) are fixed once you fix
> the bounds of the spectrum.
> So if for a range of magnetic fields you pass the same bounds, then the
> energies will be at the same
> points and so the densities will be easier to plot or compare.
>
> The width of the spectrum might change for different values of the
> magnetic field; and even if not, there is
> small randomness associated with finding the bounds, that you can get rid
> of by passing a seed to the
> random number generator like `rng=0`. If you choose to provide the bounds
> explicitly, you can disregard
> this random number generator stuff.
>
>
>
>

Reply via email to