#!/usr/bin/env python # coding: utf-8 # # # # # Solving the Schrödinger equation numerically by expansion in eigenstates # # ## Examples – Quantum Mechanics #
# By Jonas Tjemsland, Andreas Krogen og Jon Andreas Støvneng. #
# Last edited: January 20th 2019 # # ___ # # ## Introduction # # In this notebook we will be solving the one-dimensional Schrödinger equation, # # $$i\hbar\frac{\partial\Psi(x, t)}{\partial t} = -\frac{\hbar^2}{2m}\frac{\partial^2 \Psi( x, t)}{\partial x^2}+V(x)\Psi( x, t) $$ # # numerically for an arbitrary initial condition $\Psi(x, 0)$. The eigenstates $\psi_n(x)$ and the eigenenergies $E_n$ of the system are found by solving the time-independent Schrödinger equation # # $$-\frac{\hbar^2}{2m}\frac{\partial^2 \psi_n(x)}{\partial x^2}+V(x)\psi_n(x) = E_n\psi_n(x),$$ # # and normalizing the result. The inital condition $\Psi(x, 0)$ is expanded in terms of $\psi_n(x)$: # # $$\Psi(x,0) = \sum_{i}\alpha_i\psi_i(x).$$ # # # In turn, the solution at time $t$, $\Psi(x, t)$, is given by # # $$\Psi(x, t) = \sum_n\alpha_n\psi_n(x)\exp\left(-i\frac{E_n}{\hbar}t\right).$$ # # As an example, we will be propagating an electron given by a gaussian wave packet towards a potential barrier. A similar example is studied in our notebook on [One-Dimensional Wave Packet Propagation](https://nbviewer.jupyter.org/urls/www.numfys.net/media/notebooks/one_dimensional_wave_propagation.ipynb), but with a quite different approach. # # The numerical scheme that is used is developed and explained in detail in the appendix at the end of this notebook. The reader is adviced to read through this before reviewing the notebook. # # We start by importing packages, setting common figure parameters and defining physical parameters. # In[2]: import matplotlib.pyplot as plt from scipy.linalg.lapack import ssbevd import numpy as np from matplotlib import animation newparams = {'axes.labelsize': 25, 'axes.linewidth': 1, 'savefig.dpi': 200, 'lines.linewidth': 3, 'figure.figsize': (20, 10), 'ytick.labelsize': 25, 'xtick.labelsize': 25, 'ytick.major.pad': 5, 'xtick.major.pad': 5, 'figure.titlesize': 25, 'legend.fontsize': 25, 'legend.frameon': True, 'legend.handlelength': 1.5, 'axes.titlesize': 25, 'mathtext.fontset': 'stix', 'font.family': 'STIXGeneral'} plt.rcParams.update(newparams) hbar = 1.05E-34 # J⋅s. Reduced Plank's constant m = 9.11E-31 # kg. Electron mass # ## Potential # As mentioned in the introduction, we will be propagating an electron towards a potential barrier in one dimension. We will be considering a domain $x\in[0,L]$. Let us use $\Delta x = 1\text Å$, which is a typical diameter of an atom. In turn, the width of the barrier is being decided by the number of discretization points it consists of. We want each side of the potential barrier to be large, so that the electron is not influenced by the barrier or the edges at $t=0$. We choose $N=10$ discretization points for the barrier, and 50 times that for each of the sides. The barrier has a height $V_0=1.5\cdot 1.6\cdot 10^{-19}J = 1.5\text{eV}$. # # Play around with other parameters and potential barriers. The code in this notebook works even for arbitrary potentials! # In[3]: V0 = 1.5*1.6E-19 # J. Potential height dx = 1e-10 # m. Discretization step N = 10 # #. Number of discretization points in the barrier N_sides = 100*N # #. Number of discretization points on each side of the barrier Ntot = N + 2*N_sides # Total number of discretization points x = np.linspace(0, dx*Ntot, Ntot) # x-axis # Potential V = np.array([0]*N_sides + [V0]*N + [0]*N_sides) # ## Wave packet # We will be representing the initial electron as a gaussian wavepacket, # $$\Psi(x,0)=C\exp\left(-\frac{(x-x_0)^2}{4\sigma^2}+i\frac{p_0x}{\hbar}\right),$$ # where $p_0=\sqrt{2mE_0}$ is the momentum of the wave packet, $E_0$ the energy of the electron, $x_0$ is the initial expectation value, and $\sigma$ is some parameter specifying the width of the wave packet. # # It will not be unreasonable to choose $E_0\sim V_0$. As we will see, this will give a good visualization of transmission and reflection. We start by choosing an energy a bit higher than the potential height, $E_0=1.39V_0$. $x_0$ to be in the middle of the left part of the domain and $\sigma$ (one standard deviation) to 1/8 of the left part. Play around with different parameters! # In[4]: x0 = 0.5*dx*N_sides E0 = 1.390*V0 k0 = np.sqrt(2.0*m*E0)/hbar sigma = dx*N_sides/8. A = (2*np.pi*sigma**2)**(-0.25) Psi_0 = A * np.exp(-(x-x0)**2/(4*sigma**2)) * np.exp(1j*k0*x) # Check if the wave function is normalized print("Normalization:", dx*np.sum(np.abs(Psi_0)**2)) # We now visualize the initial wave packet and the potential (with a suitable scaling). # In[5]: plt.plot(x, .75*V*np.max(np.abs(Psi_0)**2)/max(1e-30,np.max(V)), '--') plt.plot(x, np.abs(Psi_0)**2) plt.title('Initial probability distribution and potential') plt.xlabel('$x$ [m]') plt.ylabel('$|\Psi(x,0)|^2$') plt.show() # ## Solving the eigenvalue problem (Schrödinger equation) # Now that all the parameters are settled, we can finally solve the Schrödinger equation. This is done by solving an eigenvalue problem. We are using a real symmetric band matrix solver (You could of course also use [numpy.linalg.eigh](https://docs.scipy.org/doc/numpy/reference/generated/numpy.linalg.eigh.html), but this requires the initialization of the whole matrix, mostly consisting of zeros). We thus need to initialize the diagonal and the sub- and superdiagonal. This is explained in detail in the appendices. # # Note that this is the most computationally demanding part of these computations. # In[6]: diag = hbar**2/(m*dx**2) + V # Diagonal sup_diag = np.ones(Ntot)*(-hbar**2/(2*m*dx**2)) # Superdiagonal # In[7]: E, psi_n, _ = ssbevd([sup_diag, diag]) # Call solver # Let us visualize some of the eigenstates and eigenenergies! # In[8]: for i in [0, 1, 3]: plt.plot(x, psi_n[:,i], label=r"$\psi_{%.0f}(x)$"%(i)) plt.plot(x, .75*V*np.max(psi_n[1])/max(1e-30,np.max(V)), '--', label="Potential") plt.title("Eigenmodes for the given potential") plt.xlabel("$x$ [m]") plt.ylabel("$\psi_n(x)$") plt.xlim([0,dx*Ntot]) plt.legend() plt.show() # In[12]: plt.plot(E/1.6E-19) plt.title('Energies') plt.xlabel('$n$') plt.ylabel('Energy (eV)') plt.show() # Here is a quick question for the reader: why is $\psi_0(x)$ and $\psi_1{x}$ almost equal in the right part of the domain? Would we expect the same result for other pairs $(\psi_n, \psi_{n+1})$? Hint: nearly degenerate. # ## Finding the expansion coefficients # We now calculate the expansion coefficents as explained in the introduction and in the appendices. # In[13]: psi_n = psi_n.astype(complex) c = np.zeros(Ntot, dtype=complex) for n in range(Ntot): c[n] = np.vdot(psi_n[:,n], Psi_0) # ## Computing $\Psi(x,t)$ # Now, everything is set to compute the wave function at some arbitrary time $t$ given the inital condition and potential. To do this, we create a function performing the calculation as explained in the introduction and in the appendices. # In[14]: def Psi(t, c, psi_n, E): """ Calculate the wave function at some time t given the expansion coefficients c, eigenstates psi_n and eigenenergies E. Parameters: ----------- t : float. Time c : 1d array-like float, len Ntot. Expansion coefficient psi_n : 1d array-like float, len Ntot. Eigenstates E : 1d array-like float, len Ntot. Eigenenergies Returns: -------- Numpy-array float, len Ntot. Wave function at time t """ return np.dot(psi_n,c*np.exp(-1j*E*t/hbar)) # ## Finding a suitable time step - Ehrenfest's theorem # # To find a suitable time step $\Delta t$, we will be using [Ehrenfest's theorem](https://en.wikipedia.org/wiki/Ehrenfest_theorem). That is, the quantum mechanical expectation values obeys the classical equations of motion. For zero potential, (the expectation value of) the particle will thus have a velocity # $$v = \frac{p(x)}{m} = \sqrt{\frac{2E_0}{m}}.$$ # We will thus use $\Delta t \sim \sqrt{m/2E_0}\Delta x$. # # Let us plot the result for some $t$'s! # In[15]: dt = 250*dx*(m/(2*E0))**.5 nt = 5 for t in np.arange(0, nt*dt, dt): plt.plot(x, np.abs(Psi(t, c, psi_n, E))**2, label=r"$t=%.1e$ s"%(t)) plt.title("Wave function for different $t$") plt.xlabel("$x$ [m]") plt.ylabel("$|\Psi(x, t)|^2$") plt.legend() plt.show() # ## Tunneling, reflection and transmission # There are many things one can learn from this simple exercise. For example, note that we have used an energy that is higher than the potential barrier, $E_0>V_0$. In classical mechanics we would expect total transmission, but from the plot above, we see that there is a probability for reflection! On the other hand, if $E_0