“Know how to solve every problem that has been solved.” “What I cannot create, I do not understand.” — Richard Feynman

Harmonic Oscillator (Finite Difference)

Quantum Chemistry

What you need to know first 1 concepts, 1 layers

The requisite-knowledge inventory for this page, bottom-up: the primitives at the base, combined upward until you reach what this page assumes. Skim the layers you already own; start wherever the ground gets unfamiliar.

  1. base
  2. you are here

The quantum harmonic oscillator is one of the most important problems in quantum mechanics. This page demonstrates solving the time-independent Schrödinger equation for the harmonic oscillator using the finite difference method.

Time-Independent Schrödinger Equation

The time-independent Schrödinger equation is:

For the harmonic oscillator, the potential is:

In atomic units (), this simplifies to:

For , the potential becomes .

Finite Difference Discretization

We discretize the domain into equally spaced points with spacing . The second derivative is approximated using central differences:

This gives us a finite difference matrix:

Hamiltonian Matrix

The Hamiltonian matrix is constructed as:

where is a diagonal matrix with elements . The eigenvalue problem becomes:

This is a standard eigenvalue problem that can be solved using standard linear algebra routines.

Analytical Solution

The analytical eigenvalues for the harmonic oscillator are:

In atomic units with :

The ground state energy is , first excited state is , etc.

Python Implementation

The following code solves the harmonic oscillator using finite differences:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
from scipy.linalg import eigh

def set_publication_style():
    """Set publication-quality matplotlib style."""
    mpl.rcParams.update({
        'font.family': 'serif',
        'font.size': 12,
        'axes.labelsize': 14,
        'axes.titlesize': 16,
        'axes.linewidth': 1.2,
        'axes.labelpad': 8,
        'axes.titlepad': 10,
        'xtick.labelsize': 12,
        'ytick.labelsize': 12,
        'xtick.direction': 'in',
        'ytick.direction': 'in',
        'xtick.top': True,
        'ytick.right': True,
        'xtick.major.size': 6,
        'ytick.major.size': 6,
        'xtick.major.width': 1.2,
        'ytick.major.width': 1.2,
        'legend.fontsize': 12,
        'legend.frameon': False,
        'lines.linewidth': 2,
        'lines.markersize': 6,
        'figure.dpi': 100,
        'savefig.dpi': 300,
        'savefig.bbox': 'tight'
    })

set_publication_style()

# Parameters
N = 1000  # Number of grid points
x_min, x_max = -5.0, 5.0  # Domain boundaries
x = np.linspace(x_min, x_max, N)
dx = x[1] - x[0]  # Grid spacing

# Planck's constant and mass (atomic units: hbar = 1, m = 1)
hbar = 1.0
m = 1.0
omega = 1.0  # Angular frequency

# Potential function: V(x) = (1/2) * m * omega^2 * x^2
def potential(x, omega=1.0):
    return 0.5 * omega**2 * x**2

# Finite difference matrix for second derivative
# Using central differences: (f_{i+1} - 2f_i + f_{i-1}) / dx^2
D2 = (-2.0 * np.eye(N) + np.diag(np.ones(N-1), 1) + np.diag(np.ones(N-1), -1)) / dx**2

# Kinetic energy operator: T = - (hbar^2 / 2m) * d^2/dx^2
T = -(hbar**2 / (2.0 * m)) * D2

# Potential energy operator: diagonal matrix
V = np.diag(potential(x, omega))

# Hamiltonian matrix
H = T + V

# Solve eigenvalue problem
energies, wavefunctions = eigh(H)

# Select first few states
num_states = 5
selected_energies = energies[:num_states]
selected_wavefunctions = wavefunctions[:, :num_states]

# Analytical eigenvalues for comparison
analytical_energies = np.array([0.5 + n for n in range(num_states)])

# Print comparison
print("Energy Level | Numerical | Analytical | Error")
print("-" * 50)
for i in range(num_states):
    error = abs(selected_energies[i] - analytical_energies[i])
    print(f"n = {i:2d}     | {selected_energies[i]:8.6f}  | {analytical_energies[i]:8.6f}  | {error:.2e}")

# Normalize wavefunctions (they should already be normalized from eigh, but ensure)
for i in range(num_states):
    norm = np.sqrt(np.trapz(selected_wavefunctions[:, i]**2, x))
    selected_wavefunctions[:, i] /= norm

# Plot results
fig, axes = plt.subplots(1, 2, figsize=(14, 6))

# Left plot: Potential and wavefunctions
V_vals = potential(x, omega)
axes[0].plot(x, V_vals, 'k--', linewidth=2, label='Potential V(x)')

colors = plt.cm.viridis(np.linspace(0, 1, num_states))
for i in range(num_states):
    # Plot probability density shifted by energy
    axes[0].plot(x, selected_wavefunctions[:, i]**2 + selected_energies[i], 
                 color=colors[i], linewidth=2, 
                 label=f'|ψ_{i}|², E_{i} = {selected_energies[i]:.4f}')

axes[0].set_xlabel('x')
axes[0].set_ylabel('Energy')
axes[0].set_title('Harmonic Oscillator: Wavefunctions and Energy Levels')
axes[0].legend()
axes[0].grid(True, alpha=0.3)
axes[0].set_xlim(x_min, x_max)

# Right plot: Wavefunctions (not shifted)
for i in range(num_states):
    axes[1].plot(x, selected_wavefunctions[:, i], 
                color=colors[i], linewidth=2, 
                label=f'ψ_{i}(x)')

axes[1].axhline(0, color='k', linestyle='--', linewidth=0.5)
axes[1].set_xlabel('x')
axes[1].set_ylabel('ψ(x)')
axes[1].set_title('Harmonic Oscillator Wavefunctions')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
axes[1].set_xlim(x_min, x_max)

plt.tight_layout()
plt.savefig('figures/harmonic_oscillator_wavefunctions.png', dpi=300, bbox_inches='tight')
plt.show()

# Verify orthogonality
print("\nOrthogonality check (should be close to identity):")
overlap = np.zeros((num_states, num_states))
for i in range(num_states):
    for j in range(num_states):
        overlap[i, j] = np.trapz(selected_wavefunctions[:, i] * selected_wavefunctions[:, j], x)
print(overlap)

Visualization

The following plot shows the harmonic oscillator wavefunctions and energy levels:

Harmonic Oscillator Wavefunctions

Key Features

Convergence

The accuracy of the finite difference method depends on:

Applications

The harmonic oscillator is fundamental in:

Extension to Other Potentials

The same finite difference approach can be used for other potentials: