🎯 Learning Objectives
- Solve the Schrödinger equation for the hydrogen atom and understand atomic orbitals
- Learn the physical meaning of the quantum numbers (n, l, m, s)
- Compute radial wavefunctions and the Bohr radius
- Understand electron spin and the Pauli exclusion principle
- Learn the Hartree approximation and the Slater determinant for many-electron atoms
- Master the concept of the Born-Oppenheimer approximation
- Understand the electronic states of the hydrogen molecular ion (H₂⁺)
- Learn the principle and applications of the variational method
📖 The Schrödinger Equation for the Hydrogen Atom
Hamiltonian of the Hydrogen Atom
The Schrödinger equation in spherical coordinates \((r, \theta, \phi)\):
\[
\left[-\frac{\hbar^2}{2m_e}\nabla^2 - \frac{e^2}{4\pi\epsilon_0 r}\right]\psi = E\psi
\]
Through separation of variables \(\psi(r, \theta, \phi) = R(r) Y_l^m(\theta, \phi)\):
Radial equation:
\[
-\frac{\hbar^2}{2m_e}\frac{1}{r^2}\frac{d}{dr}\left(r^2\frac{dR}{dr}\right) + \left[\frac{\hbar^2 l(l+1)}{2m_e r^2} - \frac{e^2}{4\pi\epsilon_0 r}\right]R = ER
\]
Quantum numbers:
- Principal quantum number \(n = 1, 2, 3, \ldots\)
- Orbital angular momentum quantum number \(l = 0, 1, \ldots, n-1\)
- Magnetic quantum number \(m = -l, -l+1, \ldots, l\)
Eigenvalues and Eigenfunctions of the Hydrogen Atom
Energy eigenvalues:
\[
E_n = -\frac{m_e e^4}{32\pi^2\epsilon_0^2\hbar^2 n^2} = -\frac{13.6 \text{ eV}}{n^2}
\]
Bohr radius:
\[
a_0 = \frac{4\pi\epsilon_0\hbar^2}{m_e e^2} \approx 0.529 \text{ Å}
\]
Atomic orbitals \(\psi_{nlm}(r, \theta, \phi) = R_{nl}(r) Y_l^m(\theta, \phi)\):
- \(n=1, l=0\): 1s orbital
- \(n=2, l=0\): 2s orbital, \(n=2, l=1\): 2p orbital
- \(n=3, l=0\): 3s orbital, \(l=1\): 3p orbital, \(l=2\): 3d orbital
💻 Worked Example 2.1: Atomic Orbitals of the Hydrogen Atom
Radial Wavefunctions
1s orbital (\(n=1, l=0\)):
\[
R_{10}(r) = 2\left(\frac{1}{a_0}\right)^{3/2} e^{-r/a_0}
\]
2s orbital (\(n=2, l=0\)):
\[
R_{20}(r) = \frac{1}{2\sqrt{2}}\left(\frac{1}{a_0}\right)^{3/2}\left(2 - \frac{r}{a_0}\right) e^{-r/(2a_0)}
\]
2p orbital (\(n=2, l=1\)):
\[
R_{21}(r) = \frac{1}{2\sqrt{6}}\left(\frac{1}{a_0}\right)^{3/2}\frac{r}{a_0} e^{-r/(2a_0)}
\]
Python implementation: atomic orbitals of the hydrogen atom
import numpy as np
import matplotlib.pyplot as plt
from scipy.special import sph_harm, genlaguerre, factorial
# Physical constants (SI units)
hbar = 1.054571817e-34 # J·s
m_e = 9.1093837015e-31 # kg
e = 1.602176634e-19 # C
epsilon_0 = 8.8541878128e-12 # F/m
a_0 = 4 * np.pi * epsilon_0 * hbar**2 / (m_e * e**2) # Bohr radius
# Atomic units (simplified)
a_0_au = 1.0 # Bohr
Ry = 13.6 # eV (Rydberg constant)
def hydrogen_energy(n):
"""Energy level of the hydrogen atom (eV)"""
return -Ry / n**2
def radial_wavefunction(r, n, l, a_0=1.0):
"""Radial wavefunction R_nl(r) (atomic units)"""
rho = 2 * r / (n * a_0)
# Laguerre polynomial
L = genlaguerre(n - l - 1, 2*l + 1)
# Normalization constant
norm = np.sqrt((2/(n*a_0))**3 * factorial(n - l - 1) / (2*n*factorial(n + l)))
R_nl = norm * np.exp(-rho/2) * rho**l * L(rho)
return R_nl
def radial_probability(r, n, l, a_0=1.0):
"""Radial probability density r² |R_nl(r)|²"""
R = radial_wavefunction(r, n, l, a_0)
return r**2 * R**2
# Radial coordinate
r = np.linspace(0, 30, 500) # in units of Bohr
# Plot
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# Radial wavefunctions
ax1 = axes[0, 0]
orbitals = [(1, 0, '1s'), (2, 0, '2s'), (2, 1, '2p'), (3, 0, '3s'), (3, 1, '3p'), (3, 2, '3d')]
for n, l, label in orbitals[:5]:
R = radial_wavefunction(r, n, l, a_0_au)
ax1.plot(r, R, linewidth=2, label=label)
ax1.set_xlabel('r (Bohr radii)')
ax1.set_ylabel('R_nl(r)')
ax1.set_title('Radial wavefunctions')
ax1.legend()
ax1.grid(True, alpha=0.3)
ax1.axhline(0, color='k', linewidth=0.5)
# Radial probability density
ax2 = axes[0, 1]
for n, l, label in orbitals[:5]:
prob = radial_probability(r, n, l, a_0_au)
ax2.plot(r, prob, linewidth=2, label=label)
ax2.set_xlabel('r (Bohr radii)')
ax2.set_ylabel('r² |R_nl(r)|²')
ax2.set_title('Radial probability density')
ax2.legend()
ax2.grid(True, alpha=0.3)
# Energy level diagram
ax3 = axes[1, 0]
n_max = 6
energies = [hydrogen_energy(n) for n in range(1, n_max+1)]
degeneracies = [n**2 for n in range(1, n_max+1)]
for n, E, g in zip(range(1, n_max+1), energies, degeneracies):
ax3.hlines(E, 0, 1, colors='blue', linewidth=2)
ax3.text(1.1, E, f'n={n}, E={E:.2f} eV ({g} states)', fontsize=10, va='center')
ax3.set_xlim([-0.1, 3])
ax3.set_ylim([min(energies) * 1.1, 0])
ax3.set_ylabel('Energy (eV)')
ax3.set_title('Energy levels of the hydrogen atom')
ax3.axhline(0, color='k', linestyle='--', linewidth=1, label='Ionization')
ax3.grid(True, alpha=0.3, axis='y')
ax3.set_xticks([])
ax3.legend()
# Most probable radius
ax4 = axes[1, 1]
r_max_values = []
n_range = range(1, 10)
for n in n_range:
for l in range(n):
r_fine = np.linspace(0.1, 50, 1000)
prob = radial_probability(r_fine, n, l, a_0_au)
r_max = r_fine[np.argmax(prob)]
r_max_values.append((n, l, r_max))
# Color-code by quantum number
colors_l = {0: 'blue', 1: 'red', 2: 'green', 3: 'purple'}
markers_l = {0: 'o', 1: 's', 2: '^', 3: 'd'}
for l_val in [0, 1, 2]:
data = [(n, r_max) for n, l, r_max in r_max_values if l == l_val]
if data:
ns, r_maxs = zip(*data)
label_l = ['s', 'p', 'd', 'f'][l_val]
ax4.plot(ns, r_maxs, markers_l[l_val], color=colors_l[l_val],
markersize=8, linewidth=2, label=f'{label_l} orbitals')
ax4.set_xlabel('Principal quantum number n')
ax4.set_ylabel('Most probable radius (Bohr radii)')
ax4.set_title('Most probable radius')
ax4.legend()
ax4.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('qchem_hydrogen_atom.png', dpi=300, bbox_inches='tight')
plt.show()
# Numerical results
print("=== Atomic orbitals of the hydrogen atom ===\n")
print(f"Bohr radius a₀ = {a_0*1e10:.4f} Å")
print(f"Rydberg constant Ry = {Ry} eV\n")
print("Energy levels:")
for n in range(1, 7):
E_n = hydrogen_energy(n)
print(f" n = {n}: E = {E_n:.4f} eV ({n**2} degenerate)")
print("\nMost probable radius (selected orbitals):")
for n, l, r_max in r_max_values[:10]:
orbital_name = ['s', 'p', 'd', 'f'][l]
print(f" {n}{orbital_name}: r_max = {r_max:.2f} a₀")
💻 Worked Example 2.2: Electron Spin and the Pauli Exclusion Principle
Electron Spin
The electron possesses an intrinsic spin angular momentum \(\mathbf{S}\):
- Spin quantum number \(s = 1/2\)
- Spin magnetic quantum number \(m_s = \pm 1/2\) (spin up \(\uparrow\), spin down \(\downarrow\))
Pauli exclusion principle:
No two or more fermions can occupy the same quantum state \((n, l, m, m_s)\).
Each atomic orbital can hold at most two electrons (a spin pair).
Electron Configuration of Many-Electron Atoms
Aufbau principle: fill orbitals starting from the lowest energy
Approximate ordering of orbital energies:
1s < 2s < 2p < 3s < 3p < 4s < 3d < 4p < ...
Hund's rule:
- For orbitals of the same energy, place electrons with parallel spins
- Fill degenerate orbitals singly before pairing electrons
Python implementation: electron configurations and spectroscopic terms
import numpy as np
import matplotlib.pyplot as plt
# Dictionary of atomic numbers and electron configurations
electron_configs = {
1: '1s¹', # H
2: '1s²', # He
3: '[He]2s¹', # Li
4: '[He]2s²', # Be
5: '[He]2s²2p¹', # B
6: '[He]2s²2p²', # C
7: '[He]2s²2p³', # N
8: '[He]2s²2p⁴', # O
9: '[He]2s²2p⁵', # F
10: '[He]2s²2p⁶', # Ne
11: '[Ne]3s¹', # Na
18: '[Ne]3s²3p⁶', # Ar
26: '[Ar]3d⁶4s²', # Fe
29: '[Ar]3d¹⁰4s¹', # Cu
}
element_names = {
1: 'H', 2: 'He', 3: 'Li', 4: 'Be', 5: 'B', 6: 'C', 7: 'N', 8: 'O',
9: 'F', 10: 'Ne', 11: 'Na', 18: 'Ar', 26: 'Fe', 29: 'Cu'
}
# First ionization energy (eV)
ionization_energies = {
1: 13.6, 2: 24.6, 3: 5.4, 4: 9.3, 5: 8.3, 6: 11.3,
7: 14.5, 8: 13.6, 9: 17.4, 10: 21.6, 11: 5.1, 18: 15.8
}
def orbital_diagram(config_str):
"""Orbital diagram for an electron configuration"""
# Simple parser (implementation omitted, conceptual)
pass
# Visualization
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# Periodicity of electron configurations (atomic numbers 1-18)
ax1 = axes[0, 0]
Z_range = list(range(1, 11)) + [11, 18]
configs = [electron_configs.get(Z, '') for Z in Z_range]
names = [element_names.get(Z, '') for Z in Z_range]
ax1.barh(range(len(Z_range)), Z_range, color='skyblue', edgecolor='black')
for i, (Z, name, config) in enumerate(zip(Z_range, names, configs)):
ax1.text(Z + 0.5, i, f'{name} ({Z}): {config}', va='center', fontsize=9)
ax1.set_yticks(range(len(Z_range)))
ax1.set_yticklabels(names)
ax1.set_xlabel('Atomic number Z')
ax1.set_title('Electron configurations (periodic table order)')
ax1.grid(True, alpha=0.3, axis='x')
# Periodicity of ionization energy
ax2 = axes[0, 1]
IE_Z = sorted(ionization_energies.keys())
IE_values = [ionization_energies[Z] for Z in IE_Z]
ax2.plot(IE_Z, IE_values, 'o-', linewidth=2, markersize=8, color='red')
for Z, IE in zip(IE_Z, IE_values):
ax2.text(Z, IE + 0.5, element_names[Z], ha='center', fontsize=9)
ax2.set_xlabel('Atomic number Z')
ax2.set_ylabel('Ionization energy (eV)')
ax2.set_title('First ionization energy')
ax2.grid(True, alpha=0.3)
# Spin multiplicity (using the C atom as an example)
ax3 = axes[1, 0]
# C atom: 1s² 2s² 2p²
# Two electrons in the 2p orbitals (parallel spins by Hund's rule)
orbital_labels = ['2p_x', '2p_y', '2p_z']
spins_up = [1, 1, 0] # number of up spins
spins_down = [0, 0, 0] # number of down spins
x = np.arange(len(orbital_labels))
width = 0.35
bars_up = ax3.bar(x - width/2, spins_up, width, label='Spin up (↑)', color='blue')
bars_down = ax3.bar(x + width/2, spins_down, width, label='Spin down (↓)', color='red')
ax3.set_ylabel('Number of electrons')
ax3.set_xlabel('2p orbitals')
ax3.set_title('2p electron configuration of the C atom (Hund\'s rule)')
ax3.set_xticks(x)
ax3.set_xticklabels(orbital_labels)
ax3.set_ylim([0, 2])
ax3.legend()
ax3.grid(True, alpha=0.3, axis='y')
# Orbital energy diagram (comparison of hydrogen-like and many-electron atoms)
ax4 = axes[1, 1]
# Hydrogen-like (n dependence only)
E_H = {1: -13.6, 2: -3.4, 3: -1.51}
orbitals_H = {'1s': (1, -13.6), '2s': (2, -3.4), '2p': (2, -3.4),
'3s': (3, -1.51), '3p': (3, -1.51), '3d': (3, -1.51)}
# Many-electron atom (with l dependence, conceptual)
E_multi = {'1s': -20, '2s': -6, '2p': -5, '3s': -2.5, '3p': -2, '3d': -0.5}
x_pos_H = [0.3, 0.8, 1.0, 1.5, 1.7, 1.9]
x_pos_multi = [0.5, 1.0, 1.2, 1.7, 1.9, 2.1]
orbital_order = ['1s', '2s', '2p', '3s', '3p', '3d']
for i, orb in enumerate(orbital_order):
if orb in orbitals_H:
ax4.hlines(orbitals_H[orb][1], x_pos_H[i]-0.1, x_pos_H[i]+0.1,
colors='blue', linewidth=2)
if orb in E_multi:
ax4.hlines(E_multi[orb], x_pos_multi[i]-0.1, x_pos_multi[i]+0.1,
colors='red', linewidth=2)
ax4.plot([], [], 'b-', linewidth=2, label='Hydrogen-like')
ax4.plot([], [], 'r-', linewidth=2, label='Multi-electron')
for i, orb in enumerate(orbital_order):
ax4.text((x_pos_H[i] + x_pos_multi[i])/2, -22, orb, ha='center', fontsize=10)
ax4.set_xlim([0, 2.5])
ax4.set_ylim([-25, 0])
ax4.set_ylabel('Energy (eV)')
ax4.set_title('Orbital energy ordering (conceptual diagram)')
ax4.legend()
ax4.grid(True, alpha=0.3, axis='y')
ax4.set_xticks([])
plt.tight_layout()
plt.savefig('qchem_electron_config.png', dpi=300, bbox_inches='tight')
plt.show()
# Numerical results
print("\n=== Electron configurations and the Pauli exclusion principle ===\n")
print("Electron configurations of selected elements:")
for Z in [1, 2, 6, 7, 8, 10]:
print(f" {element_names[Z]} (Z={Z}): {electron_configs[Z]}")
print("\nPauli exclusion principle:")
print(" - At most 1 electron per quantum state (n, l, m, m_s)")
print(" - At most 2 electrons per orbital (a spin pair)")
print("\nHund's rule (example: the C atom):")
print(" 2p²: ↑ ↑ _ (parallel spins, S=1, ³P ground state)")
💻 Worked Example 2.3: The Born-Oppenheimer Approximation and Molecular Electronic States
Born-Oppenheimer Approximation
Because electrons are far lighter than nuclei (\(m_e/m_p \approx 1/1836\)), the nuclear and electronic motions can be separated:
- Fix the nuclear positions \(\mathbf{R}\) and solve the electronic Schrödinger equation
- The electronic energy \(E_{el}(\mathbf{R})\) becomes the potential for the nuclei
- The nuclei vibrate and rotate on the surface \(E_{el}(\mathbf{R})\)
Total Hamiltonian of the molecule:
\[
\hat{H} = \hat{T}_{nuclei} + \hat{H}_{el}(\mathbf{R})
\]
\(\hat{H}_{el}(\mathbf{R})\) is the electronic Hamiltonian, which includes the nuclear coordinates \(\mathbf{R}\) as parameters.
The Hydrogen Molecular Ion (H₂⁺)
The simplest molecule: two protons (A, B) and one electron
Electronic Hamiltonian at fixed internuclear distance \(R\):
\[
\hat{H}_{el} = -\frac{\hbar^2}{2m_e}\nabla^2 - \frac{e^2}{4\pi\epsilon_0 r_A} - \frac{e^2}{4\pi\epsilon_0 r_B}
\]
LCAO (linear combination of atomic orbitals) approximation:
\[
\psi_\pm = N_\pm(\phi_A \pm \phi_B)
\]
- \(\psi_+\): bonding orbital
- \(\psi_-\): antibonding orbital
Python implementation: potential energy curve of H₂⁺
import numpy as np
import matplotlib.pyplot as plt
# Atomic units
a_0 = 1.0 # Bohr
E_h = 1.0 # Hartree
def overlap_integral(R, a_0=1.0):
"""Overlap integral S(R) (simple approximation)"""
S = np.exp(-R/a_0) * (1 + R/a_0 + (R/a_0)**2/3)
return S
def coulomb_integral(R, a_0=1.0):
"""Coulomb integral H_AA (simple approximation)"""
H_AA = -1/a_0**2 - (1/R) * (1 + 1/R) * np.exp(-2*R/a_0)
return H_AA
def exchange_integral(R, a_0=1.0):
"""Exchange integral H_AB (simple approximation)"""
S = overlap_integral(R, a_0)
H_AB = (-S/R - S/(a_0) * (1 + R/a_0)) * np.exp(-R/a_0)
return H_AB
def h2plus_energy(R, bonding=True, a_0=1.0):
"""Energy of H₂⁺ (LCAO approximation)"""
S = overlap_integral(R, a_0)
H_AA = coulomb_integral(R, a_0)
H_AB = exchange_integral(R, a_0)
# Nuclear repulsion
V_NN = 1 / R
if bonding:
# Bonding orbital
E = (H_AA + H_AB) / (1 + S) + V_NN
else:
# Antibonding orbital
E = (H_AA - H_AB) / (1 - S) + V_NN
return E
# Internuclear distance
R_range = np.linspace(0.5, 10, 200)
# Potential energy curves
E_bonding = [h2plus_energy(R, bonding=True) for R in R_range]
E_antibonding = [h2plus_energy(R, bonding=False) for R in R_range]
# Equilibrium internuclear distance and dissociation energy
E_bonding_array = np.array(E_bonding)
R_eq_idx = np.argmin(E_bonding_array)
R_eq = R_range[R_eq_idx]
E_eq = E_bonding_array[R_eq_idx]
E_dissociation = 0 # Dissociation limit (H + H⁺)
D_e = E_dissociation - E_eq
# Visualization
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# Potential energy curves
ax1 = axes[0, 0]
ax1.plot(R_range, E_bonding, 'b-', linewidth=2, label='Bonding (σ_g)')
ax1.plot(R_range, E_antibonding, 'r-', linewidth=2, label='Antibonding (σ_u*)')
ax1.axhline(0, color='k', linestyle='--', linewidth=1, label='Dissociation limit')
ax1.plot(R_eq, E_eq, 'go', markersize=10, label=f'Equilibrium (R={R_eq:.2f} a₀)')
ax1.set_xlabel('Internuclear distance R (Bohr)')
ax1.set_ylabel('Energy (Hartree)')
ax1.set_title('H₂⁺ potential energy curve')
ax1.set_ylim([-1.5, 2])
ax1.legend()
ax1.grid(True, alpha=0.3)
# Shape of the molecular orbitals (1D cross section)
ax2 = axes[0, 1]
R_vis = 2.0 # internuclear distance (for visualization)
x = np.linspace(-5, 5, 200)
# Atomic orbitals (1s)
phi_A = np.exp(-np.abs(x + R_vis/2))
phi_B = np.exp(-np.abs(x - R_vis/2))
# Molecular orbitals
S_vis = overlap_integral(R_vis)
psi_bonding = (phi_A + phi_B) / np.sqrt(2 * (1 + S_vis))
psi_antibonding = (phi_A - phi_B) / np.sqrt(2 * (1 - S_vis))
ax2.plot(x, psi_bonding, 'b-', linewidth=2, label='Bonding MO (σ_g)')
ax2.plot(x, psi_antibonding, 'r-', linewidth=2, label='Antibonding MO (σ_u*)')
ax2.axvline(-R_vis/2, color='k', linestyle=':', linewidth=1, label='Nuclei')
ax2.axvline(R_vis/2, color='k', linestyle=':', linewidth=1)
ax2.axhline(0, color='k', linewidth=0.5)
ax2.set_xlabel('Position (Bohr)')
ax2.set_ylabel('Wavefunction ψ(x)')
ax2.set_title(f'Molecular orbitals (R = {R_vis} a₀)')
ax2.legend()
ax2.grid(True, alpha=0.3)
# Electron density
ax3 = axes[1, 0]
rho_bonding = psi_bonding**2
rho_antibonding = psi_antibonding**2
ax3.plot(x, rho_bonding, 'b-', linewidth=2, label='Bonding |ψ|²')
ax3.plot(x, rho_antibonding, 'r-', linewidth=2, label='Antibonding |ψ|²')
ax3.axvline(-R_vis/2, color='k', linestyle=':', linewidth=1)
ax3.axvline(R_vis/2, color='k', linestyle=':', linewidth=1)
ax3.set_xlabel('Position (Bohr)')
ax3.set_ylabel('Electron density |ψ|²')
ax3.set_title('Electron density distribution')
ax3.legend()
ax3.grid(True, alpha=0.3)
# Energy level diagram
ax4 = axes[1, 1]
# Atomic orbital energy
E_1s = -0.5 # 1s energy of the H atom (in Hartree)
# Molecular orbital energies (R = R_eq)
E_sigma_g = h2plus_energy(R_eq, bonding=True) - 1/R_eq # excluding nuclear repulsion
E_sigma_u = h2plus_energy(R_eq, bonding=False) - 1/R_eq
# Plot
ax4.hlines(E_1s, 0, 0.3, colors='blue', linewidth=3, label='H 1s')
ax4.hlines(E_1s, 0.7, 1.0, colors='blue', linewidth=3)
ax4.hlines(E_sigma_g, 0.35, 0.65, colors='green', linewidth=3, label='σ_g (bonding)')
ax4.hlines(E_sigma_u, 0.35, 0.65, colors='red', linewidth=3, label='σ_u* (antibonding)')
# Electron configuration (↑ = 1 electron)
ax4.plot(0.5, E_sigma_g, 'o', color='black', markersize=10)
ax4.text(0.5, E_sigma_g - 0.15, '↑', fontsize=16, ha='center')
ax4.set_xlim([-0.1, 1.1])
ax4.set_ylim([-0.8, 0.2])
ax4.set_ylabel('Energy (Hartree)')
ax4.set_title('H₂⁺ molecular orbital energy levels')
ax4.set_xticks([0.15, 0.5, 0.85])
ax4.set_xticklabels(['H(A)', 'H₂⁺', 'H(B)'])
ax4.legend(loc='upper right')
ax4.grid(True, alpha=0.3, axis='y')
plt.tight_layout()
plt.savefig('qchem_h2plus_molecule.png', dpi=300, bbox_inches='tight')
plt.show()
# Numerical results
print("\n=== H₂⁺ molecule (LCAO approximation) ===\n")
print(f"Equilibrium internuclear distance R_eq = {R_eq:.2f} Bohr = {R_eq * 0.529:.2f} Å")
print(f"Equilibrium energy E_eq = {E_eq:.4f} Hartree = {E_eq * 27.2:.2f} eV")
print(f"Dissociation energy D_e = {D_e:.4f} Hartree = {D_e * 27.2:.2f} eV")
print(f"\nComparison with experimental values:")
print(f" R_eq (experiment) ≈ 1.06 Å")
print(f" D_e (experiment) ≈ 2.79 eV")
print(f"\nThe LCAO approximation is qualitatively correct, but quantitative improvement requires expanding the basis set")
💻 Worked Example 2.4: The Principle of the Variational Method
Variational Principle
For any normalized trial function \(\phi\):
\[
E[\phi] = \frac{\langle \phi | \hat{H} | \phi \rangle}{\langle \phi | \phi \rangle} \geq E_0
\]
where \(E_0\) is the exact ground-state energy.
Variational method:
- Prepare a trial function \(\phi(\alpha)\) containing a parameter \(\alpha\)
- Compute the energy expectation value \(E(\alpha) = \langle \phi(\alpha) | \hat{H} | \phi(\alpha) \rangle\)
- Solve \(\frac{\partial E}{\partial \alpha} = 0\) to find the optimal parameter
The optimized \(E(\alpha_{opt})\) provides an upper bound on the ground-state energy.
Python implementation: ground-state calculation by the variational method
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import minimize_scalar
from scipy.integrate import quad
# Hamiltonian of the harmonic oscillator (atomic units)
hbar = 1.0
m = 1.0
omega = 1.0
def harmonic_potential(x, omega=1.0):
"""Harmonic oscillator potential"""
return 0.5 * omega**2 * x**2
def trial_wavefunction_gaussian(x, alpha):
"""Trial function (Gaussian)"""
return (alpha / np.pi)**0.25 * np.exp(-alpha * x**2 / 2)
def kinetic_energy_expectation(alpha):
"""Expectation value of kinetic energy ⟨T⟩"""
# ⟨T⟩ = ∫ ψ* (-ℏ²/2m d²/dx²) ψ dx = ℏ²α/(4m)
return hbar**2 * alpha / (4 * m)
def potential_energy_expectation(alpha, omega=1.0):
"""Expectation value of potential energy ⟨V⟩"""
# ⟨V⟩ = ∫ ψ* (1/2 m ω² x²) ψ dx = m ω²/(4α)
return m * omega**2 / (4 * alpha)
def energy_expectation(alpha, omega=1.0):
"""Expectation value of total energy E(α)"""
T = kinetic_energy_expectation(alpha)
V = potential_energy_expectation(alpha, omega)
return T + V
# Range of the variational parameter
alpha_range = np.linspace(0.1, 3.0, 200)
E_variational = [energy_expectation(alpha, omega) for alpha in alpha_range]
# Optimal parameter
result = minimize_scalar(lambda a: energy_expectation(a, omega),
bounds=(0.1, 10), method='bounded')
alpha_opt = result.x
E_opt = result.fun
# Exact solution
E_exact = 0.5 * hbar * omega
# Visualization
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# Energy expectation value vs variational parameter
ax1 = axes[0, 0]
ax1.plot(alpha_range, E_variational, 'b-', linewidth=2, label='E(α)')
ax1.axhline(E_exact, color='r', linestyle='--', linewidth=2, label=f'Exact E₀ = {E_exact:.3f}')
ax1.plot(alpha_opt, E_opt, 'go', markersize=10, label=f'Optimum α = {alpha_opt:.3f}')
ax1.set_xlabel('Variational parameter α')
ax1.set_ylabel('Energy E(α)')
ax1.set_title('Variational energy')
ax1.legend()
ax1.grid(True, alpha=0.3)
# Comparison of the trial function and the exact solution
ax2 = axes[0, 1]
x = np.linspace(-4, 4, 500)
psi_trial = trial_wavefunction_gaussian(x, alpha_opt)
psi_exact = (omega / np.pi)**0.25 * np.exp(-omega * x**2 / 2)
ax2.plot(x, psi_trial, 'b-', linewidth=2, label=f'Trial (α={alpha_opt:.3f})')
ax2.plot(x, psi_exact, 'r--', linewidth=2, label='Exact')
ax2.set_xlabel('Position x')
ax2.set_ylabel('Wavefunction ψ(x)')
ax2.set_title('Comparison of wavefunctions')
ax2.legend()
ax2.grid(True, alpha=0.3)
# Kinetic energy and potential energy
ax3 = axes[1, 0]
T_values = [kinetic_energy_expectation(alpha) for alpha in alpha_range]
V_values = [potential_energy_expectation(alpha, omega) for alpha in alpha_range]
ax3.plot(alpha_range, T_values, 'b-', linewidth=2, label='⟨T⟩')
ax3.plot(alpha_range, V_values, 'r-', linewidth=2, label='⟨V⟩')
ax3.plot(alpha_range, E_variational, 'g-', linewidth=2, label='⟨E⟩ = ⟨T⟩ + ⟨V⟩')
ax3.axvline(alpha_opt, color='k', linestyle=':', linewidth=1, label=f'α_opt')
ax3.set_xlabel('Variational parameter α')
ax3.set_ylabel('Energy')
ax3.set_title('Energy components')
ax3.legend()
ax3.grid(True, alpha=0.3)
# Verification of the virial theorem
ax4 = axes[1, 1]
# For the harmonic oscillator, ⟨T⟩ = ⟨V⟩ (virial theorem)
T_at_opt = kinetic_energy_expectation(alpha_opt)
V_at_opt = potential_energy_expectation(alpha_opt, omega)
ratio = np.array(T_values) / np.array(V_values)
ax4.plot(alpha_range, ratio, 'purple', linewidth=2, label='⟨T⟩ / ⟨V⟩')
ax4.axhline(1, color='r', linestyle='--', linewidth=2, label='Virial theorem (=1)')
ax4.axvline(alpha_opt, color='k', linestyle=':', linewidth=1)
ax4.set_xlabel('Variational parameter α')
ax4.set_ylabel('⟨T⟩ / ⟨V⟩')
ax4.set_title('Virial theorem')
ax4.legend()
ax4.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('qchem_variational_method.png', dpi=300, bbox_inches='tight')
plt.show()
# Numerical results
print("\n=== Ground-state calculation by the variational method ===\n")
print(f"Trial function: ψ(x; α) = (α/π)^(1/4) exp(-αx²/2)")
print(f"\nOptimization results:")
print(f" Optimal parameter α_opt = {alpha_opt:.6f}")
print(f" Variational energy E(α_opt) = {E_opt:.6f}")
print(f" Exact energy E_exact = {E_exact:.6f}")
print(f" Relative error = {abs(E_opt - E_exact)/E_exact * 100:.6f} %")
print(f"\nVerification of the virial theorem:")
print(f" ⟨T⟩ = {T_at_opt:.6f}")
print(f" ⟨V⟩ = {V_at_opt:.6f}")
print(f" ⟨T⟩ / ⟨V⟩ = {T_at_opt / V_at_opt:.6f} ≈ 1")
📚 Summary
- The hydrogen atom is the only many-body problem with an exact solution and forms the basis of atomic orbitals
- The quantum numbers (n, l, m, m_s) completely specify the state of an electron
- Atomic orbitals are described by the product of a radial wavefunction and a spherical harmonic
- By the Pauli exclusion principle, no two or more electrons can occupy the same quantum state
- Electron configurations follow the Aufbau principle, Hund's rule, and the Pauli exclusion principle
- The Born-Oppenheimer approximation separates nuclear and electronic motion, enabling calculation of molecular electronic states
- The LCAO approximation is a fundamental method that builds molecular orbitals from linear combinations of atomic orbitals
- The variational method is a powerful technique that uses trial functions to obtain an upper bound on the ground-state energy
- These concepts form the basis of molecular orbital theory and DFT
💡 Exercises
- Excited states of the hydrogen atom: Compute the radial probability density for all n=3 orbitals (3s, 3p, 3d) and find the most probable radius.
- The He⁺ ion: Compute the energy levels and Bohr radius of the hydrogen-like ion He⁺ with Z=2.
- Electron configuration of the N atom: Write the ground-state electron configuration of the N atom (Z=7) and, using Hund's rule, find the total spin S and orbital angular momentum L.
- The H₂ molecule: Extend the H₂⁺ results to implement an LCAO calculation for the H₂ molecule (two electrons).
- Application of the variational method: Approximate the 1s orbital of the hydrogen atom with the trial function ψ(r; α) = exp(-αr) and find the optimal α by the variational method.