Towards the Hydrogen Atom

The Hydrogen Atom

Now that we’ve discussed the angular momentum operator, we can tackle one of the most important problems in quantum mechanics: the hydrogen atom. The hydrogen atom consists of a proton and an electron bound together by the Coulomb force. This system is particularly important because:

  1. It is the simplest atomic system and can be solved analytically
  2. More complex atoms can be understood as perturbations of the hydrogen atom
  3. It demonstrates key quantum mechanical concepts like quantization and orbital angular momentum

Let’s approach this step by step, starting with the Schrödinger equation in spherical coordinates.

The Schrödinger Equation in Spherical Coordinates

For a spherically symmetric potential, the time-independent Schrödinger equation takes the form:

0=H^ψ(r,ϑ,φ)Eψ(r,ϑ,φ)0=1r2r(r2ψr)+1r2sinϑϑ(sinϑψϑ)+1r2sin2ϑ2ψφ2+2m2(EV(r))ψ.

This equation looks intimidating, but we can break it down into manageable pieces using a powerful mathematical technique called separation of variables.

Separation of Variables

The key insight is that the wavefunction can be written as a product of functions, each depending on only one coordinate:

ψ(r,ϑ,φ)=R(r)Θ(ϑ)Φ(φ).

This separation ansatz reflects the spherical symmetry of the problem. Each function will describe a different aspect of the electron’s behavior: - R(r) describes how the probability density varies with distance from the nucleus - Θ(θ) and Φ(ϕ) together describe the angular distribution of the electron

Substituting this into the Schrödinger equation and rearranging terms:

0=1r2r(r2Rr)ΘΦ+1r2sin(ϑ)ϑ(sin(ϑ)Θϑ)RΦ+1r2sin2(ϑ)2Φφ2RΘ+2m2(EV(r))RΘΦ,

For mathematical convenience, we multiply by r2sin2(ϑ)/[RΘΦ]:

0=sin2(ϑ)Rddr(r2dRdr)+sin(ϑ)Θddϑ(sin(ϑ)dΘϑ)+1Φd2Φdφ2+2m2(EV(r))r2sin2(ϑ).

We can now separate the ϕ-dependent terms:

1Φd2Φdφ2=sin2(ϑ)Rddr(r2dRdr)+sin(ϑ)Θddϑ(sin(ϑ)dΘϑ)+2m2(EV(r))r2sin2(ϑ)

This equation has a remarkable property: the left side depends only on ϕ, while the right side depends only on r and ϑ. For this equality to hold for all values of the coordinates, both sides must equal a separation constant, which we’ll call C1.

The Azimuthal Equation

The equation for Φ(φ) is:

d2Φ(φ)dφ2=C1Φ(φ)

This is a simple harmonic oscillator equation with solution:

Φ(φ)=Ae±iC1φ.

Because the wavefunction must be single-valued when φ changes by 2π:

Φ(φ+n2π)=Φ(φ)e±2πinC1=1C1=m(mZ).

This gives us our first quantum number m, which represents the z-component of angular momentum. The normalized solution is:

Φm(φ)=12πeimφ

These functions are orthonormal:

φ=02πΦm(φ)Φn(φ)dφ=δmn.

The Polar Angle Equation

For the ϑ-dependent part, we get:

C1=sin2(ϑ)R(r)ddr(r2dR(r)dr)+sin(ϑ)Θ(ϑ)ddϑ(sin(ϑ)dΘ(ϑ)dϑ)+2m2(EV(r))r2sin2(ϑ),

Dividing by sin2(ϑ) and rearranging:

C1sin2(ϑ)1sin(ϑ)Θ(ϑ)ddϑ(sin(ϑ)dΘ(ϑ)dϑ)=1R(r)ddr(r2dR(r)dr)+2m2(EV(r))r2,

Again, the left side depends only on ϑ while the right side depends only on r, introducing another separation constant C2. For the ϑ equation:

C2=1sin(ϑ)Θ(ϑ)ddϑ(sin(ϑ)dΘ(ϑ)dϑ)C1sin2(ϑ).

When m=0, using ξ=cos(ϑ), this becomes the Legendre equation:

ddξ[(1ξ2)dΘ(ϑ)dξ]+C2Θ=0.

The solutions are Legendre polynomials:

Θ=k=0akξk

With the recursion relation:

ak+2=k(k+1)C2(k+2)(k+1)ak.

For finite solutions, we need C2=l(l+1) where l is an integer 0. This gives us our second quantum number l, which represents the total angular momentum.

For m0, we get associated Legendre polynomials:

Plm(cos(ϑ))=const.(1ξ2)|m2|d|m|dξ|m|Pl(cos(ϑ))

With the condition |m|l and normalization:

ϑ=0π|Plm(cos(ϑ))|2sin(ϑ)dϑ=1.

Spherical Harmonics

The angular part of the solution combines Θ and Φ into spherical harmonics:

Ylm(ϑ,φ)=Plm(cos(ϑ))Φcos(φ)

These are normalized:

ϑ=0πφ=02π|Ylm(ϑ,φ)|2sin(ϑ)dϑdφ=1.

The first few spherical harmonics are:

l m Ylm(ϑ,φ)
0 0 12π
1 ±1 38πsin(ϑ)e±iφ
1 0 34πcos(ϑ)
2 ±2 1532πsin2(ϑ)e±2iφ
2 ±1 158πcos(ϑ)sin(ϑ)e±iφ
2 0 516π(3cos2(ϑ)1)

The graph below shows the spherical harmonics for l=0,1,2,3 and m=0,1,2,3.

Code
#
def plot_spherical_harmonic(l, m, ax):
    # Create meshgrid for theta and phi
    phi = np.linspace(0, 2*np.pi, 100)
    theta = np.linspace(0, np.pi, 100)
    phi, theta = np.meshgrid(phi, theta)

    # Calculate the spherical harmonic
    Y = sph_harm(m, l, phi, theta)

    # Convert to cartesian coordinates
    r = np.abs(Y)
    x = r * np.sin(theta) * np.cos(phi)
    y = r * np.sin(theta) * np.sin(phi)
    z = r * np.cos(theta)

    # Plot the surface
    surf = ax.plot_surface(x, y, z, cmap='seismic')
    ax.set_title(f'l={l}, m={m}', fontsize=6, pad=0)
    ax.set_axis_off()

# Create figure with subplots
fig = plt.figure(figsize=get_size(12, 16))

# Plot l=0
ax = fig.add_subplot(4, 4, 1, projection='3d')
plot_spherical_harmonic(0, 0, ax)

# Plot l=1
for m in range(2):
    ax = fig.add_subplot(4, 4, 5+m, projection='3d')
    plot_spherical_harmonic(1, m, ax)

# Plot l=2
for m in range(3):
    ax = fig.add_subplot(4, 4, 9+m, projection='3d')
    plot_spherical_harmonic(2, m, ax)

# Plot l=3
for m in range(4):
    ax = fig.add_subplot(4, 4, 13+m, projection='3d')
    plot_spherical_harmonic(3, m, ax)

plt.tight_layout()
plt.show()
Figure 1— Spherical harmonics for l=0,1,2,3 and m=0,1,2,3

Solving the Radial Part: R(r)

Having solved the angular parts using spherical harmonics, we now tackle the final piece - the radial function R(r) that describes how the wavefunction varies with distance from the nucleus. From our previous separation of variables, we found that C2 satisfies:

C2=l(l+1)=1R(r)ddr(r2dR(r)dr)+2m2(EV(r))r2.

Multiplying by R(r) and dividing by r2 gives us:

l(l+1)r2R(r)=1r2ddr(r2dR(r)dr)+2m2(EV(r))R(r),

Here, the quantum number l determines the angular momentum |L|=l(l+1). For the hydrogen atom, we use the Coulomb potential:

V(r)=Ze24πε0r

Where Z is the atomic number (Z=1 for hydrogen) and ε0 is the vacuum permittivity. We also use the reduced mass μ instead of the electron mass m to account for the finite nuclear mass:

1μ=1me+1mn

After some algebra, we get the radial equation:

0=d2R(r)dr2+2rdR(r)dr+(2μ2(E+Ze24πε0r)l(l+1)r2)R(r).

This is a second-order differential equation. For bound states (E<0), the physical nature of the problem suggests an exponential decay at large distances. We can understand this intuitively: as r, the potential energy approaches zero, so the Schrödinger equation approximately becomes:

d2Rdr22μE2R

This has solutions of the form e±κr where κ=2μE2. Since we require the wavefunction to be normalizable, we must choose the decaying solution. This motivates the ansatz:

R(r)=u(r)eκr,

where κ=2μE2. The exponential factor ensures the wavefunction goes to zero at infinity, as required for bound states. We also introduce:

a=μZe24πε02

This gives us a simpler equation for u(r):

0=d2u(r)dr2+2(1rκ)du(r)dr+(2aκrl(l+1)r2)u(r).

We can solve this using a power series:

u(r)=jbjrj.

Substituting this into our equation and comparing coefficients gives us a recursive formula:

bj=2bj1κjaj(j+1)l(l+1).

For the wavefunction to be normalizable, this series must terminate. If we let j=n1 be the last non-zero term:

j<n.

This condition, combined with the recursive formula, gives us:

a=nκ

From this, we can find the energy levels:

E=a22μn2=μe432π2ε022Z2n2=RyZ2n2=En.

This is the famous Bohr formula for the energy levels of hydrogen-like atoms! Here Ry is the Rydberg energy. The quantum number n must be a positive integer, explaining why atomic energy levels are quantized.

From the denominator of our recursive formula, we also get:

ljn1

This gives us an important constraint on the angular momentum quantum number:

ln1.

The complete radial function Rn,l(r) therefore depends on two quantum numbers:

  • n (principal quantum number)
  • l (angular momentum quantum number)

The first few normalized radial functions are shown in the table below (N=(Z/(a0n))3/2, x=Zr/(a0n), a0=4πε02/(μe2) is the Bohr radius):

n l Rn,l(r)
1 0 2Nex
2 0 2Nex/2(1x2)
2 1 132Nex/2x2
3 0 2Nex/3(12x3+2x227)
3 1 232Nex/3x3(2x3)
3 2 23102Nex/3(x3)2
Code
def R_nl(n, l, r):
    x = r # Simplified version without physical constants
    if n == 1 and l == 0:
        return 2 * np.exp(-x)  # Already normalized
    elif n == 2:
        if l == 0:
            return (1/np.sqrt(2)) * (2-x) * np.exp(-x/2)
        elif l == 1:
            return (1/np.sqrt(6)) * x * np.exp(-x/2)
    elif n == 3:
        if l == 0:
            return (2/np.sqrt(27)) * (6-6*x+x**2) * np.exp(-x/3)
        elif l == 1:
            return (8/(27*np.sqrt(6))) * x * (4-x) * np.exp(-x/3)
        elif l == 2:
            return (4/(81*np.sqrt(30))) * x**2 * np.exp(-x/3)
    return 0

r = np.linspace(0, 40, 1000)

fig, ((ax1, ax1p), (ax2, ax2p), (ax3, ax3p)) = plt.subplots(3, 2, figsize=get_size(10,12))

# n = 1
ax1.plot(r, R_nl(1,0,r), label='1s (l=0)')
ax1.set_title('n = 1')
ax1.legend()
ax1p.plot(r, R_nl(1,0,r)**2, label='1s (l=0)')
ax1p.set_title('n = 1 probability density')
ax1p.set_ylim(0,2)
ax1p.set_xlim(0,20)

# n = 2
ax2.plot(r, R_nl(2,0,r), label='2s (l=0)')
ax2.plot(r, R_nl(2,1,r), label='2p (l=1)')
ax2.set_title('n = 2')
ax2.legend()
ax2p.plot(r, R_nl(2,0,r)**2, label='2s (l=0)')
ax2p.plot(r, R_nl(2,1,r)**2, label='2p (l=1)')
ax2p.set_title('n = 2 probability density')
ax2p.set_ylim(0,2)
ax2p.set_xlim(0,20)


# n = 3
ax3.plot(r, R_nl(3,0,r), label='3s (l=0)')
ax3.plot(r, R_nl(3,1,r), label='3p (l=1)')
ax3.plot(r, R_nl(3,2,r), label='3d (l=2)')
ax3.set_title('n = 3')
ax3.legend()
ax3p.plot(r, R_nl(3,0,r)**2, label='3s (l=0)')
ax3p.plot(r, R_nl(3,1,r)**2, label='3p (l=1)')
ax3p.plot(r, R_nl(3,2,r)**2, label='3d (l=2)')
ax3p.set_title('n = 3 probability density')
ax3p.set_ylim(-0.1,2)
ax3p.set_xlim(0,20)


ax3.set_xlabel(r'$r/a_0$')
ax3p.set_xlabel(r'$r/a_0$')
fig.supylabel(r'$R(r)$ / $|R(r)|^2$')

plt.tight_layout()
plt.show()
Figure 2— Radial wavefunctions R(r) for n=1,2,3

A key result is that the energy depends only on n, not on l or m. For each l value, there are 2l+1 degenerate states (different m values with the same energy). The total number of states for a given n is:

l=0n1(2l+1)=n2

giving us n2 distinct wavefunctions:

ψn,l,m(r,ϑ,φ)=Rn,l(r)Ylm(ϑ,φ).

Code
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from scipy.special import sph_harm
import random

def hydrogen_wavefunction(n, l, m, r, theta, phi):
    # Include radial part for n=1, l=0
    if n == 1 and l == 0:
        return (1/np.sqrt(np.pi)) * np.exp(-r) * sph_harm(m, l, phi, theta)
    return sph_harm(m, l, phi, theta)

# Create meshgrid
r = np.linspace(0, 10, 50)
theta = np.linspace(0, np.pi, 50)
phi = np.linspace(0, 2*np.pi, 50)

# Number of total points to generate and sample from
n_points_total = 200000
n_points_plot = 10000

# Create figure
fig = plt.figure(figsize=get_size(12, 8))

# Plot for different quantum states
for idx, (l, m) in enumerate([(0,0), (1,0), (1,1)]):
    ax = fig.add_subplot(2, 3, idx+1, projection='3d')

    # Generate more points than we'll plot
    r_points = np.random.uniform(0, 10, n_points_total)
    theta_points = np.random.uniform(0, np.pi, n_points_total)
    phi_points = np.random.uniform(0, 2*np.pi, n_points_total)

    # Calculate wavefunction
    psi = hydrogen_wavefunction(1, l, m, r_points, theta_points, phi_points)
    probability = np.abs(psi)**2

    # Sample points according to probability
    prob_normalized = probability / np.sum(probability)
    indices = np.random.choice(n_points_total, n_points_plot, p=prob_normalized)

    r_sampled = r_points[indices]
    theta_sampled = theta_points[indices]
    phi_sampled = phi_points[indices]
    probability_sampled = probability[indices]

    # Convert to Cartesian coordinates
    x = r_sampled * np.sin(theta_sampled) * np.cos(phi_sampled)
    y = r_sampled * np.sin(theta_sampled) * np.sin(phi_sampled)
    z = r_sampled * np.cos(theta_sampled)

    # Plot points with probability-based transparency
    scatter = ax.scatter(x, y, z, c='blue',
                        alpha=0.04, s=2, edgecolors='none')

    ax.set_title(f'l={l}, m={m}')
    ax.set_axis_off()

# Add second row with l=2
for idx, m in enumerate([0, 1, 2]):
    ax = fig.add_subplot(2, 3, idx+4, projection='3d')

    # Generate more points than we'll plot
    r_points = np.random.uniform(0, 10, n_points_total)
    theta_points = np.random.uniform(0, np.pi, n_points_total)
    phi_points = np.random.uniform(0, 2*np.pi, n_points_total)

    # Calculate wavefunction
    psi = hydrogen_wavefunction(1, 2, m, r_points, theta_points, phi_points)
    probability = np.abs(psi)**2

    # Sample points according to probability
    prob_normalized = probability / np.sum(probability)
    indices = np.random.choice(n_points_total, n_points_plot, p=prob_normalized)

    r_sampled = r_points[indices]
    theta_sampled = theta_points[indices]
    phi_sampled = phi_points[indices]
    probability_sampled = probability[indices]

    # Convert to Cartesian coordinates
    x = r_sampled * np.sin(theta_sampled) * np.cos(phi_sampled)
    y = r_sampled * np.sin(theta_sampled) * np.sin(phi_sampled)
    z = r_sampled * np.cos(theta_sampled)

    # Plot points with probability-based transparency
    scatter = ax.scatter(x, y, z, c='blue',
                        alpha=0.1,s=2,edgecolors='none')

    ax.set_title(f'l=2, m={m}')
    ax.set_axis_off()

plt.tight_layout()
plt.show()

Hydrogen wavefunctions for n=1, l=0,1,2 and m=0,1,2