Classical rotation¶
Molecules tumble, and electrons circle nuclei. Lecture 4.1 found the rotational levels of HCl about 20 cm apart, in the microwave, and Chapter 5 names the atomic orbitals s, p, d after their angular momentum. Angular momentum is the quantity that organizes all of this.
The simplest rotation, and the model system of this lecture, is a particle moving on a circle. This section describes it classically and defines the quantities of rotation along the way; the next section treats the same particle quantum mechanically. Appendix B covers the classical side in more depth.
Kinematics on a circle¶
Press play or move the slider. Drag the picture to turn it, and look “from above” to see point straight out of the screen.
The particle goes counterclockwise around a circle of radius . The angle grows, and the particle covers the arc (teal). The angular velocity points along the axis, and at every instant is perpendicular to (the right-angle mark) and to , so it runs along the circle.
The angle is measured in radians, so the arc length is .
The angular velocity says how fast the angle grows. It is the same for every point of a rigid body.
The speed along the circle is : at the same , points farther from the axis move faster.
As vectors, the angular velocity points along the rotation axis, with length and direction set by the right-hand rule (fingers curl with the motion, the thumb gives ). The velocity is then a vector product, : perpendicular to both and , so tangent to the circle, with length .
Angular momentum is a vector product¶
Momentum measures straight-line motion. Its rotational partner is the angular momentum, built with the same vector product as , now of position and momentum:
Source
# synced: angular_momentum_vector
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from IPython.display import HTML
TEAL, CARDINAL, GRAY, PURPLE, ORANGE = "#107895", "#C8102E", "#6c757d", "#6a3d9a", "#e07b00"
from matplotlib.patches import Circle, FancyArrowPatch
fig, ax = plt.subplots(figsize=(6.0, 4.4))
k = 0.32 # the circle seen from above at an angle: an ellipse
t = np.linspace(0, 2 * np.pi, 300)
ax.plot(np.cos(t), k * np.sin(t), color=GRAY, lw=1.2, ls=":")
tp = np.radians(-38)
P = np.array([np.cos(tp), k * np.sin(tp)])
d = np.array([-np.sin(tp), k * np.cos(tp)]); d /= np.linalg.norm(d)
ax.add_patch(FancyArrowPatch((0, 0), P, arrowstyle="-|>", mutation_scale=16, color="k", lw=2, zorder=4))
ax.text(0.45 * P[0] + 0.02, 0.45 * P[1] - 0.1, r"$\vec{r}$", fontsize=18, va="top")
ax.add_patch(FancyArrowPatch(P, P + 0.6 * d, arrowstyle="-|>", mutation_scale=18, color=ORANGE, lw=2.6, zorder=4))
ax.text(*(P + 0.62 * d + np.array([0.04, -0.02])), r"$\vec{p} = \mu\vec{v}$", fontsize=17, color=ORANGE, va="center")
ax.add_patch(Circle(P, 0.06, fc=PURPLE, ec="k", lw=1, zorder=5))
ax.add_patch(FancyArrowPatch((0, 0), (0, 1.45), arrowstyle="-|>", mutation_scale=22, color=PURPLE, lw=3.4, zorder=4))
ax.text(0.09, 1.38, r"$\vec{L} = \vec{r}\times\vec{p}$", fontsize=18, color=PURPLE, va="center")
u = np.linspace(np.radians(200), np.radians(500), 120) # the right-hand curl, counterclockwise seen from above
ax.plot(0.26 * np.cos(u), 0.95 + 0.08 * np.sin(u), color=GRAY, lw=1.5)
ax.add_patch(FancyArrowPatch((0.26 * np.cos(u[-6]), 0.95 + 0.08 * np.sin(u[-6])), (0.26 * np.cos(u[-1]), 0.95 + 0.08 * np.sin(u[-1])),
arrowstyle="-|>", mutation_scale=12, color=GRAY, lw=1.5))
ax.text(-0.33, 0.95, "right\nhand", fontsize=12, color=GRAY, ha="right", va="center")
ax.set_xlim(-1.15, 2.0); ax.set_ylim(-0.48, 1.58); ax.set_aspect("equal"); ax.axis("off")
fig.subplots_adjust(left=0.01, right=0.99, top=0.99, bottom=0.01)
plt.show()
Fig. A particle moving counterclockwise on a circle, seen from above at an angle. is perpendicular to both and : it points along the rotation axis, by the right-hand rule.
is perpendicular to the plane of and , and its length is , with the angle between them.
On a circle and are perpendicular, and from the kinematics:
Kinetic energy in terms of ¶
The energy of the particle is all kinetic. Rewrite it step by step in the quantities of rotation:
This has the same form as for straight-line motion, with in place of the momentum and in place of the mass, and it carries straight into quantum mechanics.
Linear and angular motion: the analogy¶
Every quantity of straight-line motion has a rotational partner:
| straight line | rotation | |
|---|---|---|
| position | angle | |
| velocity | angular velocity | |
| inertia | mass | moment of inertia |
| momentum | angular momentum | |
| kinetic energy | ||
| changed by | force, | torque, |
| conserved when | no force acts | no torque acts |
| quantum operator | (next section) |
The mass is written , as for the reduced mass, and is the distance from the rotation axis. The letter is saved for a quantum number.
A model system: the rigid dumbbell¶
A rigid dumbbell, such as a diatomic molecule turning with a fixed bond length, is the particle on a circle in disguise:
Source
# synced: dumbbell_reduce
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from IPython.display import HTML
TEAL, CARDINAL, GRAY, PURPLE, ORANGE = "#107895", "#C8102E", "#6c757d", "#6a3d9a", "#e07b00"
from matplotlib.patches import Circle, FancyArrowPatch
mC, mO, R = 12.0, 16.0, 1.9
rC, rO = mO / (mC + mO) * R, mC / (mC + mO) * R # distances from the center of mass, m_C r_C = m_O r_O
t = np.linspace(0, 2 * np.pi, 300)
fig, (ax, bx) = plt.subplots(1, 2, figsize=(9.6, 4.0))
for a_ in (ax, bx):
a_.set_xlim(-2.5, 2.5); a_.set_ylim(-2.3, 2.4); a_.set_aspect("equal"); a_.axis("off")
a_.add_patch(FancyArrowPatch((2.2 * np.cos(np.radians(25)), 2.2 * np.sin(np.radians(25))),
(2.2 * np.cos(np.radians(70)), 2.2 * np.sin(np.radians(70))),
connectionstyle="arc3,rad=0.2", arrowstyle="-|>", mutation_scale=16, color=PURPLE, lw=1.8))
a_.text(1.9, 1.78, r"$\omega$", fontsize=15, color=PURPLE)
ax.plot(rC * np.cos(t), rC * np.sin(t), color="#495057", lw=1, ls=":")
ax.plot(rO * np.cos(t), rO * np.sin(t), color=CARDINAL, lw=1, ls=":")
ax.plot([-rC, rO], [0, 0], color="#adb5bd", lw=6, solid_capstyle="round", zorder=2)
ax.add_patch(Circle((-rC, 0), 0.27, fc="#495057", ec="k", lw=1, zorder=3))
ax.add_patch(Circle((rO, 0), 0.31, fc=CARDINAL, ec="k", lw=1, zorder=3))
ax.text(-rC, 0, "C", color="white", ha="center", va="center", fontsize=12, weight="bold", zorder=4)
ax.text(rO, 0, "O", color="white", ha="center", va="center", fontsize=12, weight="bold", zorder=4)
ax.plot([0], [0], "+", color="k", ms=13, mew=2.2, zorder=5)
ax.annotate("", xy=(-rC, -0.5), xytext=(0, -0.5), arrowprops=dict(arrowstyle="<->", color="#495057", lw=1.2))
ax.annotate("", xy=(rO, -0.5), xytext=(0, -0.5), arrowprops=dict(arrowstyle="<->", color=CARDINAL, lw=1.2))
ax.text(-rC / 2, -0.62, "$r_C$", ha="center", va="top", fontsize=13, color="#495057")
ax.text(rO / 2, -0.62, "$r_O$", ha="center", va="top", fontsize=13, color=CARDINAL)
ax.text(0, -2.1, r"$I = m_C r_C^2 + m_O r_O^2$", ha="center", fontsize=14)
bx.plot(R * np.cos(t), R * np.sin(t), color=PURPLE, lw=1.2, ls=":")
bx.plot([0, R * np.cos(np.radians(30))], [0, R * np.sin(np.radians(30))], color="k", lw=1.6)
bx.add_patch(Circle((R * np.cos(np.radians(30)), R * np.sin(np.radians(30))), 0.27, fc=PURPLE, ec="k", lw=1, zorder=3))
bx.text(R * np.cos(np.radians(30)), R * np.sin(np.radians(30)), r"$\mu$", color="white", ha="center", va="center",
fontsize=14, zorder=4)
bx.plot([0], [0], "+", color="k", ms=13, mew=2.2)
bx.text(0.55 * np.cos(np.radians(30)) - 0.05, 0.55 * np.sin(np.radians(30)) + 0.12, "$r$", fontsize=14,
ha="right", va="bottom")
bx.text(0, -2.1, r"$I = \mu r^2$ with $\mu = m_Cm_O/(m_C + m_O)$, $r = r_C + r_O$", ha="center", fontsize=13.5)
fig.text(0.5, 0.53, "=", ha="center", va="center", fontsize=30, color=GRAY)
fig.subplots_adjust(left=0.01, right=0.99, top=0.99, bottom=0.02, wspace=0.1)
plt.show()
Fig. Left: carbon monoxide as a rigid dumbbell, turning about its center of mass (+). Each atom moves on its own circle, with . Right: the same motion as one particle of reduced mass on a circle of radius , the bond length. Both have the same moment of inertia.
With and ,
the same reduced mass that turned a vibrating diatomic into one bead on a spring in Lecture 4.1. Everything in this lecture therefore applies to a rotating diatomic molecule, with ; in three dimensions (later) the dumbbell can also point in any direction.
No torque, no change: the spinning skater¶
Differentiate with the product rule. The first term is , since the vector product of parallel vectors vanishes, and Newton’s law turns into the force:
The torque changes angular momentum the way a force changes momentum. With no torque, stays fixed in size and direction. A force along , toward or away from the axis, exerts no torque, so pulling a mass inward cannot change . A spinning skater who pulls the arms in therefore spins faster:
Source
# synced: skater_spin
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from IPython.display import HTML
TEAL, CARDINAL, GRAY, PURPLE, ORANGE = "#107895", "#C8102E", "#6c757d", "#6a3d9a", "#e07b00"
from matplotlib.patches import Circle
nf = 44
I0, mm, rout, rin = 0.12, 0.5, 1.0, 0.36 # the body, each hand weight, arm lengths out and in
s = 0.5 - 0.5 * np.cos(2 * np.pi * np.arange(nf) / nf) # 0 -> 1 -> 0: arms out, in, out again
r = rout + (rin - rout) * s
I = I0 + 2 * mm * r ** 2
Lc = I[0] * 0.17 # fixed angular momentum; arms out turn 0.17 rad per frame
w = Lc / I # rad per frame
ang = np.cumsum(w) - w[0]
fig, (ax, bx) = plt.subplots(1, 2, figsize=(9.6, 4.2), gridspec_kw={"width_ratios": [1, 1.15], "wspace": 0.1})
ax.set_xlim(-1.3, 1.3); ax.set_ylim(-1.3, 1.3); ax.set_aspect("equal"); ax.axis("off")
ax.add_patch(Circle((0, 0), 0.22, fc="#adb5bd", ec="k", lw=1, zorder=3))
(arms,) = ax.plot([], [], color="#495057", lw=5, solid_capstyle="round", zorder=2)
hands = [ax.add_patch(Circle((0, 0), 0.11, fc=CARDINAL, ec="k", lw=1, zorder=4)) for _ in range(2)]
trails = [ax.plot([], [], color=CARDINAL, lw=4, alpha=0.3, solid_capstyle="round")[0] for _ in range(2)]
names = ["moment of inertia $I$", r"angular velocity $\omega$", r"angular momentum $L = I\omega$"]
cols = [ORANGE, TEAL, PURPLE]
vals = [I / I[0], w / w[0], I * w / (I[0] * w[0])]
bars = bx.barh(range(3)[::-1], [v[0] for v in vals], color=cols, height=0.42)
texts = [bx.text(0, 2 - j, "", va="center", fontsize=12.5, color=cols[j]) for j in range(3)]
for j, nm in enumerate(names):
bx.text(0.02, 2 - j + 0.36, nm, ha="left", va="bottom", fontsize=13, color=cols[j])
bx.axvline(1, color=GRAY, lw=0.8, ls=":")
bx.set_xlim(0, 5.6); bx.set_ylim(-0.45, 2.95); bx.set_yticks([]); bx.set_xticks([0, 1, 2, 3, 4, 5])
for sp in ("top", "right", "left"):
bx.spines[sp].set_visible(False)
bx.set_xlabel("relative to the arms-out value", fontsize=11.5)
fig.subplots_adjust(left=0.01, right=0.98, top=0.98, bottom=0.14)
def update(i):
c, sn = np.cos(ang[i]), np.sin(ang[i])
arms.set_data([-r[i] * c, r[i] * c], [-r[i] * sn, r[i] * sn])
for k_, sg in enumerate((1, -1)):
hands[k_].center = (sg * r[i] * c, sg * r[i] * sn)
sweep = np.linspace(ang[i] - 1.6 * w[i], ang[i], 30) # the angle swept just before: a streak as long as the speed
trails[k_].set_data(sg * r[i] * np.cos(sweep), sg * r[i] * np.sin(sweep))
for j, (bar, v) in enumerate(zip(bars, vals)):
bar.set_width(v[i])
texts[j].set_position((v[i] + 0.08, 2 - j)); texts[j].set_text(f"{v[i]:.2f}")
ani = FuncAnimation(fig, update, frames=nf, interval=110, blit=False)
plt.close(fig)
HTML(ani.to_jshtml())Fig. A spinning skater seen from above: a body with a weight in each hand. As the arms come in, the moment of inertia drops (orange). must stay the same (purple), so the angular velocity rises by the same factor (teal).
The same rule turns a diver’s tuck into a fast somersault, a collapsing star into a rapidly spinning pulsar, and keeps a molecule tumbling at a steady rate between collisions: nothing changes unless something twists.
Conservation from symmetry: Noether’s theorem¶

Fig. Emmy Noether (1882 to 1935) proved that every continuous symmetry of the laws of physics comes with a conserved quantity.
Why is angular momentum conserved at all? Noether’s theorem answers with symmetry:
| if nothing changes when you | then this is conserved |
|---|---|
| shift the system in space | momentum |
| turn the system about an axis | angular momentum |
| shift the clock | energy |
A free molecule, or an electron pulled toward a nucleus, looks the same after any turn about the center: its potential energy depends only on the distance. So its angular momentum is conserved. In quantum language, Lecture 3.7 showed that an observable is a constant of motion when its operator commutes with . For these systems commutes with , and angular momentum quantum numbers can label the stationary states.
A particle on a ring¶
Now the same particle on a circle, of mass and radius with no potential energy, treated quantum mechanically. Real examples are the electrons that circulate around benzene (below), a methyl group spinning about its CC bond (Problem 1), or a diatomic turning in a plane.
The operator¶
The arc length along the ring is . Momentum along the ring is , and the angular momentum about the axis is times it:
The energy is all kinetic, , which with becomes
is the momentum operator with the angle in place of , and plays the part of the mass.
is built from alone, so the two commute and share eigenfunctions (Operators 2).
The wave must join up¶
The eigenvalue equation reads , and its solution is
On an infinite line any value of would do, as any momentum does for a free particle. On a ring, and are the same point, so after one full turn the wavefunction must return to its starting value:
By Euler’s formula (Appendix A.2), equals 1 exactly when is a whole number.
Source
# synced: ring_waves
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from IPython.display import HTML
TEAL, CARDINAL, GRAY, PURPLE, ORANGE = "#107895", "#C8102E", "#6c757d", "#6a3d9a", "#e07b00"
phi = np.linspace(0, 2 * np.pi, 721)
cases = [(0, "constant"), (1, "one wavelength"), (2, "two wavelengths"), (3, "three wavelengths"),
(2.5, "the ends miss")]
fig, axes = plt.subplots(1, 5, figsize=(9.6, 2.35))
for ax, (m, note) in zip(axes, cases):
col = TEAL if float(m).is_integer() else CARDINAL
ax.plot(np.cos(phi), np.sin(phi), color=GRAY, lw=1.2, ls=":")
r = 1 + 0.3 * np.cos(m * phi) # the real part of e^{im phi}, drawn out from the ring
ax.plot(r * np.cos(phi), r * np.sin(phi), color=col, lw=2.6)
ax.plot([r[0]], [0], "o", color=col, ms=6.5, zorder=4) # the start, phi = 0
if col == CARDINAL: # after one turn the wave is back at phi = 0, but at r = 0.7
ax.plot([r[-1]], [0], "o", color=CARDINAL, ms=6.5, mfc="white", mew=2, zorder=4)
ax.annotate("", xy=(r[0] - 0.03, -0.32), xytext=(r[-1] + 0.03, -0.32),
arrowprops=dict(arrowstyle="<->", color=CARDINAL, lw=1.4, shrinkA=0, shrinkB=0))
ax.set_xlim(-1.42, 1.42); ax.set_ylim(-1.42, 1.42); ax.set_aspect("equal"); ax.axis("off")
ax.set_title(rf"$m = {m:g}$", fontsize=14, color=col)
fig.subplots_adjust(left=0.01, right=0.99, top=0.86, bottom=0.01, wspace=0.06)
plt.show()
Fig. The real part of drawn around the ring (dotted). For a whole number of wavelengths fits, and the wave joins itself at the dot where it started. For the wave comes back to at the open circle, away from its start: the function would have two values at one point, so is not allowed.
The quantization comes from no wall and no potential, only from the ring closing on itself. Normalizing on the ring, , gives the factor . Each is an eigenfunction of as well:
Pairs, and no zero-point energy¶
Cut the ring open and straighten it: a box of length (Lecture 3.2). In the box, half wavelengths fit between the walls; on the ring, a whole number of wavelengths must fit around. So the ring keeps only the box states with even , and gains one that the box cannot have:
Source
# synced: ring_ladder
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from IPython.display import HTML
TEAL, CARDINAL, GRAY, PURPLE, ORANGE = "#107895", "#C8102E", "#6c757d", "#6a3d9a", "#e07b00"
plt.rcParams.update({"axes.spines.top": False, "axes.spines.right": False})
fig, ax = plt.subplots(figsize=(9.6, 4.3))
b0, b1 = 0.6, 2.9 # box column
r0, r1 = 6.3, 9.0 # ring column: +m on the left half, -m on the right
rc = 0.5 * (r0 + r1)
for n in range(1, 5): # box: E_n = n^2/4 in units of hbar^2/2I
E, even = n * n / 4, n % 2 == 0
ax.hlines(E, b0, b1, color=GRAY, lw=2.6 if even else 1.6, ls="-" if even else (0, (4, 3)),
alpha=1 if even else 0.65)
ax.text(b0 - 0.12, E, rf"$n = {n}$", ha="right", va="center", fontsize=12, color=GRAY)
if even: # the same wavelength fits around the ring
ax.plot([b1 + 0.1, r0 - 0.1], [E, E], color=PURPLE, lw=1.1, ls=":", alpha=0.7)
for m in range(3): # ring: E_m = m^2 in units of hbar^2/2I
E = m * m
if m == 0:
ax.hlines(E, r0, r1, color=PURPLE, lw=2.8)
ax.text(r1 + 0.12, E, r"$m = 0$", va="center", fontsize=12, color=PURPLE)
else:
ax.hlines(E, r0, rc - 0.1, color=PURPLE, lw=2.8)
ax.hlines(E, rc + 0.1, r1, color=PURPLE, lw=2.8)
ax.text(r1 + 0.12, E, rf"$m = \pm {m}$", va="center", fontsize=12, color=PURPLE)
ax.text(0.5 * (r0 + rc), E + 0.11, rf"$+{m}$ ↺", ha="center", va="bottom", fontsize=11.5, color=PURPLE)
ax.text(0.5 * (rc + r1), E + 0.11, rf"$-{m}$ ↻", ha="center", va="bottom", fontsize=11.5, color=PURPLE)
ax.text(0.5 * (b0 + b1), 4.65, r"box of length $2\pi r$", ha="center", fontsize=13, color=GRAY, weight="bold")
ax.text(0.5 * (r0 + r1), 4.65, r"ring of radius $r$", ha="center", fontsize=13, color=PURPLE, weight="bold")
ax.set_xlim(-0.6, 10.4); ax.set_ylim(-0.2, 5.0)
ax.set_xticks([]); ax.spines["bottom"].set_visible(False)
ax.set_yticks([0, 0.25, 1, 2.25, 4]); ax.set_yticklabels(["0", "1/4", "1", "9/4", "4"]); ax.tick_params(labelsize=11)
ax.spines["left"].set_bounds(0, 4.25)
ax.set_ylabel(r"energy (units of $\hbar^2/2I$)", fontsize=12)
fig.subplots_adjust(left=0.08, right=0.99, top=0.98, bottom=0.03)
plt.show()
Fig. Energy levels of a ring of radius (purple) and of the box of length made by cutting it open (gray), in units of . The box has , the ring . The ring keeps the even box levels, each twice: runs counterclockwise (↺) and clockwise (↻). Its lowest level, , has no energy at all.
Pairs. and have the same energy: the same speed, in opposite directions. Every level above the lowest is doubly degenerate.
No zero-point energy. is flat. It has no curvature, so no kinetic energy. The box needs because its walls force at both ends; the ring has no ends. There is no conflict with the uncertainty principle: in the position on the ring is completely unknown.
Widening gaps. The levels grow as , like those of the box.
The same comparison with matrices. In Lecture 3.5 the box became a matrix with the curvature row on every line. On a ring the last point’s neighbor is the first point, so gluing the ends changes just two corner entries:
import numpy as np
N = 400 # grid points around the ring
dphi = 2 * np.pi / N # grid spacing in angle
D2 = np.eye(N, k=1) - 2 * np.eye(N) + np.eye(N, k=-1)
box = -D2[1:, 1:] / dphi**2 # H = -(hbar^2/2I) d^2/dphi^2 in units of hbar^2/2I; walls at 0 and 2 pi
D2[0, -1] = D2[-1, 0] = 1 # glue the ends: the first and last points become neighbors
ring = -D2 / dphi**2
print("box: ", np.round(np.linalg.eigvalsh(box)[:6], 3) + 0)
print("ring:", np.round(np.linalg.eigvalsh(ring)[:7], 3) + 0)box: [0.25 1. 2.25 4. 6.249 8.998]
ring: [0. 1. 1. 4. 4. 8.998 8.998]
The box gives . The ring gives : a level at 0, then 1, 4 and 9 twice each. These are the even box levels, doubled, and a new level at zero (up to the grid error in the last digit).
Running and standing waves¶
The density of a ring state is the same everywhere, . A state with sharp is spread evenly around the ring, while its phase winds times around it. With its time factor, is a wave running around the ring: counterclockwise for , clockwise for .
The states and have the same energy, and that is the physical point: any linear superposition of them is again a state of that energy. Take the simplest one,
(by Euler’s formula, ). Because the energies match, the superposition is still a stationary state, and its density never changes. That is unlike a superposition of different energies, whose density moves (Lecture 3.7). It is a standing wave with nodes, like a state of the box. It is an eigenfunction of but not of : a measurement of gives or , each with probability (Lecture 3.6), and . The free particle showed the same pair, against , in Operators 2.
Source
# synced: ring_flow
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from IPython.display import HTML
TEAL, CARDINAL, GRAY, PURPLE, ORANGE = "#107895", "#C8102E", "#6c757d", "#6a3d9a", "#e07b00"
from matplotlib.patches import FancyArrowPatch, Patch
from matplotlib.lines import Line2D
nf = 36
phi = np.linspace(0, 2 * np.pi, 361)
cases = [(2, r"$e^{+2i\phi}$"), (-2, r"$e^{-2i\phi}$"), (0, r"$2\cos 2\phi$")]
fig, axes = plt.subplots(1, 3, figsize=(9.6, 3.4), gridspec_kw={"wspace": 0.32})
parts = []
for ax, (m, title) in zip(axes, cases):
ax.set_xlim(-1.62, 1.62); ax.set_ylim(-1.62, 1.62); ax.set_aspect("equal"); ax.axis("off")
ax.set_title(title, fontsize=17)
dens = np.ones_like(phi) if m else 2 * np.cos(2 * phi) ** 2 # |Psi|^2 in units of its average
band = 0.1 * dens
ax.fill(np.r_[(1 + band) * np.cos(phi), ((1 - band) * np.cos(phi))[::-1]],
np.r_[(1 + band) * np.sin(phi), ((1 - band) * np.sin(phi))[::-1]], color=TEAL, alpha=0.25, lw=0)
ax.plot(np.cos(phi), np.sin(phi), color=GRAY, lw=0.8, ls=":")
(wave,) = ax.plot([], [], color=TEAL, lw=2.4)
if m:
a0, a1 = np.radians(55), np.radians(125)
if m < 0:
a0, a1 = a1, a0
ax.add_patch(FancyArrowPatch((1.45 * np.cos(a0), 1.45 * np.sin(a0)), (1.45 * np.cos(a1), 1.45 * np.sin(a1)),
connectionstyle=f"arc3,rad={0.22 if m > 0 else -0.22}", arrowstyle="-|>",
mutation_scale=18, color=PURPLE, lw=2))
parts.append((m, wave))
fig.subplots_adjust(left=0.01, right=0.99, top=0.88, bottom=0.13)
fig.legend(handles=[Line2D([0], [0], color=TEAL, lw=2.4, label=r"real part of $\Psi$"),
Patch(facecolor=TEAL, alpha=0.25, label=r"probability density $|\Psi|^2$")],
loc="lower center", ncol=2, frameon=False, fontsize=13, bbox_to_anchor=(0.5, 0.0), columnspacing=3)
for k, sign in ((0, "+"), (1, "=")): # running + running = standing
x = 0.5 * (axes[k].get_position().x1 + axes[k + 1].get_position().x0)
fig.text(x, 0.45, sign, ha="center", va="center", fontsize=34, color=GRAY)
def update(i):
wt = 2 * np.pi * i / nf # E t / hbar over one period of the phase
for m, wave in parts:
if m: # the real part of e^{i(m phi - w t)}
re, amp = np.cos(m * phi - wt), 0.25
else: # the sum of the two running waves
re, amp = np.cos(2 * phi - wt) + np.cos(-2 * phi - wt), 0.25
r = 1 + amp * re
wave.set_data(r * np.cos(phi), r * np.sin(phi))
ani = FuncAnimation(fig, update, frames=nf, interval=90, blit=False)
plt.close(fig)
HTML(ani.to_jshtml())Fig. Two running waves add up to a standing wave. The line is the real part of the wavefunction, drawn as bumps around the ring; the shaded band is the probability density . Left and middle: and run counterclockwise and clockwise, and their density is even all around and never changes. Right: their sum, , stands still; its density drops to zero at four nodes.
Classical and quantum, side by side¶
| classical particle on a circle | quantum particle on a ring | |
|---|---|---|
| angular momentum | any value, | only , |
| energy | any value, | only |
| where it is | a definite angle, | with sharp , anywhere: |
| lowest energy | 0, at rest at a known angle | 0, with the angle completely unknown |
| two directions | same energy at the same speed | the pair |
The quantum steps are tiny for heavy or large rotors. The methyl group of Problem 1 has 13 levels below at room temperature, and a spinning wheel has : its levels are far too close to resolve, and it behaves classically.
Angular momentum in three dimensions¶
Spherical coordinates¶
Rotation in space is easiest to describe with a distance and two angles: the polar angle , measured down from the axis, and the azimuthal angle , measured around it as on the ring.

Fig. Spherical coordinates. A small patch of the sphere is tall and wide.
The patch in the figure has area . On a sphere of radius 1 it is the surface element , used by every integral over angles below. The factor shrinks the patches near the poles, where the circles of latitude are short.
Three components, three operators¶
Classically each component of uses the two coordinates perpendicular to its own axis: , and . Replacing each momentum by times a derivative gives the operators:
is the ring operator in disguise. Turning a point about changes only . By the chain rule, with and ,
So measures how a function changes when it is turned a little about the axis, and , do the same for turns about and . The ring had a single axis. Space has three, and turns about different axes get in each other’s way.
Turns about different axes do not commute¶
Give a book a quarter turn about , then a quarter turn about . Start again and make the same two turns in the other order. The book ends up facing a different way:
Source
# synced: rotation_order
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from IPython.display import HTML
TEAL, CARDINAL, GRAY, PURPLE, ORANGE = "#107895", "#C8102E", "#6c757d", "#6a3d9a", "#e07b00"
from matplotlib.patches import Polygon
from matplotlib.colors import to_rgb
nf = 42
el, az = np.radians(24), np.radians(35)
cam = np.array([np.cos(el) * np.cos(az), np.cos(el) * np.sin(az), np.sin(el)]) # unit vector toward the viewer
ex = np.array([-np.sin(az), np.cos(az), 0.0]) # screen right
ey = np.cross(cam, ex) # screen up
to2d = lambda p: np.c_[p @ ex, p @ ey]
Rx = lambda a: np.array([[1, 0, 0], [0, np.cos(a), -np.sin(a)], [0, np.sin(a), np.cos(a)]])
Ry = lambda a: np.array([[np.cos(a), 0, np.sin(a)], [0, 1, 0], [-np.sin(a), 0, np.cos(a)]])
a, b, c = 0.5, 0.72, 0.13 # half sizes of the book: width, height, thickness
corners = np.array([[sx * a, sy * b, sz * c] for sx in (-1, 1) for sy in (-1, 1) for sz in (-1, 1)])
PAGES = "#f3ead8"
faces = [((0, 0, 1), [1, 5, 7, 3], TEAL), ((0, 0, -1), [0, 2, 6, 4], "#adb5bd"), ((-1, 0, 0), [0, 1, 3, 2], PURPLE),
((1, 0, 0), [4, 6, 7, 5], PAGES), ((0, 1, 0), [2, 3, 7, 6], PAGES), ((0, -1, 0), [0, 4, 5, 1], PAGES)]
arrow = np.array([[-0.07, -0.45], [0.07, -0.45], [0.07, 0.16], [0.2, 0.16], [0, 0.5], [-0.2, 0.16], [-0.07, 0.16]])
arrow = np.c_[arrow, np.full(len(arrow), c + 0.004)] # an arrow printed on the cover, pointing to the top edge
ease = lambda s: np.clip(s, 0, 1) ** 2 * (3 - 2 * np.clip(s, 0, 1))
s1 = lambda k: ease((k - 3) / 12) # first quarter turn over frames 4 to 15
s2 = lambda k: ease((k - 19) / 12) # second quarter turn over frames 20 to 31
orders = [("x", "y", lambda k: Ry(np.pi / 2 * s2(k)) @ Rx(-np.pi / 2 * s1(k))),
("y", "x", lambda k: Rx(-np.pi / 2 * s2(k)) @ Ry(np.pi / 2 * s1(k)))]
unit = {"x": np.array([1.0, 0, 0]), "y": np.array([0, 1.0, 0]), "z": np.array([0, 0, 1.0])}
fig, axes = plt.subplots(1, 2, figsize=(9.6, 4.1))
parts = []
for ax, (first, second, R) in zip(axes, orders):
ax.set_xlim(-1.3, 1.3); ax.set_ylim(-1.18, 1.12); ax.set_aspect("equal"); ax.axis("off")
ax.set_title(rf"turn about ${first}$, then about ${second}$", fontsize=14)
polys = [ax.add_patch(Polygon(np.zeros((4, 2)), closed=True, fc=col, ec="#495057", lw=1.2, zorder=2))
for _, _, col in faces]
mark = ax.add_patch(Polygon(np.zeros((7, 2)), closed=True, fc="white", ec="none", alpha=0.95, zorder=3))
(axis_line,) = ax.plot([], [], color=CARDINAL, lw=1.8, ls=(0, (5, 3)), zorder=4)
axis_lab = ax.text(0, 0, "", color=CARDINAL, fontsize=15, weight="bold", ha="center", va="center", zorder=4)
o = np.array([-1.05, -0.95]) # a fixed triad of axes in the corner
triad = {}
for name, v in unit.items():
d = 0.28 * to2d(v[None])[0]
ln = ax.annotate("", xy=o + d, xytext=o, arrowprops=dict(arrowstyle="-|>", color=GRAY, lw=1.6,
mutation_scale=12, shrinkA=0, shrinkB=0))
tx = ax.text(*(o + 1.38 * d), name, color=GRAY, fontsize=12, ha="center", va="center")
triad[name] = (ln, tx)
step = None
parts.append((first, second, R, polys, mark, axis_line, axis_lab, triad, step))
fig.subplots_adjust(left=0.01, right=0.99, top=0.91, bottom=0.01, wspace=0.04)
def update(k):
for first, second, R, polys, mark, axis_line, axis_lab, triad, step in parts:
M = R(k)
pts = corners @ M.T
for poly, (n, idx, col) in zip(polys, faces):
nv = M @ np.array(n, float)
poly.set_visible(nv @ cam > 1e-6) # a convex box: draw only the faces turned to us
poly.set_xy(to2d(pts[idx]))
if poly.get_visible():
rgb = np.array(to_rgb(col))
poly.set_facecolor(rgb * (0.78 + 0.22 * (nv @ cam)))
cover = M @ np.array([0, 0, 1.0])
mark.set_visible(cover @ cam > 1e-6)
mark.set_xy(to2d(arrow @ M.T))
now = None if k >= 32 else (first if k < 18 else second)
for name, (ln, tx) in triad.items():
on = name == now
ln.arrow_patch.set_color(CARDINAL if on else GRAY)
tx.set_color(CARDINAL if on else GRAY); tx.set_fontweight("bold" if on else "normal")
if now is None:
axis_line.set_data([], []); axis_lab.set_text("")
else:
ends = to2d(np.array([-1.08, 1.08])[:, None] * unit[now])
axis_line.set_data(ends[:, 0], ends[:, 1])
axis_lab.set_position(1.12 * ends[1]); axis_lab.set_text(now)
ani = FuncAnimation(fig, update, frames=nf, interval=85, blit=False)
plt.close(fig)
HTML(ani.to_jshtml())Fig. A book given the same two quarter turns in opposite orders. Left: about (dashed red line), then about . Right: about , then about . The axes stay fixed in space (corner triad). The two books end facing different directions, with the arrow on the cover pointing different ways.
Each component of measures the effect of a small turn about its axis, so if the turns do not commute, neither can the components. The flip and swap matrices of Operators 2 showed the same thing for mirrors. Turns add one more feature: two small turns, about and then about , differ from the opposite order by a small extra turn about . That is why the commutator of and comes out proportional to .
The commutators¶
The commutator follows from . Expand into four commutators. A coordinate and the momentum along a different axis commute, as do two coordinates or two momenta; only and fail to commute. So the two terms without both and , and , vanish, and in the other two the remaining factors slide out:
Cycling the labels gives the other two.
The second line says the length commutes with every component:
Why commutes with
Use the product rule (Problem 5 of Operators 2) on each square:
and . The three add up to zero, so . The same steps work for and .
A concrete check: acting on , and ¶
Going further: angular momentum as matrices
Any combination stays a combination of the three, so acts on the column of coefficients as a matrix, as operators did in Lecture 3.5. Column holds what makes of the -th function, and , follow the same way:
The eigenvalues of are , and 0, with eigenvectors and : the functions and of the example. numpy checks the commutator and the eigenvalues of all three:
import numpy as np
Lx = np.array([[0, 0, 0], [0, 0, -1j], [0, 1j, 0]]) # in units of hbar
Ly = np.array([[0, 0, 1j], [0, 0, 0], [-1j, 0, 0]])
Lz = np.array([[0, -1j, 0], [1j, 0, 0], [0, 0, 0]])
print(np.allclose(Lx @ Ly - Ly @ Lx, 1j * Lz)) # True: [Lx, Ly] = i Lz
print((Lx @ Lx + Ly @ Ly + Lz @ Lz).real) # 2 times the identity: L^2 = 2 hbar^2
print(np.linalg.eigvalsh(Lx), np.linalg.eigvalsh(Lz)) # [-1, 0, 1] for bothThe commutator holds as a matrix identity, entry by entry.
is times the identity: all three functions have , the value of .
Every component has the same three eigenvalues, , 0 and : no axis is special. But the eigenvectors differ from axis to axis, so no function here is an eigenfunction of two components at once.
Only one component at a time¶
Two components cannot both be sharp. By Operators 2, sharp values of and in one state would need , so as well, and cycling the argument makes all three components zero. Only a state with no rotation at all has sharp components. For any other state the uncertainty principle of Lecture 3.6 bounds the spreads, .
The length and one component can. commutes with every component, so it shares eigenfunctions with any one of them. By convention that one is . The axis can be any direction you choose: along a magnetic field, along the polarization of a laser, or along a molecule’s own axis.
Contrast linear momentum: , and commute, and a plane wave has all three sharp. For rotation you can know the length of and one projection, never its full direction.
A particle on a sphere: the spherical harmonics¶
and the Hamiltonian¶
Written in spherical coordinates (a long application of the chain rule), the squared length is
It acts on the angles only; the distance never appears. It is the angular part of the Laplacian,
so the kinetic energy of any particle splits into a radial part and a rotational part, . Chapter 5 uses this split for the hydrogen atom.
A particle of mass held on a sphere of radius , with no potential, keeps only the rotational part:
This is the rigid rotor, a diatomic molecule turning with a fixed bond length (Lecture 4.5). Its eigenfunctions are those of .
The solution¶
Separate the angles, . The angle enters only through , so the part is the ring all over again: with whole . What remains is an equation for . Its solutions stay finite at both poles only when the eigenvalue of is , with a whole number no smaller than . Otherwise they blow up at or , much as the oscillator’s series blew up at large unless it stopped (Lecture 4.2). The finite solutions are polynomials in and (associated Legendre functions).
Chemists know by letters: s, p, d, f for . is often written and called the magnetic quantum number, because a magnetic field along separates states of different (Spin). For a rotating molecule, spectroscopists write and .
| in | ||||
|---|---|---|---|---|
| 0 | 0 | s | ||
| 1 | 0 | p | ||
| 1 | p | |||
| 2 | 0 | d | ||
| 2 | d | |||
| 2 | d |
Every is times a function of : a ring state wrapped around the axis.
In the functions are and , the eigenfunctions of the example above. The functions are products of two of them: is a polynomial of degree in , and .
The signs follow the usual convention (Condon and Shortley’s). Some books leave them out; they change no probability.
Normalized and orthogonal on the sphere¶
Two states with different are orthogonal through the integral alone: for . States with the same and different cancel in the integral. Both are guaranteed because and are Hermitian (Operators 2).
numpy integrates every pair with on a grid of angles:
import numpy as np
from scipy.special import sph_harm_y # sph_harm_y(l, m, theta, phi)
n = 200
theta = (np.arange(n) + 0.5) * np.pi / n # polar angle: midpoints of n strips
phi = np.arange(2 * n) * np.pi / n # azimuth: once around
TH, PH = np.meshgrid(theta, phi, indexing="ij")
dA = np.sin(TH) * (np.pi / n) ** 2 # surface element sin(theta) dtheta dphi
lm = [(l, m) for l in range(3) for m in range(-l, l + 1)] # the 9 states with l <= 2
Y = np.array([sph_harm_y(l, m, TH, PH).ravel() for l, m in lm])
S = Y.conj() @ (Y * dA.ravel()).T # S[a, b] = <a|b>
print(np.round(S.real, 3) + 0)[[1. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 1. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 1. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 1. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 1. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 1. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 1. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 1. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 1.]]
The overlap matrix is the identity: each has length 1 and is perpendicular to the other eight.
Pictures on the globe: nodal lines¶
A spherical harmonic is a wave on the surface of a sphere. Color each point by the sign of its real part:
Source
# synced: harmonic_globes
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from IPython.display import HTML
TEAL, CARDINAL, GRAY, PURPLE, ORANGE = "#107895", "#C8102E", "#6c757d", "#6a3d9a", "#e07b00"
from scipy.special import sph_harm_y
from matplotlib.colors import to_rgb
S = 261
u = np.linspace(-1, 1, S)
U, V = np.meshgrid(u, u[::-1]) # screen coordinates, V upward
inside = U ** 2 + V ** 2 < 1
W = np.sqrt(np.clip(1 - U ** 2 - V ** 2, 0, None)) # toward the viewer
el, az = np.radians(22), np.radians(-55) # the north pole tipped 22 degrees toward us
cam = np.array([np.cos(el) * np.cos(az), np.cos(el) * np.sin(az), np.sin(el)])
ex = np.array([-np.sin(az), np.cos(az), 0.0])
ey = np.cross(cam, ex)
P = U[..., None] * ex + V[..., None] * ey + W[..., None] * cam # the point of the sphere behind each pixel
TH, PH = np.arccos(np.clip(P[..., 2], -1, 1)), np.arctan2(P[..., 1], P[..., 0])
light = np.array([-0.45, 0.55, 0.7]) / np.linalg.norm([-0.45, 0.55, 0.7])
shade = 0.5 + 0.5 * np.clip(U * light[0] + V * light[1] + W * light[2], 0, 1)
white, pos, neg = np.ones(3), np.array(to_rgb(TEAL)), np.array(to_rgb(ORANGE))
th1 = np.linspace(0, np.pi, 721)
fig, axes = plt.subplots(4, 4, figsize=(9.6, 7.5))
for l in range(4):
for m in range(4):
ax = axes[l, m]
ax.set_xlim(-1.25, 1.25); ax.set_ylim(-1.15, 1.35); ax.set_aspect("equal"); ax.axis("off")
if m > l:
continue
top = np.abs(sph_harm_y(l, m, th1, 0 * th1).real).max() # the largest |Re Y| on the sphere
s = sph_harm_y(l, m, TH, PH).real / top # -1 .. 1
rgb = np.where(s[..., None] > 0, white + s[..., None] * (pos - white), white - s[..., None] * (neg - white))
rgb = np.clip(rgb, 0, 1)
img = np.dstack([rgb * shade[..., None], inside.astype(float)])
ax.imshow(img, extent=(-1, 1, -1, 1), interpolation="bilinear", zorder=1)
if l > 0:
ax.contour(U, V, np.ma.masked_where(U ** 2 + V ** 2 > 0.985, s), levels=[0], colors="#212529",
linewidths=1.4, zorder=2)
ax.add_patch(plt.Circle((0, 0), 1, fill=False, ec="#868e96", lw=0.8, zorder=3))
ax.plot([0, 0], [ey[2], ey[2] + 0.3], color="#495057", lw=1.4, zorder=3) # the z axis leaving the north pole
ax.text(0.08, ey[2] + 0.28, "$z$", fontsize=10, color="#495057", va="center")
ax.text(0, -1.12, f"{l - m} + {m} = {l}", ha="center", va="top", fontsize=10.5, color=GRAY)
for l in range(4):
axes[l, 0].text(-1.45, 0.1, rf"$l = {l}$", ha="right", va="center", fontsize=14)
for m in range(4):
axes[0, m].set_title(rf"$m = {m}$", fontsize=14)
fig.subplots_adjust(left=0.07, right=0.995, top=0.955, bottom=0.035, wspace=0.0, hspace=0.12)
plt.show()
Fig. The real part of on a sphere, for to 3 and to , with the north pole tipped toward the viewer. Teal is positive, orange negative, and the dark lines are nodes. Under each globe: the nodal circles around the axis plus the circles through the poles, which always add up to .
circles around the axis. These are the zeros of : circles of latitude where the wave changes sign on the way from pole to pole.
circles through the poles. The real part carries , which changes sign times around the axis: great circles through both poles.
in all, like the nodes of the box and the nodes of the oscillator. More nodes mean more bending and more kinetic energy: grows with .
The density does not depend on . , so a state with sharp is symmetric about the axis, like the even density on the ring. The circles through the poles belong to the real part, a standing wave made of and . Such real combinations are the familiar orbitals , , , ... of Atomic Orbitals.
Try it: pick and , drag the globe to turn it, and switch to the density to watch the circles through the poles disappear. The panel on the right is the vector model of the next section.
The vector model¶
The two sharp numbers fix the length of and its height along . Draw as an arrow with exactly those two properties:
Source
# synced: vector_cones
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from IPython.display import HTML
TEAL, CARDINAL, GRAY, PURPLE, ORANGE = "#107895", "#C8102E", "#6c757d", "#6a3d9a", "#e07b00"
fig, axes = plt.subplots(1, 2, figsize=(9.6, 4.7), gridspec_kw={"width_ratios": [1, 1.32], "wspace": 0.02})
t = np.linspace(0, 2 * np.pi, 300)
LIGHT = "#cdb9e3"
lz = {-2: r"-2\hbar", -1: r"-\hbar", 0: "0", 1: r"+\hbar", 2: r"+2\hbar"}
for ax, l in zip(axes, (1, 2)):
R = np.sqrt(l * (l + 1)) # |L| in units of hbar
ax.plot(R * np.cos(t), R * np.sin(t), color=GRAY, lw=1, ls=":")
ax.annotate("", xy=(0, R + 0.62), xytext=(0, -R - 0.35),
arrowprops=dict(arrowstyle="-|>", color="k", lw=1.4, mutation_scale=14, shrinkA=0, shrinkB=0))
ax.text(0.14, R + 0.55, "$z$", fontsize=14, va="center")
for m in range(-l, l + 1):
rm = np.sqrt(R * R - m * m) # radius of the cone's rim: the part of L off the z axis
ax.plot(rm * np.cos(t), m + 0.2 * rm * np.sin(t), color=LIGHT, lw=1.6)
ax.plot([-rm, 0, rm], [m, 0, m], color=LIGHT, lw=1.2)
tip = (rm * np.cos(np.radians(-42)), m + 0.2 * rm * np.sin(np.radians(-42)))
ax.annotate("", xy=tip, xytext=(0, 0), arrowprops=dict(arrowstyle="-|>", color=PURPLE, lw=2.2,
mutation_scale=15, shrinkA=0, shrinkB=0))
ax.plot([0], [m], "o", color=PURPLE, ms=5, zorder=5)
ax.plot([0.05, R + 0.25], [m, m], color=GRAY, lw=0.8, ls=(0, (2, 3)))
ax.text(R + 0.32, m, rf"$m = {m:+d}$: $L_z = {lz[m]}$" if m else r"$m = 0$: $L_z = 0$", va="center",
fontsize=12, color=PURPLE)
root = "2" if l == 1 else "6"
ax.set_title(rf"$l = {l}$", fontsize=15)
ax.set_xlim(-R - 0.25, R + 2.75 if l == 2 else R + 2.55); ax.set_ylim(-R - 0.5, R + 0.75)
ax.set_aspect("equal"); ax.axis("off")
fig.subplots_adjust(left=0.0, right=0.99, top=0.92, bottom=0.01)
plt.show()
Fig. The vector model for and . Every arrow has the same length (dotted circle), and its height takes values. Where the arrow points around the axis is unknown, so each state is drawn as a cone of possible directions.
never points along . The largest projection, , is shorter than the length . If lay along , then would both be sharp, which the commutator forbids.
The rest is in and . Since , every has
By the symmetry about each takes half, and . The part of perpendicular to has a definite size but no definite direction: that is the cone.
Do not take the cone literally. In a stationary state nothing moves, and does not sweep around its cone. The cone is a picture of what is sharp (the length and the height) and what is not (the direction around ).
Going further: why climbs in steps of one (raising and lowering operators)
The spherical harmonics came from solving a differential equation. As for the oscillator in Lecture 4.2, the commutators alone give the whole ladder of values, and they explain the .
Two new operators. Combine the two components that are not sharp:
Their commutators with follow from the cyclic rules, and :
Climbing. Let . Rewrite the commutator as and apply it to :
is again an eigenfunction of , with one higher: raises by one, and in the same way lowers it by one. Both commute with , so does not change. Compare for the oscillator.
The ladder has a top and a bottom. Climbing cannot go on forever. Write . Then
and an average of squares cannot be negative, so . There must be a top rung that turns into nothing, , and a bottom rung with . To find where they are, multiply out
Call the of the top rung . There the left side gives zero:
The same steps with put the bottom rung at . In between the rungs are one apart, so is a whole number:
follows from the commutators alone. The extra beyond is on the top rung: the part of that cannot line up with , because and do not commute. It plays the role of the oscillator’s .
can be The algebra allows half-integers. A particle moving in space needs whole , because must join up around the ring, so orbital motion and molecular rotation have whole . The half-integer ladders are realized by spin: the electron’s own angular momentum has , written (Spin).

Every ladder the commutators allow. Each has rungs one apart, from to . On the ladder, (orange) climbs and (purple) descends, and both stop at the ends. Ladders with whole describe rotation in space; the half-integer ones (dashed) describe spin.
In summary:
The square root keeps the states normalized, like the of the oscillator. Since is the adjoint of , the squared length of is .
connects only to , so in every , as the cone said. It is the same nearest-neighbor pattern as among the oscillator states.
In spherical coordinates (Problem 8):
Toward the rigid rotor¶
| particle on a ring | particle on a sphere | |
|---|---|---|
| coordinates | and | |
| sharp observables | and | |
| quantum numbers | and | |
| states | ||
| Hamiltonian | ||
| energies | ||
| states per level | 2 (1 for ) | |
| lowest energy | 0 | 0 |
A diatomic molecule turning with a fixed bond length is a particle with its reduced mass on a sphere of radius (Lecture 4.1): with . Its states are the of this lecture, and its energies climb with ever wider gaps.
The dipole of a diatomic points along its axis, so light couples to the rotation through , and is a mixture of and . That gives the rotational selection rule . Lecture 4.5 writes for and turns these levels into microwave spectra.
Problems¶
Problem 1: A methyl group as a rotor¶
A methyl group spins about the CC bond of ethane. Treat it as three H atoms ( u) on a ring of radius 1.02 Å, with the carbon on the axis. Find the moment of inertia, the energy unit in cm, and the first gap. How many states lie below at 300 K ( cm)? (Real methyl groups are hindered by a threefold barrier, which this model leaves out.)
Solution
Each H atom sits Å from the axis, so
dividing by J cm. The levels are cm, so the first gap, from to , is 5.36 cm. A level lies below when , that is : thirteen states, to 6. A free methyl rotor would be almost classical at room temperature. The real barrier, about 1000 cm in ethane, turns its lowest levels into torsional vibrations instead.
Problem 2: An electron circling benzene¶
In the benzene example, an electron in the highest filled level has on a ring of radius 139 pm. Using , find its speed, compare it with the speed of light, and find the time for one lap. Its density is the same all around the ring; in what sense is it “circling”? What changes for ?
Problem 3: A lopsided state on a ring¶
A particle on a ring is in the state . Normalize it, write it as a sum of ring states , and give the possible results of measuring with their probabilities. Find and .
Solution
Problem 4: Powers of ¶
Show that and, using the product rule, that . Write in spherical coordinates. Which spherical harmonic is it proportional to for and (on the unit sphere)? Why must be the top rung of its ladder?
Problem 5: Measuring when is sharp¶
A particle is in the state (up to normalization), which has . Using the matrices (the “Going further” box after the example), find the possible results of measuring and their probabilities, and compute and . Compare with the vector model.
Solution
The state is the column . The matrix only mixes and , so its eigenvectors are
(check one: ). Project the state onto each, with the bra conjugated (Lecture 3.6):
So with probability , and or with probability each. Then and .
The vector model predicts , in agreement. A sharp leaves spread over all three of its values.
import numpy as np
Lx = np.array([[0, 0, 0], [0, 0, -1j], [0, 1j, 0]]) # hbar = 1
v = np.array([1, 1j, 0]) / np.sqrt(2)
vals, U = np.linalg.eigh(Lx)
print(vals.round(6), (np.abs(U.conj().T @ v) ** 2).round(6)) # [-1, 0, 1] with [0.25, 0.5, 0.25]Problem 6: The magic angle¶
Show that is an eigenfunction of with eigenvalue . Find the angles of its two nodal cones. Solid-state NMR spins samples at the first of these angles, the “magic angle”; look up why.
Problem 7: How fuzzy are and ?¶
Use and to find in the state . Compare it with the bound from Lecture 3.6. For which is the bound reached exactly? Interpret the result with the cones.
Problem 8: Climbing with ¶
Use to compute and , with the functions from the table. Check both against .