numerical methods

# The Lanczos algorithm

Finding the eigenvalues and eigenvectors of large hermitian matrices is a key problem of (numerical) quantum mechanics. Often, however, the matrices of interest are much too large to employ exact methods. A popular and powerful approximation method is based on the Lanczos algorithm. The Lanczos algorithm determines an orthonormal basis of the Kyrlov sub-space $\mathcal{K}_k(\Psi, \hat H)$. The Kyrlov sub-space $\mathcal{K}_k(\Psi, \hat H)$ is the linear space that is spanned by the vectors $\Psi$, $\hat H\Psi$, ${\hat H}^2\Psi$, …, ${\hat H}^{k-1}\Psi$ with $k\le \dim\hat H$. Furthermore, the Lanczos algorithm constructs a real symmetric tridiagonal matrix $\hat H’$ with $\dim\hat H’=k$ that is an approximation of $\hat H$ in the sense that the eigenvalues of $\hat H’$ are close to some eigenvalues of $\hat H$. The eigenvectors of $\hat H$ can be approximated via the eigenvectors of $\hat H’$. Thus, the Lanczos algorithm reduces the problem of matrix diagonalization of large hermitian matrices to the diagonalization of a (usually) much smaller real symmetric tridiagonal matrix, which is a much simpler task.

The Lanczos algorithm has been applied to many problems of nonrelativistic quantum mechanics, in particular bound state calculations and time propagation. In a recent work (arXiv:1407.7370) we evaluated the Lanczos algorithm  for solving the time-independent as well as the time-dependent relativistic Dirac equation with arbitrary electromagnetic fields. We demonstrate that the Lanczos algorithm can yield very precise eigenenergies and allows very precise time propagation of relativistic wave packets. The Dirac Hamiltonian’s property of not being bounded does not hinder the applicability of the Lanczos algorithm. As the Lanczos algorithm requires only matrix-vector and inner products, which both can be efficiently parallelized, it is an ideal method for large-scale calculations. The excellent parallelization capabilities are demonstrated by a parallel implementation of the Dirac Lanczos propagator utilizing the Message Passing Interface standard.

The following python code shows how to solve the time-dependent free one-dimensional Dirac equation via the Lanczos algorithm. The Hamiltonian

\hat H = c \begin{pmatrix}
0 & 1 \\ 1 & 0
\end{pmatrix} \left(-\mathrm{i}\frac{\partial}{\partial x} \right) + \begin{pmatrix}
1 & 0 \\ 0 & -1
\end{pmatrix}m_0c^2
is approximated via second order finite differences. A detailed description of the Lanczos algorithm and its application to the Dirac equation is given in arXiv:1407.7370.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# import useful modules
import matplotlib
from math import factorial
from numpy import *
from pylab import *
from numpy.polynomial.hermite import *

# use LaTeX, choose some nice looking fonts and tweak some settings
matplotlib.rc('font', family='serif')
matplotlib.rc('font', size=16)
matplotlib.rc('legend', fontsize=16)
matplotlib.rc('legend', numpoints=1)
matplotlib.rc('legend', handlelength=1.5)
matplotlib.rc('legend', frameon=False)
matplotlib.rc('lines', lw=1.5)
matplotlib.rc('text', usetex=True)
matplotlib.rc('text.latex',
preamble=[r'\usepackage[T1]{fontenc}',
r'\usepackage{amsmath}',
r'\usepackage{txfonts}',
r'\usepackage{textcomp}'])

close('all')
figure(figsize=(6, 4.5))

c=137.0359998  # speed of light in a.u.
N=1024+512

# the 1d Dirac Hamiltonian
def H(Psi, x, t, dt):
Psi=reshape(Psi, (N, 2))
dx=x[1]-x[0]
Psi_new=empty_like(Psi)
Psi_new[1:-1, 0]=-1j*c*(Psi[2:, 1]-Psi[0:-2, 1])/(2*dx) + c**2*Psi[1:-1, 0]
Psi_new[1:-1, 1]=-1j*c*(Psi[2:, 0]-Psi[0:-2, 0])/(2*dx) - c**2*Psi[1:-1, 1]
Psi_new[0, 0]=Psi_new[0, 1]=0
Psi_new[-1, 0]=Psi_new[-1, 1]=0
Psi_new*=dt
return reshape(Psi_new, 2*N)

# the Lanczos algorithm
def Lanczos(Psi, x, t, dt, H, m):
Psi_=Psi.copy()
# run Lanczos algorithm to calculate basis of Krylov space
V_j, V_j_1=[], []
A=zeros((m, m))
norms=zeros(m)
for j in range(0, m):
norms[j]=norm(Psi_)
V_j=Psi_/norms[j]
Psi_=H(V_j, x, t, dt)
if j>0:
A[j-1, j]=A[j, j-1]=vdot(V_j_1, Psi_).real
Psi_-=A[j-1, j]*V_j_1
A[j, j]=vdot(V_j, Psi_).real
Psi_-=A[j, j]*V_j
V_j_1, V_j=V_j, V_j_1
# diagonalize A
l, v=eig(A)
# calculate matrix exponential in Krylov space
c=dot(v, dot(diag(exp(-1j*l)), v[0, :]*norms[0]))
# run Lanczos algorithm 2nd time to transform result into original space
Psi_, Psi=Psi, zeros_like(Psi)
for j in range(0, m):
V_j=Psi_/norms[j]
Psi+=c[j]*V_j
Psi_=H(V_j, x, t, dt)
if j>0:
A[j-1, j]=A[j, j-1]=vdot(V_j_1, Psi_).real
Psi_-=A[j-1, j]*V_j_1
A[j, j]=vdot(V_j, Psi_).real
Psi_-=A[j, j]*V_j
V_j_1, V_j=V_j, V_j_1
return Psi

# define computational grid
x0, x1=-0.5, 0.5
x=linspace(x0, x1, N)
dx=x[1]-x[0]       # size of spatial grid spacing
dt=4./c**2         # temporal step size

# constuct momentum grid
dp=2*pi/(N*dx)
p=(arange(0, N)-0.5*(N-1))*dp
# choose initial condition
p_mu=75.       # mean momentum
sigma_p=50.    # momentum width
x_mu=-0.05     # mean position
# upper and lower components of free particle states,
# see e.g. Thaller »Advanced visual quantum mechanics«
d_p=sqrt(0.5+0.5/sqrt(1+p**2/c**2))
d_m=sqrt(0.5-0.5/sqrt(1+p**2/c**2))
d_m[p<0]*=-1
# initial condition in momentum space,
# gaussian wave packet of positive energy states
rho=(2*pi*sigma_p**2)**(-0.25)*exp(-(p-p_mu)**2/(4*sigma_p**2) - 1j*p*x_mu)
Psi=zeros((N, 2), dtype='complex')
Psi[:, 0]=d_p*rho
Psi[:, 1]=d_m*rho
# transform into real space with correct complex phases
Psi[:, 0]*=exp(1j*x[0]*dp*arange(0, N))
Psi[:, 1]*=exp(1j*x[0]*dp*arange(0, N))
Psi=ifft(Psi, axis=0)
Psi[:, 0]*=dp*N/sqrt(2*pi)*exp(1j*x*p[0])
Psi[:, 1]*=dp*N/sqrt(2*pi)*exp(1j*x*p[0])

# propagate
for k in range(0, 20):
# plot wave function
clf()
plot(x, Psi[:, 0].real**2+Psi[:, 0].imag**2+
Psi[:, 1].real**2+Psi[:, 1].imag**2,
color='#266bbd', label=r'$|\Psi(x)|^2'$)
gca().set_xlim(x0, x1)
gca().set_ylim(-1, 16)
xlabel(r'\$x)
legend(loc='upper left')
tight_layout()
draw()
show()
pause(0.05)
Psi=reshape(Lanczos(reshape(Psi, 2*N), x, 0, dt, H, 128), (N, 2)