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:
It is the simplest atomic system and can be solved analytically
More complex atoms can be understood as perturbations of the hydrogen atom
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:
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:
This separation ansatz reflects the spherical symmetry of the problem. Each function will describe a different aspect of the electron’s behavior: - 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:
For mathematical convenience, we multiply by :
We can now separate the -dependent terms:
This equation has a remarkable property: the left side depends only on , while the right side depends only on and . For this equality to hold for all values of the coordinates, both sides must equal a separation constant, which we’ll call .
The Azimuthal Equation
The equation for is:
This is a simple harmonic oscillator equation with solution:
Because the wavefunction must be single-valued when changes by :
This gives us our first quantum number m, which represents the z-component of angular momentum. The normalized solution is:
These functions are orthonormal:
The Polar Angle Equation
For the -dependent part, we get:
Dividing by and rearranging:
Again, the left side depends only on while the right side depends only on r, introducing another separation constant . For the equation:
When , using , this becomes the Legendre equation:
The solutions are Legendre polynomials:
With the recursion relation:
For finite solutions, we need where is an integer . This gives us our second quantum number , which represents the total angular momentum.
For , we get associated Legendre polynomials:
With the condition and normalization:
Spherical Harmonics
The angular part of the solution combines and into spherical harmonics:
These are normalized:
The first few spherical harmonics are:
0
0
1
1
0
2
2
2
0
The graph below shows the spherical harmonics for and .
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 subplotsfig = plt.figure(figsize=get_size(12, 16))# Plot l=0ax = fig.add_subplot(4, 4, 1, projection='3d')plot_spherical_harmonic(0, 0, ax)# Plot l=1for m inrange(2): ax = fig.add_subplot(4, 4, 5+m, projection='3d') plot_spherical_harmonic(1, m, ax)# Plot l=2for m inrange(3): ax = fig.add_subplot(4, 4, 9+m, projection='3d') plot_spherical_harmonic(2, m, ax)# Plot l=3for m inrange(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:
Having solved the angular parts using spherical harmonics, we now tackle the final piece - the radial function that describes how the wavefunction varies with distance from the nucleus. From our previous separation of variables, we found that satisfies:
Multiplying by and dividing by gives us:
Here, the quantum number determines the angular momentum . For the hydrogen atom, we use the Coulomb potential:
Where is the atomic number ( for hydrogen) and is the vacuum permittivity. We also use the reduced mass instead of the electron mass to account for the finite nuclear mass:
After some algebra, we get the radial equation:
This is a second-order differential equation. For bound states (), the physical nature of the problem suggests an exponential decay at large distances. We can understand this intuitively: as , the potential energy approaches zero, so the Schrödinger equation approximately becomes:
This has solutions of the form where . Since we require the wavefunction to be normalizable, we must choose the decaying solution. This motivates the ansatz:
where . The exponential factor ensures the wavefunction goes to zero at infinity, as required for bound states. We also introduce:
This gives us a simpler equation for :
We can solve this using a power series:
Substituting this into our equation and comparing coefficients gives us a recursive formula:
For the wavefunction to be normalizable, this series must terminate. If we let be the last non-zero term:
This condition, combined with the recursive formula, gives us:
From this, we can find the energy levels:
This is the famous Bohr formula for the energy levels of hydrogen-like atoms! Here is the Rydberg energy. The quantum number must be a positive integer, explaining why atomic energy levels are quantized.
From the denominator of our recursive formula, we also get:
This gives us an important constraint on the angular momentum quantum number:
The complete radial function therefore depends on two quantum numbers:
(principal quantum number)
(angular momentum quantum number)
The first few normalized radial functions are shown in the table below (, , is the Bohr radius):
1
0
2
0
2
1
3
0
3
1
3
2
Code
def R_nl(n, l, r): x = r # Simplified version without physical constantsif n ==1and l ==0:return2* np.exp(-x) # Already normalizedelif 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)return0r = np.linspace(0, 40, 1000)fig, ((ax1, ax1p), (ax2, ax2p), (ax3, ax3p)) = plt.subplots(3, 2, figsize=get_size(10,12))# n = 1ax1.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 = 2ax2.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 = 3ax3.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:
giving us distinct wavefunctions:
.
Code
import numpy as npimport matplotlib.pyplot as pltfrom mpl_toolkits.mplot3d import Axes3Dfrom scipy.special import sph_harmimport randomdef hydrogen_wavefunction(n, l, m, r, theta, phi):# Include radial part for n=1, l=0if n ==1and 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 meshgridr = 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 fromn_points_total =200000n_points_plot =10000# Create figurefig = plt.figure(figsize=get_size(12, 8))# Plot for different quantum statesfor idx, (l, m) inenumerate([(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=2for idx, m inenumerate([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