Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

The Quantum Harmonic Oscillator

Hamiltonian of the Harmonic Oscillator

H=12μpx2+12kx2{H = \frac{1}{2\mu}p_x^2 + \frac{1}{2}kx^2}
H^=22μd2dx2+12kx2{\hat{H} = -\frac{\hbar^2}{2\mu}\frac{d^2}{dx^2} + \frac{1}{2}kx^2 }
H^ψv(x)=Evψv(x)\hat{H}\psi_v(x) = E_v \psi_v(x)

Parameters of the Hamiltonian

Eigenfunctions and Eigenvalues

ψ0(x)=(απ)1/4eαx2/2{\psi_0(x) = \left(\frac{\alpha}{\pi}\right)^{1/4}e^{-\alpha x^2/2}}
ψ1(x)=(4α3π)1/4xeαx2/2{\psi_1(x) = \left(\frac{4\alpha^3}{\pi}\right)^{1/4} x e^{-\alpha x^2/2}}

Hermite polynomials show nodal and symmetry features of wavefunctions

nHermite Polynomial (Hn(x))( H_n(x))
01
12x2x
24x224x^2 - 2
38x312x8x^3 - 12x
416x448x2+1216x^4 - 48x^2 + 12

Hermite polynomials obey the following relations, which are useful when evaluating integrals.

Characteristic Equation

Hv(y)2yHv(y)+2vHv(y)=0{H_v''(y) - 2yH_v'(y) + 2vH_v(y) = 0}

Recursion Relation

Hv+1(y)=2yHv(y)2vHv1(y){H_{v+1}(y) = 2yH_v(y) - 2vH_{v-1}(y)}

Orthogonality and Normalization

Hv(y)Hv(y)ey2dy={0, if vvπ2vv!, if v=v{\int\limits_{-\infty}^{\infty}H_{v'}(y)H_v(y)e^{-y^2}dy = \left\lbrace\begin{matrix} 0, & \textnormal{ if }v' \ne v\\ \sqrt{\pi}2^vv!, & \textnormal{ if }v' = v\\ \end{matrix}\right. }
Source
import numpy as np
import matplotlib.pyplot as plt
from scipy.special import hermite, factorial

# Hermite polynomial H_v(y)
def H_v(v, y):
    return hermite(v)(y)

# Harmonic oscillator wavefunction ψ_v(y)
def psi_v(v, y):
    norm_factor = 1 / np.sqrt(2**v * factorial(v) * np.sqrt(np.pi))
    return norm_factor * H_v(v, y) * np.exp(-y**2 / 2)

# Define the y values
y_values = np.linspace(-5, 5, 1000)

# Plot the first three Hermite polynomials
fig, axs = plt.subplots(1, 2, figsize=(12, 6))

# Hermite polynomials H_0, H_1, H_2
axs[0].plot(y_values, H_v(0, y_values), label=r'$H_0(y)$', color='blue')
axs[0].plot(y_values, H_v(1, y_values), label=r'$H_1(y)$', color='green')
axs[0].plot(y_values, H_v(2, y_values), label=r'$H_2(y)$', color='red')
axs[0].set_title('First Three Hermite Polynomials')
axs[0].set_xlabel('y')
axs[0].set_ylabel(r'$H_v(y)$')
axs[0].legend()

# Harmonic oscillator wavefunctions ψ_0, ψ_1, ψ_2
axs[1].plot(y_values, psi_v(0, y_values), label=r'$\psi_0(y)$', color='blue')
axs[1].plot(y_values, psi_v(1, y_values), label=r'$\psi_1(y)$', color='green')
axs[1].plot(y_values, psi_v(2, y_values), label=r'$\psi_2(y)$', color='red')
axs[1].set_title('First Three Harmonic Oscillator Wavefunctions')
axs[1].set_xlabel('y')
axs[1].set_ylabel(r'$\psi_v(y)$')
axs[1].legend()

plt.tight_layout()
plt.show()
<Figure size 1200x600 with 2 Axes>
Source
import numpy as np
import matplotlib.pyplot as plt
from scipy.special import factorial

# Set constants
VMAX = 6
QPAD_FRAC = 1.3
SCALING = 0.7
COLOUR1 = 'blue'
COLOUR2 = 'red'

# Normalization constant and energy for vibrational state v
def N(v):
    return 1. / np.sqrt(np.sqrt(np.pi) * 2**v * factorial(v))

def get_E(v):
    return v + 0.5

# Generate Hermite polynomials
def make_Hr():
    Hr = [np.poly1d([1.]), np.poly1d([2., 0.])]
    for v in range(2, VMAX + 1):
        Hr.append(2 * np.poly1d([1., 0.]) * Hr[v-1] - 2 * (v-1) * Hr[v-2])
    return Hr

Hr = make_Hr()

# Get wavefunction and turning points
def get_psi(v, q):
    return N(v) * Hr[v](q) * np.exp(-q**2 / 2)

def get_turning_points(v):
    qmax = np.sqrt(2 * get_E(v))
    return -qmax, qmax

# Get potential energy
def get_potential(q):
    return q**2 / 2

# Plot function
def plot_func(ax, f, q, scaling=1, yoffset=0):
    ax.plot(q, f * scaling + yoffset, color=COLOUR1)
    ax.fill_between(q, f * scaling + yoffset, yoffset, f > 0, color=COLOUR1, alpha=0.5)
    ax.fill_between(q, f * scaling + yoffset, yoffset, f < 0, color=COLOUR2, alpha=0.5)

# Create subplots
fig, axs = plt.subplots(1, 2, figsize=(14, 8))

# Generate q range and potential
qmin, qmax = get_turning_points(VMAX)
xmin, xmax = QPAD_FRAC * qmin, QPAD_FRAC * qmax
q = np.linspace(qmin, qmax, 500)
V = get_potential(q)

# Plot potential and wavefunctions for both psi and |psi|^2
for ax, prob_plot in zip(axs, [False, True]):
    ax.plot(q, V, color='k', linewidth=1.5, label='Potential $V(q)$')
    for v in range(VMAX + 1):
        psi_v = get_psi(v, q)
        E_v = get_E(v)
        plot_func(ax, psi_v**2 if prob_plot else psi_v, q, scaling=SCALING * (1.5 if prob_plot else 1), yoffset=E_v)
        ax.text(s=f'$E_{v} = {E_v:.1f}$', x=qmax + 0.2, y=E_v + 0.1, va='center', ha='left')
        ax.axhline(y=E_v, color='gray', linestyle='--', alpha=0.7)  # Draw energy levels as dashed lines
    ax.set_xlim(xmin, xmax)
    ax.set_ylim(0, E_v + 0.5)
    ax.set_yticks([])
    ax.spines['top'].set_visible(False)
    ax.spines['right'].set_visible(False)
    ax.spines['left'].set_position('center')
    ax.set_xlabel('$q$', fontsize=12)
    ylabel = r'$|\psi(q)|^2$' if prob_plot else r'$\psi(q)$'
    ax.set_title(ylabel, fontsize=14)
    ax.legend()

plt.tight_layout()
plt.show()
<Figure size 1400x800 with 2 Axes>

Odd/even symmetry of wavefunctions

Consequences for evaluating integrals

xn=nxn=0\langle x_n \rangle = \langle n|x |n\rangle =0
xn20\langle x^2_n\rangle \neq 0
p=npx^n=0\langle p \rangle = \langle n | \hat{p_x}|n\rangle = 0
p2=npx2^n0\langle p^2 \rangle = \langle n | \hat{p^2_x}|n\rangle \neq 0

Visual illustration of orthogonality of eigenfunctions for quantum harmonic oscillator

Source
import numpy as np
import matplotlib.pyplot as plt
from scipy.special import hermite
from scipy.integrate import quad

# Hermite polynomial H_v(y) function
def H_v(v, y):
    return hermite(v)(y)

# Function to compute the product of H_v and H_v' for orthogonality check
def integrand(y, v1, v2):
    return H_v(v1, y) * H_v(v2, y) * np.exp(-y**2)

# Compute orthogonality integral
def check_orthogonality(v1, v2):
    # Integral of the product of two Hermite polynomials with exp(-y^2)
    integral, _ = quad(lambda y: integrand(y, v1, v2), -10, 10)  # Using finite limits for stability
    return integral

# Define the y range for plotting
y_values = np.linspace(-5, 5, 1000)

# Plot and check orthogonality for different pairs
def plot_orthogonality(v1, v2):
    H1 = H_v(v1, y_values)
    H2 = H_v(v2, y_values)
    product = H1 * H2 * np.exp(-y_values**2)

    fig, ax = plt.subplots(figsize=(10, 6))
    ax.plot(y_values, product, label=f'Product $H_{{{v1}}}(y) H_{{{v2}}}(y) e^{{-y^2}}$', color='purple')
    ax.fill_between(y_values, 0, product, where=(product > 0), color='blue', alpha=0.5, label='Positive area')
    ax.fill_between(y_values, 0, product, where=(product < 0), color='red', alpha=0.5, label='Negative area')
    
    # Orthogonality integral
    integral_value = check_orthogonality(v1, v2)
    ax.set_title(f'Orthogonality Check: $H_{{{v1}}}(y)$ and $H_{{{v2}}}(y)$ \n Integral Value = {integral_value:.2e}')
    ax.set_xlabel('y')
    ax.set_ylabel(f'$H_{{{v1}}}(y) H_{{{v2}}}(y) e^{{-y^2}}$')
    ax.axhline(0, color='black', linestyle='--')
    ax.legend()
    plt.show()

# Plot orthogonality for different cases
plot_orthogonality(1, 2)  # Odd vs Even: H_1(y) and H_2(y)
plot_orthogonality(0, 2)  # Even vs Even: H_0(y) and H_2(y)
plot_orthogonality(1, 3)  # Odd vs Odd: H_1(y) and H_3(y)
<Figure size 1000x600 with 1 Axes>
<Figure size 1000x600 with 1 Axes>
<Figure size 1000x600 with 1 Axes>

Tunneling and quantum-to-classical behavior

Source
import numpy as np
import matplotlib.pyplot as plt
from scipy.special import factorial

# Set constants
QPAD_FRAC = 1.3
SCALING = 1.0
COLORS = ['blue', 'green', 'red']  # Different colors for each wavefunction
V_VALUES = [0, 2, 10]  # Vibrational quantum numbers to plot

# Normalization constant for vibrational state v
def normalization_constant(v):
    return 1. / np.sqrt(np.sqrt(np.pi) * 2**v * factorial(v))

# Energy for vibrational state v
def energy_level(v):
    return v + 0.5

# Generate Hermite polynomials
def make_Hermite_polynomials(v_max):
    Hr = [np.poly1d([1.]), np.poly1d([2., 0.])]
    for v in range(2, v_max + 1):
        Hr.append(2 * np.poly1d([1., 0.]) * Hr[v-1] - 2 * (v-1) * Hr[v-2])
    return Hr

# Get wavefunction for level v on grid q
def get_wavefunction(v, q, Hr):
    return normalization_constant(v) * Hr[v](q) * np.exp(-q**2 / 2)

# Get classical turning points for state v
def get_turning_points(v):
    qmax = np.sqrt(2 * energy_level(v))
    return -qmax, qmax

# Classical probability distribution for a harmonic oscillator
def classical_probability(v, q):
    qmax = np.sqrt(2 * energy_level(v))
    return 1 / (np.pi * np.sqrt(qmax**2 - q**2))

# Create plot
def plot_wavefunctions_square(v_values, colors):
    fig, ax = plt.subplots(figsize=(14, 8))
    
    # Generate Hermite polynomials up to the maximum v in v_values
    v_max = max(v_values)
    Hr = make_Hermite_polynomials(v_max)
    
    # Plot quantum probability distributions for each v
    for v, color in zip(v_values, colors):
        qmin, qmax = get_turning_points(v)
        q = np.linspace(qmin * 1.5, qmax * 1.5, 5000)  # Increased resolution
        psi_v_square = get_wavefunction(v, q, Hr)**2
        
        # Plot |psi|^2 for quantum mechanical probability
        ax.plot(q, psi_v_square, label=f'Quantum $v={v}$', color=color)
        
        # Plot classical probability for the same v
        q_classical = np.linspace(-qmax, qmax, 1000)
        classical_prob = classical_probability(v, q_classical)
        ax.plot(q_classical, classical_prob, '--', label=f'Classical $v={v}$', color=color, alpha=0.7)

    ax.set_xlim(-10, 10)
    ax.set_ylim([0, 1])
    ax.set_xlabel('$q$')
    ax.set_ylabel(r'$|\psi(q)|^2$ and Classical Probability')
    ax.set_title('Quantum-Classical Comparison: $|\psi(q)|^2$ vs Classical Probability for v=0, 2, 10')
    ax.legend()
    
    plt.show()
    
# Plot wavefunction squares for v=0, 10, 100, and classical probability
plot_wavefunctions_square(V_VALUES, COLORS)
<>:66: SyntaxWarning: invalid escape sequence '\p'
<>:66: SyntaxWarning: invalid escape sequence '\p'
/tmp/ipykernel_3384/3622494536.py:66: SyntaxWarning: invalid escape sequence '\p'
  ax.set_title('Quantum-Classical Comparison: $|\psi(q)|^2$ vs Classical Probability for v=0, 2, 10')
/tmp/ipykernel_3384/3622494536.py:38: RuntimeWarning: divide by zero encountered in divide
  return 1 / (np.pi * np.sqrt(qmax**2 - q**2))
<Figure size 1400x800 with 1 Axes>

Problems