Having learned the basic concept of DFT, we will continue to apply the DFT approach to simulate a more complicated system than a single electron system, i.e., the H2 molecule. H2 is the simplest molecule, consisting of two protons and two electrons. This small size is ideal for demonstrating the DFT method in a manageable way.
By solving for the ground state energy and electron density of H2, we hope to better understand the numerical aspects of the DFT method. In addition, we will analyze the results to understand the bonding in H2 and the electron distribution.
In an H2 molecule, we need to consider the following variables in the KS equation.
- Nuclei: two protons, located at positions
$R_1$ and$R_2$ . - Electrons: two electrons interacting with the protons and each other.
We first write down its Hamiltonian,
Where
-
$\hat{T}_e$ : Kinetic energy of the electrons. -
$\hat{V}_{\text{ext}}$ : External potential due to nuclei. -
$\hat{V}_{\text{ee}}$ : Electron-electron interaction. -
$\hat{V}_{\text{nn}}$ : Nucleus-nucleus interaction.
Hence, the Kohn-Sham formalism reduces the many-body problem to a series of single-particle equations. For the case of H2, there is only one Kohn-Sham equation solved because both electrons occupy the same Kohn-Sham orbital. We assume that both electrons have opposite spins and fill the same orbital, so the total electron density is computed as
In a hydrogen molecule, the external potential is given by the Coulomb interaction with the two protons:
To avoid the singularity in the Coulomb interaction for each electron with the protons, the external potential
where
The Hartree potential represents the classical electrostatic potential at a point
Direct evaluation of
where
This involves three steps:
- Fourier transform of electron density:
- Calculate the Hartree Potential in Fourier Space (avoid division by zero at
$\mathbf{k}$ = 0):
- Inverse Fourier Transform to Real Space:
Correspondingly, the Hartree Energy is
In a hydrogen molecule, the exchange and correlation effects account for the complex quantum interactions between electrons, beyond the simple electrostatic repulsion captured by the Hartree term. These effects are crucial in accurately describing the binding energy, molecular structure, and overall electronic distribution within the molecule.
The exchange-correlation (XC) energy,
In the Local Density Approximation (LDA), the exchange-correlation energy density at each point is assumed to depend only on the local electron density. For a given density
where
For the case of simplicity, we ignore the correlation term here.
Adding all potential terms together, the effective potential is
The total energy is the sum of potential and kinetic energies. The kinetic energy
where
Thus, the total energy is
where
Below gives an example Python code to compute the ground state of hydrogen molecule.
import numpy as np
from scipy.sparse import kron, eye
from scipy.sparse import diags, csr_matrix
from scipy.fft import fftn, ifftn
from scipy.sparse.linalg import eigsh # Sparse eigenvalue solver for faster performance
import matplotlib.pyplot as plt
# Define 3D grid parameters
N = 80 #120 # Grid size along each dimension
L = 6 #40 # Simulation box size in Bohr
dx = L / N
x = np.linspace(-L/2, L/2, N)
y = np.linspace(-L/2, L/2, N)
z = np.linspace(-L/2, L/2, N)
X, Y, Z = np.meshgrid(x, y, z, indexing="ij")
# Positions of the two protons in H2
R1 = np.array([-0.7, 0, 0])
R2 = np.array([0.7, 0, 0])
num_electrons = 2.0
E_nuc = 1.0 / (R2[0]-R1[0])
# Softened Coulomb potential for two protons
softening = 0.1 # To prevent singularity
V_ext = -1 / np.sqrt((X - R1[0])**2 + (Y - R1[1])**2 + (Z - R1[2])**2 + softening**2)
V_ext += -1 / np.sqrt((X - R2[0])**2 + (Y - R2[1])**2 + (Z - R2[2])**2 + softening**2)
# Initial guess for electron density
rho = np.exp(-1 * ((X - R1[0])**2 + (Y - R1[1])**2 + (Z - R1[2])**2))
rho += np.exp(-1 * ((X - R2[0])**2 + (Y - R2[1])**2 + (Z - R2[2])**2))
total_density = np.sum(rho) * dx**3
rho *= num_electrons / total_density
# Define FFT-based Poisson solver for Hartree potential
def compute_hartree_potential(rho):
kx = 2 * np.pi * np.fft.fftfreq(N, d=dx)
ky = 2 * np.pi * np.fft.fftfreq(N, d=dx)
kz = 2 * np.pi * np.fft.fftfreq(N, d=dx)
KX, KY, KZ = np.meshgrid(kx, ky, kz, indexing="ij")
k2 = KX**2 + KY**2 + KZ**2
k2[0, 0, 0] = 1 # Avoid division by zero for the zero-frequency term
rho_k = fftn(rho)
V_H_k = 4 * np.pi * rho_k / k2
V_H_k[0, 0, 0] = 0 # Set the zero-frequency term to zero for neutrality
V_H = np.real(ifftn(V_H_k))
return V_H #* 2
# Exchange-correlation potential (Local Density Approximation)
def compute_exchange_correlation_potential(rho):
c = (3 / np.pi)**(1 / 3)
return -c * rho**(1 / 3)
def kinetic_energy_operator(N, dx):
# Define 1D kinetic energy finite difference operator
main_diag = -2 * np.ones(N)
side_diag = np.ones(N - 1)
T_1D = diags([main_diag, side_diag, side_diag], [0, -1, 1], shape=(N, N)) / dx**2
# Build 3D kinetic energy operator using Kronecker products
I = eye(N, format='csr') # Identity matrix for each dimension
T = kron(kron(T_1D, I), I) + kron(kron(I, T_1D), I) + kron(kron(I, I), T_1D)
return -0.5 * T # Scale by -0.5
# SCF loop parameters
tolerance = 1e-6
max_iterations = 100
damping_factor = 0.5
# Kinetic energy operator (sparse)
T = kinetic_energy_operator(N, dx); print(T.shape)
for iteration in range(max_iterations):
# Compute Hartree and XC potentials
V_H = compute_hartree_potential(rho)
V_XC = compute_exchange_correlation_potential(rho)
V_eff = V_ext + V_H + V_XC
# Construct H
V_eff_flat = V_eff.flatten()
H = T + diags(V_eff_flat, 0, shape=(N**3, N**3))
# Solve the Kohn-Sham equation (using sparse eigenvalue solver for the lowest eigenvalue)
energies, orbitals = eigsh(H, k=1, which='SA')
psi = orbitals[:, 0].reshape((N, N, N))
psi /= np.sqrt(np.sum(np.abs(psi)**2) * dx**3)
# Update electron density
rho_new = num_electrons * np.abs(psi)**2 # Two electrons in ground state
# Calculate energies for this iteration
T_s = 2* np.sum(orbitals[:, 0] * T.dot(orbitals[:, 0])) * dx**3
E_H = 0.5 * np.sum(rho * V_H) * dx**3
E_ext = np.sum(rho * V_ext) * dx**3; print(rho.sum()*dx**3, rho.shape)
E_XC = np.sum(rho * V_XC) * dx**3
# Print the energies
print(f"Iteration {iteration + 1}:")
print(f"Kinetic Energy (T) = {T_s:.6f} Hartree")
print(f"External Energy (E_ext)= {E_ext:.6f} Hartree")
print(f"Hartree Energy (E_H) = {E_H:.6f} Hartree")
print(f"Exchange-Correlation (E_XC) = {E_XC:.6f} Hartree")
print(f"Total Effective Energy = {T_s + E_ext + E_H + E_XC + E_nuc:.6f} Hartree")
print(f"Solver KS HOMO Energy = {energies[0]:.6f} Hartree\n")
# Damping update
rho = (1 - damping_factor) * rho + damping_factor * rho_new
total_density = np.sum(rho) * dx**3
total_electrons = np.sum(rho) * dx**3
print(f"Total electrons: {total_electrons}")
#XC_scaling_factor = min(XC_scaling_factor + 0.1, 1.0) # Gradually ramp up to 1.0
# Check convergence
if np.linalg.norm(rho_new - rho) < tolerance:
print(f"Converged after {iteration + 1} iterations")
break
else:
print("Did not converge within the maximum number of iterations")
The code iteratively solves the KS equations. In each iteration, the electron density is updated from the orbitals, and the effective potential is recalculated using the new density. This process continues until the electron density converges (i.e., when the difference between the new and old density is smaller than a set tolerance).
Here the function eigsh
solves the eigenvalue problem for the Hamiltonian, returning the Kohn-Sham energies and orbitals. Once the Hamiltonian is constructed and solved, the Kohn-Sham orbitals
An example output looks like the following
Iteration 1:
Kinetic Energy (T) = 1.263639 Hartree
External Energy (E_ext)= -3.557743 Hartree
Hartree Energy (E_H) = 0.545992 Hartree
Exchange-Correlation (E_XC) = -0.819774 Hartree
Total Effective Energy = -1.853602 Hartree
Solver HOMO Energy = -1.046968 Hartree
Iteration 2:
Kinetic Energy (T) = 1.256039 Hartree
External Energy (E_ext)= -3.599140 Hartree
Hartree Energy (E_H) = 0.545473 Hartree
Exchange-Correlation (E_XC) = -0.812328 Hartree
Total Effective Energy = -1.895671 Hartree
Solver HOMO Energy = -1.046764 Hartree
Iteration 3:
Kinetic Energy (T) = 1.253817 Hartree
External Energy (E_ext)= -3.614615 Hartree
Hartree Energy (E_H) = 0.543885 Hartree
Exchange-Correlation (E_XC) = -0.809045 Hartree
Total Effective Energy = -1.911672 Hartree
Solver HOMO Energy = -1.047270 Hartree
To provide a comparison for convergence values in the Kohn-Sham DFT iterations, here are the typical reference values for the energy components of the hydrogen molecule:
- Kinetic Energy (
$T$ ): Approximately 1.2–1.3 Hartree - External Energy (
$E_{\text{ext}}$ ): between -3.4 and -3.8 Hartree - Hartree Energy (
$E_{\text{H}}$ ): Usually around 1.2 Hartree - Exchange-Correlation Energy (
$E_{\text{XC}}$ ): Typically between -0.8 and -0.9 Hartree - Total Energy: around -1.19 Hartree
- The HOMO energy (highest occupied molecular orbital) is generally around -0.6 Hartree.
Increasing
One can also plot electron density to see if the calculation can successfully reproduce the well-known bonding and anti-bonding picture from the ground and first excited states.
energies, orbitals = eigsh(H, k=2, which='SA')
psi = orbitals[:, 0].reshape((N, N, N)) # Ground state orbital
psi /= np.sqrt(np.sum(np.abs(psi)**2) * dx**3)
rho = np.abs(psi)**2 # Two electrons in ground state
# Plot a cross-section of the electron density along the z=0 plane
plt.contourf(rho[:, :, N//2], extent=(-L/2, L/2, -L/2, L/2), origin='lower')
plt.colorbar(label='Electron Density')
plt.xlabel('x (Bohr)')
plt.ylabel('y (Bohr)')
plt.title('Electron Density for H2 Molecule in 3D')
plt.savefig('H2-bond.png')
plt.close()
psi = orbitals[:, 1].reshape((N, N, N)) # Ground state orbital
psi /= np.sqrt(np.sum(np.abs(psi)**2) * dx**3)
rho = np.abs(psi)**2 # Two electrons in ground state
rho *= num_electrons / (np.sum(rho) * dx**3)
plt.contourf(rho[:, :, N//2], extent=(-L/2, L/2, -L/2, L/2), origin='lower')
plt.colorbar(label='Electron Density')
plt.xlabel('x (Bohr)')
plt.ylabel('y (Bohr)')
plt.title('Electron Density for H2 Molecule in 3D')
plt.savefig('H2-antibond.png')
plt.close()
-
Interpret the Ground State Energy. The ground state energy of the H2 molecule is a critical value that represents the lowest energy configuration of the system. It includes contributions from the kinetic energy of the electrons, the external potential energy due to the nuclei, the Hartree energy representing electron-electron repulsion, and the exchange-correlation energy.
-
Compare the Computed Energy Values. The computed total energy of the H2 molecule should be compared with known reference values from literature or high-precision quantum chemistry calculations. Typically, the total energy for the H2 molecule is around -1.17 Hartree. Any significant deviation from this value could indicate issues with the numerical setup or the approximations used.
-
Discuss the covalent bonding. The electron density distribution in the H2 molecule shows how electrons are shared between the two protons, forming a covalent bond. In the ground state, the electron density is highest between the two nuclei, indicating a strong bonding interaction. This distribution can be visualized through contour plots or 3D density plots.
-
Compare the Results Obtained from LDA and GGA. The Local Density Approximation (LDA) and the Generalized Gradient Approximation (GGA) are two different approaches to approximating the exchange-correlation energy in DFT. LDA considers only the local electron density, while GGA includes the gradient of the electron density. GGA typically provides more accurate results for systems with varying electron densities, such as molecules and surfaces.
-
Extending DFT to More Complicated Systems (Molecules or Crystals). Extending DFT to more complicated systems, such as larger molecules or crystals, involves several numerical challenges. These include the increased computational cost due to the larger number of electrons and nuclei, the need for more sophisticated algorithms to solve the Kohn-Sham equations efficiently, and the handling of periodic boundary conditions in crystals. Advanced techniques such as plane-wave basis sets, pseudopotentials, and parallel computing are often employed to address these challenges.