#!/usr/bin/env python # coding: utf-8 # ## The Variational Method # # The variational method is a simple approach to finding the energy of the ground state of a system. We write a *trial* wavevector which depends on one or more parameters, and then vary the parameters to find the lowest energy. We will show in lectures that this always gives an *upper bound* to the energy of the system; we define the energy for the system in the trial state as: # # $$ # E(\lambda) = \frac{\langle \psi(\lambda) \vert \hat{H} \vert \psi(\lambda) \rangle}{\langle \psi(\lambda)\vert \psi(\lambda)\rangle} # $$ # # where $\lambda$ is the parameter. # # We will start with a simple example: finding the optimum value of the exponent, $\alpha$, for the ground state of the quantum harmonic oscillator. As we know the exact answer, we can check that the approach is correct. # In[1]: # Import libraries and set up in-line plotting. get_ipython().run_line_magic('matplotlib', 'inline') import matplotlib.pyplot as pl import numpy as np # Define the x-axis from -width to +width # This makes the QHO finite in width, which is an approximation # We will need to be careful not to make omega too small width = 10.0 num_x_points = 1001 x = np.linspace(-width,width,num_x_points) dx = 2.0*width/(num_x_points - 1) # Integrate two functions over the width of the well def integrate_functions(f1,f2,size_x,dx): """Integrate two functions over defined x range""" sum = 0.0 for i in range(size_x): sum = sum + f1[i]*f2[i] sum = sum*dx return sum # Define a function to act on a basis function with the potential def add_pot_on_basis(Hphi,V,phi): for i in range(V.size): Hphi[i] = Hphi[i] + V[i]*phi[i] # In[2]: # Define the potential in the square well def square_well_potential(x,V,a): """Potential for a particle in a square well, expecting two arrays: x, V(x), and potential height, a""" for i in range(x.size): V[i] = 0.5*a*x[i]*x[i] # If necessary, plot to ensure that we know what we're getting #pl.plot(x,V) # Now let's define the trial function to use, which will be a gaussian: # $$f(x) = A exp(-0.5\alpha^2 x^2)$$ # # We'll also need its second derivative for the energy, which we can find analytically in this case: # $$df/dx = -x\alpha^2 A exp(-0.5\alpha x^2)$$ # $$d2f/dx2 = -\alpha^2 A exp(-0.5\alpha x^2) + \alpha^4 x^2 A exp(-0.5\alpha x^2) = \alpha^2 A (\alpha^2 x^2 -1) exp(-0.5\alpha x^2)$$ # In[3]: from math import pi root_pi = np.sqrt(pi) # Define a gaussian with proper normalisation as our test function def gauss(x,alpha): return np.sqrt(alpha / (root_pi))* np.exp(-0.5 * alpha**2 * x**2) # We can also write the second derivative function easily enough def d2gauss(x,alpha): return np.sqrt(alpha / (root_pi))* alpha*alpha * (alpha*alpha*x*x - 1) * np.exp(-0.5 * alpha**2 * x**2) # Declare space for the potential and call the routine omega = 2.0 # Set the frequency VQHO = np.linspace(0.0,width,num_x_points) square_well_potential(x,VQHO,omega*omega) print "From theory, E min, alpha: ",0.5*omega,np.sqrt(omega) psi = gauss(x,np.sqrt(omega)) # Check that I've normalised the gaussian correctly print "Norm is: ",integrate_functions(psi,psi,num_x_points,dx) pl.plot(x,psi) # Now that we have the trial function, with the parameter $\alpha$ as our variational parameter, we can vary the energy and look for the minimum. We'll do this in a **very** crude way, just scanning values of $\alpha$. There are much better approaches, though, which we will address in a later notebook. # In[4]: psi = gauss(x,np.sqrt(omega)) # Kinetic energy Hpsi = -0.5*d2gauss(x,np.sqrt(omega)) # Add potential add_pot_on_basis(Hpsi,VQHO,psi) # Check the exact answer - we won't be able to do this normally ! print "Energy at optimum alpha is: ",integrate_functions(psi,Hpsi,num_x_points,dx) alpha_vals = np.linspace(0.1,10.1,1001) energy = np.zeros(1001) i=0 e_min = 1e30 alph_min = 0.0 for alpha in alpha_vals: psi = gauss(x,alpha) norm = integrate_functions(psi,psi,num_x_points,dx) #if np.abs(norm-1.0)>1e-6: # print "Norm error: ",alpha,norm Hpsi = -0.5*d2gauss(x,alpha) add_pot_on_basis(Hpsi,VQHO,psi) energy[i] = integrate_functions(psi,Hpsi,num_x_points,dx) if energy[i] integral = integrate_functions(eigenbasis_sw(n,width,1.0,x),eigenbasis_sw(n,width,1.0,x),num_x_points,dx) # Use 1/sqrt{A} as normalisation constant normalisation = 1.0/np.sqrt(integral) basis_array[i,:] = eigenbasis_sw(n,width,normalisation,x) d2basis_array[i,:] = d2eigenbasis_sw(n,width,normalisation,x) # Define the potential in the square well def square_well_linear_potential(x,V,a): """Potential for a particle in a square well, expecting two arrays: x, V(x), and potential height, a""" for i in range(x.size): V[i] = a*(x[i]-width/2.0) # Plot to ensure that we know what we're getting pl.plot(x,V) # Declare space for this potential (Vdiag) and call the routine Vdiag = np.linspace(0.0,width,num_x_points) square_well_linear_potential(x,Vdiag,1.0) # Now build the Hamiltonian - we could actually evaluate the expectation value by using vector-matrix-vector algebra, though we will use integration below for now. # In[6]: # Declare space for the matrix elements Hmat2 = np.eye(num_basis) # Loop over basis functions phi_n (the bra in the matrix element) print "Full Hamiltonian" for n in range(num_basis): # Loop over basis functions phi_m (the ket in the matrix element) for m in range(num_basis): # Act with H on phi_m and store in H_phi_m H_phi_m = -0.5*d2basis_array[m] add_pot_on_basis(H_phi_m,Vdiag,basis_array[m]) # Create matrix element by integrating Hmat2[m,n] = integrate_functions(basis_array[n],H_phi_m,num_x_points,dx) # The comma at the end prints without a new line; the %8.3f formats the number print "%8.3f" % Hmat2[m,n], # This print puts in a new line when we have finished looping over m print # Finally, we set up the crude parameter scan. I have chosen a range for $\alpha$ which is helpful, though this approach sometimes requires a degree of trial and error. # In[7]: n_alpha = 101 alpha_vals = np.linspace(-0.1,0.1,n_alpha) energy2 = np.zeros(n_alpha) i=0 e_min = 1e30 alph_min = 0.0 for alpha in alpha_vals: psi = basis_array[0] + alpha*basis_array[1] H_psi = -0.5*(d2basis_array[0] + alpha*d2basis_array[1]) add_pot_on_basis(H_psi,Vdiag,psi) norm = integrate_functions(psi,psi,num_x_points,dx) #print alpha, norm #print Hmat2[0,0] + Hmat2[1,0]*alpha + Hmat2[0,1]*alpha + Hmat2[1,1]*alpha*alpha energy2[i] = integrate_functions(psi,H_psi,num_x_points,dx)/norm if energy2[i]