Dear Christoph, Thank you so much for your email and for your advice.
I have attached the full code for my carbon nanotube system (simplified slightly) which, when run, should output the animation I have also attached. The system is evolved for 5 time steps over a short period of time (0 to 0.5e-8s). It seems that when I increase the number of lattice points, the probability density function becomes 'spiky' over time and the resolution of the graph deteriorates. However, this might just be a problem with my code. Thank you again for any help with this. Best wishes, Isobel ________________________________ From: Christoph Groth Sent: Thursday, December 01, 2022 15:34 To: Clarke, Isobel Cc: [email protected]; Buitelaar, Mark Subject: Re: [Kwant] Resolution of Tkwant Clarke, Isobel wrote: > I was wondering if there is a limit on the resolution of Kwant and > Tkwant when calculating the density of an evolved wavefunction. Hi, I’m not aware of any particular issue where a Tkwant simulation “explodes” after some time. Obviously, the numerical calculations have a limited precision. It would be most helpful if you could provide a complete example that demonstrates the problem that you experience. It doesn’t have to be the same physical system that you use in research. It’s actually better if it’s simpler, but one should be able to observe the concrete problems that trouble you. > In addition, I was wondering if you had any advice for making the code > run faster? I ran the simulation for 10 time steps over a very short > time period to obtain the attached animation and it took around > 4 hours on my desktop. As a vague rule of thumb, one needs to step up to a a computing cluster for tkwant when a laptop/desktop is sufficient with kwant alone. (This assumes that one wants to treat similar systems with comparable numbers of orbitals.) Or one has to wait longer... Tkwant calculations need to be integrated over many energies, while with Kwant at zero temperature that’s not necessary. So there’s literally more to do. There are bottlenecks and problems that could be improved in both Kwant and Tkwant, and there may be also possible workarounds. It can help to profile a run to see where time is actually spent. Having a small but representative running example (that ideally does not take four hours!) would help here as well. Cheers Christoph
Animation - 20 lattice points.mp4
Description: Animation - 20 lattice points.mp4
import kwant.continuum
import numpy as np
import matplotlib.pyplot as plt
import kwant
from numpy import vectorize
from matplotlib import animation
from tkwant import onebody
import tkwant
import os.path
import json
import time as timelib
from matplotlib import animation
class System:
def __init__(self, hamiltonian, pertubation_type, number_of_lattices,
potential_type):
self.potential_type = potential_type
self.hamiltonian = hamiltonian
self.number_of_lattices = number_of_lattices
self.pertubation_type = pertubation_type
self.evolve_state = False
self.a_0_SI = 5.2917721090380e-11
self.total_length_SI = 0.66e-6
self.omega_0_eV = 1e-3 # in eV
self.B_0_au = 2.127659574468085e-08
self.b_sl_au = 2.946499088192983e-12
self.eV_0_au = 0.0003674930360069677
self.pulse_frequency_au = self.ev_to_hartree(self.omega_0_eV)
self.total_length_au = self.m_to_au(self.total_length_SI)
self.lattice_size_au = self.total_length_au / self.number_of_lattices
self.mu_B_au = .5
self.z_au = np.arange(-self.total_length_au / 2, self.total_length_au /
2, self.lattice_size_au,
dtype=np.double)
def ev_to_hartree(self, ev):
return ev / 2.72114e1
def hartree_to_ev(self, hartree):
return hartree * 2.72114e1
def m_to_au(self, m):
return m / self.a_0_SI
def second_to_au(self, time):
return time * 4.1341373336493e16
def au_to_second(self, time):
return time / 4.1341373336493e16
def cosine_v_ac(self, time, z, eV_0_au, pulse_frequency_au,
total_length_au):
return ((eV_0_au * np.cos(2 * np.pi * pulse_frequency_au * time)) * z)
/ total_length_au
def sine_v_ac(self, time, z, eV_0_au, pulse_frequency_au, total_length_au):
return ((eV_0_au * np.sin(2 * np.pi * pulse_frequency_au * time)) * z)
/ total_length_au
def potential(self, z, time):
if self.potential_type == 0: #infinite square well
total_potential = 0
elif self.potential_type == 1: #parabolic potential
total_potential = .5 * (z * self.omega_0_au) ** 2
if self.pertubation_type == "cos":
self.pertubation = self.cosine_v_ac
else:
self.pertubation = self.sine_v_ac
total_potential += self.pertubation(time, z, self.eV_0_au,
self.pulse_frequency_au, self.total_length_au)
return total_potential
def kwant_shape(self, site):
(z,) = site.pos
return (-self.total_length_au / 2 <= z < self.total_length_au / 2)
def make_system(self):
self.template = kwant.continuum.discretize(self.hamiltonian,
grid=self.lattice_size_au)
self.syst = kwant.Builder()
# add the nanotube to the system
self.syst.fill(self.template, self.kwant_shape, (0,))
self.syst = self.syst.finalized()
self.B_y_au = 0
def B_constant(z):
return -self.B_0_au
def C_constant(z):
return -self.b_sl_au * z
def D_constant(z):
return -self.B_y_au
# coefficient for the kinetic energy term
A_constant = 1
# parameters of hamiltonian
self.params = dict(A=A_constant, V=self.potential, B=B_constant,
C=C_constant, D=D_constant)
self.tparams = self.params.copy() # copy the params array
self.tparams['time'] = 0 # initial time = 0
# compute the Hamiltonian matrix
hamiltonian = self.syst.hamiltonian_submatrix(params=self.tparams)
# compute the eigenvalues (energies) and eigenvectors (wavefunctions).
eigenValues, eigenVectors = np.linalg.eig(hamiltonian)
# sort the eigenvectors and eigenvalues according the ascending
eigenvalues.
idx = eigenValues.argsort()
self.initial_eigenvalues = eigenValues[idx]
eigenVectors = eigenVectors[:, idx]
# initial wave functions of unperturbed hamiltonian
self.psi_1_init = eigenVectors[:, 0]
self.psi_2_init = eigenVectors[:, 1]
# tkwant object representing the spin-up state
self.spin_up_state =
tkwant.onebody.WaveFunction.from_kwant(syst=self.syst,
psi_init=self.psi_1_init,
energy=eigenValues[0],
params=self.params)
return self.syst
def evolve(self, time_steps, lattices):
self.evolve_state = True
# extract lowest two energies (our qubit states)
E_1 = np.real(self.initial_eigenvalues[0])
E_2 = np.real(self.initial_eigenvalues[1])
# compute the difference in these energies
self.delta_E = np.abs(E_2 - E_1)
# compute the resonant frequency for the rabi oscillations
self.pulse_frequency_au = self.delta_E / (2 * np.pi)
total_osc_time = self.second_to_au(0.5e-8)
times_au = np.linspace(0, total_osc_time, num=time_steps)
density_operator = kwant.operator.Density(self.syst)
psi = self.spin_up_state
if self.pertubation_type == "cos":
y3_func = self.cosine_v_ac
else:
y3_func = self.sine_v_ac
# empty array for density and perturbation function
density = np.zeros((time_steps, lattices))
perturb = np.zeros((time_steps, lattices))
array = np.arange(0, time_steps, 1)
for i in array:
# evolve the state with time
psi.evolve(times_au[i])
density[i] = psi.evaluate(density_operator)
perturb[i] = self.hartree_to_ev(y3_func(times_au[i], self.z_au,
self.eV_0_au, self.pulse_frequency_au, self.total_length_au))
self.evolve_state = False
return density, perturb
def animations(self, density, y3, nsteps):
# create a figure with two subplots
fig_animation, axs = plt.subplots(2,1)
# 1920 x 1080
w_in_inches = 10
h_in_inches = 6
dpi = 100
fig_animation.set_size_inches(w_in_inches, h_in_inches, True)
fig_animation.set_dpi(dpi)
fig_animation.tight_layout(pad=5.0)
# set variables for each of the axes
ax1 = axs[0]
ax2 = axs[1]
# initialise two line objects (one in each axes)
line1, = ax1.plot([], [], lw=2)
line2, = ax2.plot([], [], lw=2, color='r', label='$H_1(t)$')
line = [line1, line2]
z_max_au = self.m_to_au(self.total_length_SI) / 2
z_max_SI = self.total_length_SI / 2
y2_max = self.hartree_to_ev((self.eV_0_au * z_max_au /
self.total_length_au))
y1 = density[0]
y1_max = np.max(y1) * 2
lattice_size_SI = self.total_length_SI / self.number_of_lattices
z_SI = np.arange(-self.total_length_SI / 2, self.total_length_SI / 2,
lattice_size_SI, dtype=np.double)
# PDF
ax1.set_xlim(-z_max_SI, z_max_SI)
ax1.set_ylim(0, np.max(density[-1,:]*1.2))
ax1.set_ylabel("$|\psi(z,t)|^2$")
ax1.set_xlabel("z ($m$)")
ax1.set_title("PDF over time")
# Potential
ax2.set_xlim(-z_max_SI, z_max_SI)
ax2.set_ylim(-y2_max, y2_max)
ax2.set_ylabel("$E$ (eV)")
ax2.set_xlabel("z ($m$)")
# initialization function: plot the background of each frame
def init():
line[0].set_data([], [])
line[1].set_data([], [])
return line
def animate(i):
# update line objects.
line[0].set_data(z_SI, density[i,:])
line[1].set_data(z_SI, y3[i,:])
ax2.legend(loc="upper right")
return line
# call the animator. blit=True means only re-draw the parts that have
changed.
anim = animation.FuncAnimation(fig_animation, animate, init_func=init,
frames=nsteps-1, interval=300, blit=True)
#folder_path = r"\Users\"
#anim.save('{}/animation.mp4'.format(folder_path), writer='ffmpeg')
plt.show()
return True
def main():
lattices = 20
potential = 0
system = System("((A * k_z**2) + V(z, time)) * identity(2) + B(z) * sigma_z
+ C(z) * sigma_x + D(z) * sigma_y",
pertubation_type="sin", number_of_lattices=lattices,
potential_type=potential)
system.make_system();
nsteps = 5
density, perturbation = system.evolve(nsteps,lattices)
system.animations(density, perturbation, nsteps)
if __name__ == '__main__':
main()
