Tip
Run this Jupyter Notebook locally: Download Notebook
Performance of evolution#
Cutoff in the coefficients#
The time evolution operator for a Hamiltonian \(H\)
uses a KPM expansion as a function of time \(\Delta t\), instead of the usual Fermi energy \(\varepsilon\) of spectral functions.
In the expansion
the coefficients \(c_m\) are proportional to the Bessel functions of the first kind of order \(m\), so they decay exponentially fast after a certain threshold \(m^*\).
Thus, is it extremely efficient to cutoff the expansion for a certain target accuracy
The exact value of \(m^*\) depends on the time step times the bounds of the spectrum \(\Delta t\times\Delta E\), and on the desired accuracy. To find \(m^*\) for all values of \(\Delta t\times\Delta E\), we define a few typical target accuracies: \(10^{-8}, 10^{-16}, 10^{-32}\), and numerically obtain the answer. Then, we will use a numerical estimate of the function that fits \(m^*\).
[1]:
import numpy as np
import holoviews as hv
hv.extension()
[2]:
import scipy
import scipy.optimize
from scipy.special import jv
[3]:
from kpm_tools.evolution import coef_evo
numerical evaluation of the coefficients#
First, we obtain an ndarray of the coefficients vs the time delta \(\Delta t\) and the number of moments num_moments.
Here, we use \(\Delta E = 1\), such that dt is the rescaled time delta \(\Delta t \times \Delta E\).
[4]:
dt_array = np.logspace(-3,2,1000)
num_moments = 500
[5]:
coefs_array = np.array([
coef_evo(num_moments, 0, ab=(2,0), delta_time=dt)
for dt in dt_array
])
coefs_array = coefs_array[:,1:,0]
[6]:
num_moments_array = np.arange(1, num_moments)
make a fit#
Numerical estimation of the best num_moments#
Then we obtain the best num_moments for a given dt and accuracy.
[7]:
accuracy = 1e-16
[8]:
# get the transition point in `m` such that coefs are smaller than accuracy
def max_m_array(coefs_array, accuracy):
return np.count_nonzero(np.abs(coefs_array) > accuracy, axis=1)
[9]:
(
hv.QuadMesh((
num_moments_array,
dt_array,
np.log(np.abs(coefs_array))/np.log(10)
),
kdims=['M', '$dt$'],
vdims=hv.Dimension('z', label='${log}_{10}|c_n|$', range=(np.log(accuracy)/np.log(10), 0))
).opts(colorbar=True, fig_size=200)
*
hv.Curve((
max_m_array(coefs_array, accuracy),
dt_array,
))
)[:30,:4]
/tmp/ipykernel_5521/785646435.py:5: RuntimeWarning: divide by zero encountered in log
np.log(np.abs(coefs_array))/np.log(10)
[9]:
We can already see that only a handful of moments is needed for a fairly large vale of the time step dt.
Analitical fit of the best num_moments#
Since we don’t want to store arrays of best num_moments, we make an analytical approximation to the best num_moments for a given accuray and as a function of dt.
[10]:
def func(dt, A, B, C, D):
return (A * np.log(1/accuracy) * (dt) ** B) + C * dt + D * np.log(1/accuracy)
p_acc16, _ = scipy.optimize.curve_fit(func,
dt_array,
max_m_array(coefs_array, accuracy),
p0=(0.5, 0.25, 2, 0),
# bounds=([0,0,0,2], [1,1,1,2+1e-8])
)
[11]:
hv.QuadMesh((
num_moments_array,
dt_array,
np.log(np.abs(coefs_array+np.finfo(float).eps))/np.log(10)
),
kdims=['M', '$dt$'],
vdims=hv.Dimension('z', label='$|c_n|$', range=(np.log(accuracy)/np.log(10), 0))
).opts(logy=True, logz=False, colorbar=True, fig_size=200) * \
hv.Curve((np.array([func(dt, *p_acc16) for dt in dt_array]),
dt_array)).opts(logy=True, logx=True)
[11]:
Result#
Finally, these parameters give the best choice of num_moments as a function of dt for typical accuracy goals: 1e-8, 1e-16, 1e-32.
[12]:
'p_acc8 = [0.42464678, 0.33500138, 2.00044861, 0.07265139]'
'p_acc16 = [0.34310197, 0.33722835, 2.01232381, 0.08868359]'
'p_acc32 = [0.28132266, 0.32958046, 2.04428473, 0.09146841]'
[12]:
'p_acc32 = [0.28132266, 0.32958046, 2.04428473, 0.09146841]'
From the scaling analysis we can see that large time steps are possible, and require a proportionally larger amount of moments in the KPM expansion.
Smaller timesteps have a slight overhead in the number of moments: \(m^*=4\) for very small time steps of \(10^{-3}\) but only \(m^*=6\) for a ten times larger time step \(10^{-2}\).
Thus, it is more convenient -in terms of performance- to make one big time step, rather than many subsequent smaller time steps.
Nevertheless, it is still fairly efficient to make many time steps for visualizing the time evolution.