#!/usr/bin/env python # coding: utf-8 # # # # # Eigenenergies of the Double-Well Potential # ## Examples - Quantum Mechanics #
# By Henning G. Hugdal, Magnus H-S Dahle and Peter Berg #
# Las edited: January 19th 2019 # ___ # # Potentials with two minima are often called double-well potentials. They have a wide range of applications in physics in general, including quantum mechanics. Two examples are: # # - Hydrogen bonding: e.g. a proton (hydrogen ion) between the oxygen atoms of two water molecules will experience a double-well potential. Hence, it will tend to be closer to one of the two atoms. # # - Inversion of ammonia: In ammonia, $NH_3$, the nitrogen atom is located above the plane made up by the three hydrogen atoms. The nitrogen atom can push through the plane of hydrogen, so that you now have an oppositely directed, but otherwise identical molecule. This could correspond to a specific double-well potential as shown in the figure above. You can read more about the inversion of ammonia in [the Feynman Lectures on Physics](http://www.feynmanlectures.caltech.edu/III_08.html#Ch8-S6 "The Feynman Lectures on Physics, Vol III") online. # # # # The aim of this example is to calculate the eigenenergies of the lowest states for a one-dimensional double-well potential of the form # # $$ # \qquad V(x) = \frac{V_\mathrm{max}}{b^4}(x^2-b^2)^2, # $$ # # where $V_\mathrm{max}$ and $b$ are constants. This can be done with the method of forward shooting. We will use the same iterative formula as obtained in [this example](https://nbviewer.jupyter.org/urls/www.numfys.net/media/notebooks/ex_qm1_numerical_determination_of_eigenvalues_for_harmonic_oscillator.ipynb), which calculates the wave function at a point $x_{i+1}$ based on its value at the two previous points, according to # # $$ # \qquad \psi_{i+1} = -\psi_{i-1} + 2\left[1 - (\Delta x)^2 \left(E-V(x_i)\right)\right] \psi_i, # $$ # # where we have set $\hbar=m=1$ for simplicity. The first iteration has to be treated separately; using initial values $\psi_0$ and $\psi_0'$, $\psi_1$ can be calculated to lowest order from # # $$ # \qquad \psi_1 = \psi_0 + \Delta x \cdot \psi_0'. # $$ # # This then allows us to calculate $\psi_i$ for higher values of $i$. Since the potential is symmetric about $x=0$, we know that the wave functions must be either symmetric or anti-symmetric, with the $n$th excited state having parity $(-1)^n$ and $n$ nodes. This gives all the information we need in order to calculate the eigenenergies. # # We will find the eigenenergies of the ground- and first excited state for two different values of $V_\mathrm{max}$ so as to see how the potential barrier affects the system. We choose the value $b=0.2$ throughout the whole example. # # First, we import the modules needed. Then we define some functions which will be used to find the eigenenergies and to plot the wave functions: # # - Results: a tuple used to return the results from the forward shooting. # - forwardShoot(): function which is used to calculate the eigenenergy and wave function for a given potential. # - normPlot(): normalizes and plots the wave function. # # This spares us a lot of code writing since the same procedure is repeated for the ground- and first excited state for two different values of $V_\mathrm{max}$. Note that the function forwardShoot() only works for the ground- and first excited state as it is defined now. (In fact, it works for all states where the wave function $\psi_n$ approaches 0 from above for large values of $x$, i.e. $\lim_{x \to \infty} \psi_n(x) \rightarrow 0^{+}$. This is the case for $\psi_n$ with $n \in \{0,1,4,5,8,9,\dots\}$.) To work for the second and third excited states, the conditions used for the bisection method would have to be changed. # In[1]: get_ipython().run_line_magic('matplotlib', 'inline') import numpy as np import matplotlib.pyplot as plt from collections import namedtuple # Set common figure parameters: newparams = {'axes.labelsize': 14, 'axes.linewidth': 1, 'savefig.dpi': 300, 'lines.linewidth': 1.0, 'figure.figsize': (8, 3), 'figure.subplot.wspace': 0.4, 'figure.dpi': 200, 'ytick.labelsize': 10, 'xtick.labelsize': 10, 'ytick.major.pad': 5, 'xtick.major.pad': 5, 'legend.fontsize': 10, 'legend.frameon': False, 'legend.handlelength': 1.5} plt.rcParams.update(newparams) # In[2]: Results = namedtuple('res', ['func','E']) def forwardShoot(V, dx, psi_0, dpsi_0, E_l=0.0, E_u=400.0, acc=1e-10): """Takes a potential and a vector of zeroes and uses the forward shooting method to calculate the eigenenergy using the upper and lower bounds given. Arguments: V Potential dx Step length x-axis psi_0 Initial value of wavefunction at x=0 dpsi_0 Initial value of first derivative of wavefunction at x=0 E_l Lower bound of energy E_u Upper bound of energy acc Accuracy when determining the energy Returns tuple with elements func Wavefunction E Eigenenergy """ f = np.zeros(len(V)) f[0] = psi_0 df_0 = dpsi_0 E1 = E_l # Lower bound for eigenenergy E2 = E_u # Upper bound for eigenenergy E = E1 DeltaE = 1 while DeltaE > acc: for i, V_ in enumerate(V[0:-1]): if i==0: f[i+1]=f[i]+dx*df_0 else: f[i+1] = -f[i-1]+2*f[i]*(1-dx**2*(E-V_)) # Implementation of bisection method. If the function value is out of bounds, # a new value for the energy is chosen. When the difference between the upper # and lower bound for the energy is smaller than the given accuracy, # the while loop stops, yielding a result for E. if f[i] > 20: E1 = E E = E1 + (E2-E1)/2 break elif f[i] <- 20: E2 = E E = E2 - (E2-E1)/2 break DeltaE = E2-E1 res = Results(f, E) return res def normPlot(V, func, E, x, x_end, title='', sym=True, legend_loc=1): """Normalizes and plots the wavefunction together with the potential and eigenenergy. Arguments: V Potential func Wavefunction E Eigenenergy x Vector with x-values x_end Biggest x-value to be included in normalization/plot title Plot title sym True if symmetric wavefunction, false if anti-symmetric legend_loc Placement of plot legend Returns: F Normalized wavefunction. """ dx = x[1]-x[0] sign = -1 if sym: sign = 1 c = sum(2*func[0:int(x_end/dx)]**2*dx) F = func/np.sqrt(c) plt.figure() p1, = plt.plot(x, V/V[0], 'b') p2, = plt.plot(x, F, 'g') p3, = plt.plot([-x_end,x_end], [E/V[0], E/V[0]], 'r--') plt.legend([p1,p2,p3], [r'Potential $V(x)/V_\mathrm{max}$',r'Wave function $\psi(x)$', r'Eigenenergy $E/V_\mathrm{max}$'], loc=legend_loc) plt.plot(-x, V/V[0], 'b', -x, sign*F, 'g', [-x_end,x_end], [0,0], 'k:') plt.ylim([min(sign*F[0:int(x_end/dx)])-0.1, max(F[0:int(x_end/dx)])+0.5]) plt.xlim([-x_end, x_end]) plt.xlabel(r'$x$') plt.title(title) return F # ### Potential barrier $V_\mathrm{max} = 40$ # # #### Ground State # # With $n=0$, the ground state has even parity and is hence symmetric about the origin. This means that the initial condition for $\psi'$ is $\psi_0' = 0 $, while we can choose $\psi_0$ freely, e.g. $\psi_0 = 1$, since this is only a matter of normalization. The energy is calculated, using the bisection method, with lower bound $E=0$, and upper bound $E=40$, since it is reasonable to assume that there exists a state with energy $E0$ and if we plot the absolute square of these two functions, we obtain almost the exact same function, as seen from the plot below. We also have $E_0/E_1 \approx 1$. # In[12]: plt.figure() pp1, = plt.plot(x, G0**2, 'b') p2, = plt.plot(x, G1**2, 'r') plt.legend([p1,p2], ['Ground State','First Excited State']) plt.plot(-x, G0**2, 'b', -x, G1**2, 'r', [-0.5,0.5], [0,0], 'g:') plt.ylabel(r'$\psi(x)$') plt.xlabel(r'$x$') plt.ylim([-0.1, 5.5]) plt.xlim([-0.5, 0.5]) plt.title(r'Absolute Square of Wavefunctions for $V_\mathrm{max}=400$'); # We also see that with the higher potential barrier, the probability of the particle being in the classically forbidden region, is dramatically reduced for the ground state, as it should be. # Also note that since the energies and the absolute squares of the two wave functions are so similar when $V_\mathrm{max} = 400$, it is possible to construct wave functions such that the particle is almost certainly either in the left or the right well, given by $\psi_0 - \psi_1$ and $\psi_0 + \psi_1$ respectively, as seen in the following plot. # In[13]: plt.figure() p1, = plt.plot(x, 0.5*(G0-G1)**2,'b') p2, = plt.plot(x, 0.5*(G0+G1)**2,'r') plt.legend([p1,p2], [r'$|\psi_0 - \psi_1|^2/2$',r'$|\psi_0 + \psi_1|^2/2$']) plt.plot(-x, 0.5*(G0+G1)**2,'b', -x, 0.5*(G0-G1)**2, 'r', [-0.5,0.5], [0,0], 'g:') plt.ylabel(r'$\psi(x)$') plt.xlabel(r'$x$') plt.ylim([-0.1, 10.0]) plt.xlim([-0.5, 0.5]); # In other words, as the potential barrier increases, the situation resembles more and more the situation of a particle being confined in one of two separate wells. # # ___ # # One might wonder: how large is the energy of the second excited state for $V_\mathrm{max}=400$? Is it close to $E_0$ and $E_1$? To find out, we need to make a small change to the function forwardShoot(): # In[14]: def forwardShoot2(V, dx, psi_0, dpsi_0, E_l=0.0, E_u=400.0, acc=1e-10): """Takes a potential and a vector of zeroes and uses the forward shooting method to calculate the eigenenergy using the upper and lower bounds given. Arguments: V Potential dx Step length x-axis psi_0 Initial value of wavefunction at x=0 dpsi_0 Initial value of first derivative of wavefunction at x=0 E_l Lower bound of energy E_u Upper bound of energy acc Accuracy when determining the energy Returns tuple with elements func Wavefunction E Eigenenergy """ f = np.zeros(len(V)) f[0] = psi_0 df_0 = dpsi_0 E1 = E_l # Lower bound for eigenenergy E2 = E_u # Upper bound for eigenenergy E = E1 DeltaE = 1 while DeltaE > acc: for i, V_ in enumerate(V[0:-1]): if i==0: f[i+1]=f[i]+dx*df_0 else: f[i+1] = -f[i-1]+2*f[i]*(1-dx**2*(E-V_)) # Implementation of bisection method. If the function value is out of bounds, # a new value for the energy is chosen. When the difference between the upper # and lower bound for the energy is smaller than the given accuracy, # the while loop stops, yielding a result for E. if f[i] > 20: # Changes here: E2 = E E = E2 - (E2-E1)/2 break elif f[i] <- 20: # Changes here: E1 = E E = E1 + (E2-E1)/2 break DeltaE = E2-E1 res = Results(f, E) return res # We know that the second excited state should be symmetric about the origin. Hence, we can use the same initial conditions as for the ground state. We also use the energy of the first excited state as a lower bound. # In[15]: g_0 = 0.1 dg_0 = 0 res5 = forwardShoot2(V=V, dx=dx, psi_0=g_0, dpsi_0=dg_0, E_l=E2_1, E_u=400) E2_2 = res5.E print(r'The energy is: %s' % E2_2) # The energy is $E_2=349.08$, a big increase compared to $E_1$! # In[16]: G2 = normPlot(V, res5.func, E2_2, x, 0.5, title=r'Second excited state, $V_\mathrm{max}=400$', sym=True, legend_loc=8) # We now see that the wave function has changed dramatically from that of the two lower states. Near the origin, it is now more similar to that of the ground state for $V_\mathrm{max}=40$. This seems reasonable considering that the energy is now comparable to the potential barrier, which was also the case for the ground state with lower potential barrier. A particle is no longer confined to the two wells since there is now a considerable probability of the particle being between the wells, meaning in the classically forbidden area.