DEMO: Solving via Variational Method#
Populating the and Matrices#
To determine the variationally optimal energy and wave function, we need to determine the variationally optimal linear coefficients
Posit the trial wave function (done)
Populate the
and matricesDiagonalize
.
Here will will populate the
The Overlap Matrix #
We start with
where
From normalization conditions, we know that
We now set about determining
This integral is somewhat involved but can be solved. Rather than doing so here I just provide the solution
We note the
Show code cell source
import numpy as np
def S12(R):
return np.exp(-R)*(1+R+R**2/3)
import matplotlib.pyplot as plt
%matplotlib inline
R = np.arange(0,8,0.01)
plt.figure(figsize=(8,5),dpi= 80, facecolor='w', edgecolor='k')
plt.tick_params(axis='both',labelsize=20)
plt.grid( which='major', axis='both', color='#808080', linestyle='--')
plt.plot(R,S12(R),lw=3)
plt.xlabel(r'R/$a_0$',fontsize=20)
plt.ylabel(r'$S_{12}(R)$',fontsize=20);
From this information we can populate the
Below is a code snippet to populate this matrix.
Show code cell source
import numpy as np
def S(R):
return np.array([[1,S12(R)],[S12(R),1]])
print("S_12(2.5) = ", S12(2.5))
print("S(2.5) = ")
print(S(2.5))
S_12(2.5) = 0.45830790898343504
S(2.5) =
[[1. 0.45830791]
[0.45830791 1. ]]
The Hamiltonian Matrix :#
The Hamiltonia matrix
where
Each of these will need to be determined but we will be able to determine
where we recognize that the first integral is simply the 1s energy of a hydrogen atom and we define the second integral as
where that last integral becomes
For the
Thus, we get that
For
where we recognize that
where the last step I have used the solution for the simple case of H
We can now populate the
For a nuclear separation value of 2.5 Bohr, for example, this yields
Below is the code to compute this.
Show code cell source
import numpy as np
def H(R):
J = np.exp(-2*R)*(1+1/R)- 1/R
K = -np.exp(-R)*(1+R)
H12 = H21 = -0.5*S12(R) + K + S12(R)/R
H11 = H22 = -0.5 + J + 1/R
return np.array([[H11,H12],[H21,H22]])
R = 2.5
print("H(1.5) = ")
print(H(R))
H(1.5) =
[[-0.49056687 -0.33312829]
[-0.33312829 -0.49056687]]
Combined Solution#
Now that we have equations for
Let’s choose 2.5 Bohr, for example, in which case we have
Computing the eigenvalues of
The ground state energy is the first (minimum) of these. The corresponding eigenvectors (or vectors of variational coefficients) are
where these are listed as column vectors meaning the that coefficients for the ground state energy are
Show code cell source
print("S^{-1}H(2.5) = ", np.dot(np.linalg.inv(S(2.5)),H(2.5)))
e, v = np.linalg.eig(np.dot(np.linalg.inv(S(2.5)),H(2.5)))
print("Eigenvalues:", e)
print("Eigenvectors:", v)
S^{-1}H(2.5) = [[-0.4277358 -0.13709359]
[-0.13709359 -0.4277358 ]]
Eigenvalues: [-0.56482939 -0.29064221]
Eigenvectors: [[-0.70710678 0.70710678]
[-0.70710678 -0.70710678]]
Due to the symmetry of this problem and normalization requirements, it turns out that the coefficients must always be
That is the must always be the same magnitude (symmetry) and their values are dictated by normalization (which depends on the overlap of the two basis functions).
The minimum energy state is the linear combination in which the coefficients both have the same sign. The higher energy (antibonding orbital) has the coefficients with opposing sign. These can be described as
Solution as a Function of #
In order to investigate things such as the predicted bond length and bonding energy of this model of the H
Below I make a plot of energies as a function of
Show code cell source
import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np
R = np.arange(1,8,0.001)
plt.figure(figsize=(8,6),dpi= 80, facecolor='w', edgecolor='k')
plt.tick_params(axis='both',labelsize=20)
plt.grid( which='major', axis='both', color='#808080', linestyle='--')
E = np.empty(R.size)
E_ab = np.empty(R.size)
for i, r in enumerate(R):
e, v = np.linalg.eig(np.dot(np.linalg.inv(S(r)),H(r)))
E[i] = np.amin(e)
E_ab[i] = np.amax(e)
plt.plot(R,E,lw=4, label=r'E$_+$')
plt.plot(R,E_ab,lw=2, label=r'E$_-$')
plt.xlabel(r'R/$a_0$',fontsize=20)
plt.ylabel(r'$E_0 (Hartree)$',fontsize=20)
plt.ylim(-0.6,-0.2)
plt.legend(fontsize=20)
plt.title(r'LCAO Variational Solution to the H$_2^+$ Molecule',fontsize=16);
Show code cell source
min_index = np.argmin(E)
print("Bonding energy:", np.round(E[min_index]+0.5,5), "Hartree or ", np.round(627.5895*(E[min_index]+0.5),5), "kcal/mol")
print("Bonding distance:", np.round(R[min_index],5), "Bohr")
Bonding energy: -0.06483 Hartree or -40.68725 kcal/mol
Bonding distance: 2.493 Bohr
We see from the plot and code output above that the predicted bond distance for this model of H
These are not actually in great agreement with the “exact” values of 1.999 Bohr and -0.1 Hartree. But the simple approach outline here does give qualitative agreement and is a useful model for generalizing to other molecules.
We could get better agreement in a number of ways. These include:
Increasing the number of basis functions
Improving the basis functions (either by modifying them or including additional variational parameters)
Orbital/Electron Densities#
Show code cell source
# make two plots of the same spherical harmonic
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm, colors
import numpy as np
import matplotlib.pyplot as plt
from scipy.special import sph_harm
from scipy.special import eval_genlaguerre
from scipy.special import lpmv
from scipy.special import factorial
%matplotlib inline
from scipy.optimize import root
a0 = 1.0 # radial unit of Bohr!
def hydrogen_atom_wf(r,theta,phi,n,l,m):
Y_norm = np.sqrt((2*l+1)*factorial(l-np.abs(m))/(4*np.pi*factorial(l+np.abs(m))))
R_prefactor = -np.sqrt(factorial(n-l-1)/(2*n*factorial(n+l)))*(2.0/(n*a0))**(l+1.5)*np.power(r,l)*np.exp(-r/(n*a0))
R = R_prefactor*eval_genlaguerre(n-l-1,2*l+1,2*r/(n*a0))
return Y_norm*sph_harm(m, l, phi, theta).real*R
def h2plus_prob(r_nuc,x,y,z):
r1 = np.sqrt(x*x + y*y + z*z)
phi1 = np.arctan2(y,x)
theta1 = np.arccos(z/r1)
r2 = np.sqrt((x-r_nuc)**2 + y*y + z*z)
phi2 = np.arctan2(y,x-r_nuc)
theta2 = np.arccos(z/r2)
#
s12 = np.exp(-r_nuc)*(1+r_nuc+r_nuc**2/3)
#
h2plus_wf = np.sqrt(1/(2*(1-s12)))*(hydrogen_atom_wf(r1,theta1,phi1,1,0,0) + hydrogen_atom_wf(r2,theta2,phi2,1,0,0))
return h2plus_wf**2
def h2plus_anti_prob(r_nuc,x,y,z):
r1 = np.sqrt(x*x + y*y + z*z)
phi1 = np.arctan2(y,x)
theta1 = np.arccos(z/r1)
r2 = np.sqrt((x-r_nuc)**2 + y*y + z*z)
phi2 = np.arctan2(y,x-r_nuc)
theta2 = np.arccos(z/r2)
#
s12 = np.exp(-r_nuc)*(1+r_nuc+r_nuc**2/3)
#
h2plus_wf = np.sqrt(1/(2*(1-s12)))*(hydrogen_atom_wf(r1,theta1,phi1,1,0,0) - hydrogen_atom_wf(r2,theta2,phi2,1,0,0))
return h2plus_wf**2
def plot_h2plus_prob(r_nuc, ax_obj, r=np.linspace(0,2,20), theta=np.linspace(0,np.pi,20), phi=np.linspace(0,2*np.pi,25)):
R, THETA, PHI = np.meshgrid(r, theta, phi)
R = R.flatten()
THETA = THETA.flatten()
PHI = PHI.flatten()
x = R*np.sin(THETA)*np.cos(PHI)
y = R*np.sin(THETA)*np.sin(PHI)
z = R*np.cos(THETA)
x = np.concatenate((x,x+r_nuc))
y = np.concatenate((y,y))
z = np.concatenate((z,z))
wf = h2plus_prob(r_nuc,x,y,z)
vmax = max(np.abs(np.amin(wf)),np.abs(np.amax(wf)))
vmin = -vmax
# plot
ax_obj.set_title(rf'$R={r_nuc}$', fontsize=18)
ax_obj.scatter3D(x,y,z,c=wf, cmap='RdBu', vmin=vmin, vmax=vmax,alpha=0.25)
#ax_obj.set_box_aspect((100,100,100))
ax_obj.set_axis_off()
ax_obj.axes.xaxis.set_ticklabels([])
ax_obj.axes.yaxis.set_ticklabels([])
ax_obj.axes.zaxis.set_ticklabels([])
def plot_h2plus_prob_xz_projection(r_nuc, ax_obj):
x = np.linspace(-2.5,8,1200)
z = np.linspace(-2,2,600)
X, Z= np.meshgrid(x, z)
Y = np.zeros(X.shape)
x = X.flatten()
y = Y.flatten()
z = Z.flatten()
wf = h2plus_prob(r_nuc,x,y,z)
wf = wf.reshape(X.shape)
vmax = max(np.abs(np.amin(wf)),np.abs(np.amax(wf)))
vmin = -vmax
# plot
ax_obj.set_title(rf'$R={r_nuc}$ Bonding', fontsize=18)
#c = ax_obj.pcolormesh(X, Z, wf, cmap='RdBu', vmin=vmin, vmax=vmax)
c = ax_obj.pcolormesh(X, Z, wf, cmap='hot_r',vmin=0,vmax=vmax)
# set the limits of the plot to the limits of the data
ax_obj.axis([-2.5, 8, -2, 2])
ax_obj.set_aspect('equal')
#ax_obj.set_axis_off()
return c
def plot_h2plus_anti_prob_xz_projection(r_nuc, ax_obj):
x = np.linspace(-2.5,8,1200)
z = np.linspace(-2,2,600)
X, Z= np.meshgrid(x, z)
Y = np.zeros(X.shape)
x = X.flatten()
y = Y.flatten()
z = Z.flatten()
wf = h2plus_anti_prob(r_nuc,x,y,z)
wf = wf.reshape(X.shape)
vmax = max(np.abs(np.amin(wf)),np.abs(np.amax(wf)))
vmin = -vmax
# plot
ax_obj.set_title(rf'$R={r_nuc}$ Antibonding', fontsize=18)
#c = ax_obj.pcolormesh(X, Z, wf, cmap='RdBu', vmin=vmin, vmax=vmax)
c = ax_obj.pcolormesh(X, Z, wf, cmap='hot_r',vmin=0,vmax=vmax)
# set the limits of the plot to the limits of the data
ax_obj.axis([-2.5, 8, -2, 2])
ax_obj.set_aspect('equal')
#ax_obj.set_axis_off()
return c
Show code cell source
fig, ax = plt.subplots(5,2,figsize=(8,12),dpi= 80, facecolor='w', edgecolor='k')
R = np.array([6.0,3.5,3.0,2.5,2.0])
for i, r in enumerate(R):
plot_h2plus_prob_xz_projection(r,ax[i,0])
plot_h2plus_anti_prob_xz_projection(r,ax[i,1])
plt.show();