From the classical spring to the quantum one¶
Lecture 4.1 ended with a vibrating diatomic: one bead of reduced mass on a spring of force constant , oscillating at . This lecture quantizes it. We write the Hamiltonian, solve , examine the states, and then explain why the levels come out evenly spaced.
Replace the momentum by in the classical energy:
is the stretch of the bond, the reduced mass, and the force constant, the curvature of the bond potential at its minimum.
The integer that labels the states is the vibrational quantum number, the italic letter . Do not confuse it with the frequency (Greek nu). The angular frequency of the classical spring is .
One energy and one length¶
Before solving anything, ask what sizes to expect. The Hamiltonian contains only three constants, , and , and there is only one way to combine them into an energy and one way into a length. Every result below is a number times these two scales.
Energy. is a rate (s) and is an action (J s), so is an energy. Since , it is : the energy of a photon at the frequency of the classical spring (the photoelectric effect). The solution below shows that it is also the gap between neighboring levels.
Length. Confining a quantum particle to a region of size costs kinetic energy of order , as in the box of Lecture 3.2, where . The spring stores potential energy of order at a stretch . Squeezing the state raises the first, letting it spread raises the second. Set them equal and solve for in two steps.
Step 1: collect the powers of . Multiply both sides by and divide by :
Step 2: take the fourth root. It looks odd until you write (the classical frequency ). Then the right side is a perfect square, and the fourth root of a square is a square root:
This is the size of the lowest vibrational states: how far the bond quivers. The section on zero-point energy makes this balance exact.
Measure position in this unit, , and the constants drop out. Since , the kinetic term becomes and the potential . Both prefactors equal (check: and ), so
Every harmonic oscillator, an H-Cl bond or an I-I bond, is this one problem; only and change. The figures below use units with , in which and .
Solving the oscillator¶
As with the trial-energy slider of Lecture 3.1, solutions that stay finite far from the well exist only at special energies. Solving the equation (by a power series, or by the operator method at the end of this lecture) gives a ladder of evenly spaced levels, each state a polynomial times a Gaussian:
The polynomials are the Hermite polynomials , written in the dimensionless position :
| nodes | symmetry | |||
|---|---|---|---|---|
| 0 | 1 | 0 | even | |
| 1 | 1 | odd | ||
| 2 | 2 | even | ||
| 3 | 3 | odd | ||
| 4 | 4 | even |
Each polynomial follows from the two before it by the recursion . Check: .
The polynomial and the Gaussian have separate jobs. The polynomial carries the shape: its roots become the nodes of . The Gaussian makes the state die away far from the well, however fast the polynomial grows:
Source
# synced: hermite_build
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})
y = np.linspace(-4.2, 4.2, 700)
Hs = [np.ones_like(y), 2 * y, 4 * y**2 - 2, 8 * y**3 - 12 * y]
names = [r"$H_0 = 1$", r"$H_1 = 2y$", r"$H_2 = 4y^2 - 2$", r"$H_3 = 8y^3 - 12y$"]
roots = [[], [0.0], [-np.sqrt(0.5), np.sqrt(0.5)], [-np.sqrt(1.5), 0.0, np.sqrt(1.5)]]
nodes = ["no node", "1 node", "2 nodes", "3 nodes"]
fig, axes = plt.subplots(2, 4, figsize=(9.6, 4.2), gridspec_kw={"hspace": 0.45, "wspace": 0.1})
for v in range(4):
top, bot = axes[0, v], axes[1, v]
H = Hs[v]
s = np.abs(H[np.abs(y) <= 1.9]).max() # each panel on its own scale, set by the roots
top.axhline(0, color=GRAY, lw=0.8)
top.plot(y, H, color="k", lw=2.2)
top.plot(roots[v], [0] * len(roots[v]), "o", color=CARDINAL, ms=7, zorder=5)
top.set_ylim(-1.2 * s, 1.2 * s)
top.set_title(names[v], fontsize=12.5)
psi = H * np.exp(-y**2 / 2)
m = np.abs(psi).max()
bot.axhline(0, color=GRAY, lw=0.8)
bot.plot(y, psi, color=TEAL, lw=2.4)
bot.plot(roots[v], [0] * len(roots[v]), "o", color=CARDINAL, ms=7, zorder=5)
bot.set_ylim(-1.25 * m, 1.25 * m)
bot.set_title(rf"$\psi_{v}$: " + nodes[v], fontsize=12.5, color=TEAL)
for ax in (top, bot):
ax.set_xlim(-4.2, 4.2); ax.set_yticks([]); ax.set_xticks([-3, 0, 3])
ax.spines["left"].set_visible(False); ax.tick_params(labelsize=10)
axes[0, 0].set_ylabel("polynomial", fontsize=11.5)
axes[1, 0].set_ylabel("state", fontsize=11.5, color=TEAL)
fig.subplots_adjust(left=0.04, right=0.99, top=0.92, bottom=0.07)
plt.show()
Fig. Top: the first four Hermite polynomials, each on its own vertical scale, with their roots in red. Bottom: the states . The Gaussian kills the growth of the polynomial at large and keeps its roots, which become the nodes of the state.
The first five states, written out. Each is the same Gaussian factor times a polynomial, , with :
| 0 | ||
| 1 | ||
| 2 | ||
| 3 | ||
| 4 |
Back in , for example, . Each polynomial has degree , hence roots and nodes, and contains only even or only odd powers of .
Source
# synced: ho_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})
vmax = 5
y = np.linspace(-4.6, 4.6, 900)
phi = np.zeros((vmax + 1, y.size))
phi[0] = np.pi**-0.25 * np.exp(-y**2 / 2)
phi[1] = np.sqrt(2) * y * phi[0]
for v in range(1, vmax):
phi[v + 1] = np.sqrt(2 / (v + 1)) * y * phi[v] - np.sqrt(v / (v + 1)) * phi[v - 1]
fig, (a1, a2) = plt.subplots(1, 2, figsize=(9.6, 4.7), sharey=True, gridspec_kw={"wspace": 0.05})
for ax, sq in ((a1, False), (a2, True)):
ax.plot(y, 0.5 * y**2, color="k", lw=2.2)
for v in range(vmax + 1):
E = v + 0.5
yt = np.sqrt(2 * E) # classical turning points
f = 1.25 * phi[v]**2 if sq else 0.5 * phi[v]
ax.hlines(E, -yt, yt, color=GRAY, lw=0.8, ls="--")
ax.fill_between(y, E, E + f, where=np.abs(y) >= yt, color=ORANGE, alpha=0.6, lw=0)
ax.plot(y, E + f, color=TEAL, lw=2)
ax.plot([-yt, yt], [E, E], "o", color="k", ms=3)
ax.set_xlim(-4.6, 4.6); ax.set_ylim(0, 6.5); ax.set_xticks([])
ax.spines["bottom"].set_visible(False)
for v in range(vmax + 1):
a2.text(4.7, v + 0.5, rf"$v = {v}$", va="center", fontsize=11.5)
a1.set_yticks([v + 0.5 for v in range(vmax + 1)])
a1.set_yticklabels([r"$\frac{1}{2}$", r"$\frac{3}{2}$", r"$\frac{5}{2}$", r"$\frac{7}{2}$", r"$\frac{9}{2}$", r"$\frac{11}{2}$"], fontsize=12.5)
a1.set_ylabel(r"energy ($\hbar\omega$)", fontsize=12)
a1.set_title(r"$\psi_v(x)$: $v$ nodes, even and odd in turn", loc="left", fontsize=12.5)
a2.set_title(r"$|\psi_v(x)|^2$: orange lies beyond the turning points", loc="left", fontsize=12.5)
a2.spines["left"].set_visible(False); a2.tick_params(left=False)
fig.subplots_adjust(left=0.07, right=0.93, top=0.92, bottom=0.03)
plt.show()
Fig. The first six states of the harmonic oscillator, each drawn on its energy level in the well, in units with . Left: , with nodes. Right: the probability density . Black dots mark the classical turning points, where ; orange marks the parts of each state beyond them.
Nodes: has nodes, as in the box. More nodes means more curvature, more kinetic energy, and a higher rung.
Equal steps: in a box the gaps grow as . Here the well widens as the energy rises, giving each new node more room, and the two effects balance exactly. The rough argument at the end of Lecture 4.1 got ; the raising operator below gets exactly .
One frequency: every gap is a multiple of , so every Bohr frequency of Lecture 3.7 is a multiple of . Any state in the well therefore returns to its starting shape after one classical period . That is why the packet of Lecture 3.7 swung exactly like a mass on a spring.
numpy confirms the formula: the grid Hamiltonian of Lecture 4.1 gives the same levels and states. In units with (so and ):
import numpy as np
from math import factorial
N = 400 # the spring on a grid, as in Lecture 4.1
x = np.linspace(-8, 8, N); dx = x[1] - x[0]
D2 = (np.eye(N, k=1) - 2 * np.eye(N) + np.eye(N, k=-1)) / dx**2
E, psi = np.linalg.eigh(-0.5 * D2 + np.diag(0.5 * x**2))
psi = psi / np.sqrt(dx) # unit vectors -> normalized functions
psi = psi * np.sign(psi[N - 100]) # sign convention: positive at x = 4
H2 = 4 * x**2 - 2 # the Hermite polynomial H_2
psi2 = H2 * np.exp(-x**2 / 2) / np.sqrt(2**2 * factorial(2) * np.sqrt(np.pi))
print("energies:", np.round(E[:5], 4))
print("largest difference between the two psi_2:", round(np.abs(psi[:, 2] - psi2).max(), 5))energies: [0.4999 1.4997 2.4993 3.4987 4.4979]
largest difference between the two psi_2: 0.00025
The energies agree with to better than 0.1%, and the two versions of differ by a few parts in ten thousand, the error of the grid.
Even and odd states¶
The well is symmetric, , and the states alternate between even and odd:
The Gaussian is even, and a Hermite polynomial of even degree contains only even powers of , one of odd degree only odd powers. Symmetry settles many integrals without computing them, using three rules:
The integral of an odd function over a symmetric range is zero: the left half cancels the right half.
Products follow the signs: even times even and odd times odd are even, even times odd is odd.
A derivative flips the symmetry: the slope of an even function is odd, and of an odd function even (the slope of is ).
In every state :
, because is even and is odd. The oscillator is centered at the bottom of the well.
, because has the opposite symmetry to , so the integrand is odd. Moving left and moving right are equally likely.
and are not zero: they are averages of quantities that are never negative. The spreads and carry the zero-point motion.
The same rules settle matrix elements between two different states, . The integrand has three factors, bra, operator and ket, and its parity is the product of theirs. An odd operator flips the parity of the state it acts on: multiplies by an odd function, and flips it by rule 3. An even operator keeps it: , and .
| sandwich | integrand | integral | example |
|---|---|---|---|
| odd | 0 | ||
| odd | 0 | ||
| odd | 0 | ||
| even | can survive | ||
| even | can survive |
An odd number of odd factors makes the integrand odd, and the integral is zero without computing anything.
An even integrand only can survive. has an even integrand and is still zero, by orthogonality: the lobes cancel, as in the figure below.
In particular links only states of opposite parity, so in the harmonic oscillator light can change only by an odd number. The ladder operators at the end of this lecture narrow that to .
Eigenfunctions of a Hermitian operator with different eigenvalues are orthogonal (Operators 2). Symmetry shows half of this at a glance:
Source
# synced: ho_orthogonality
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})
y = np.linspace(-4.5, 4.5, 1201); dy = y[1] - y[0]
phi = np.zeros((3, y.size))
phi[0] = np.pi**-0.25 * np.exp(-y**2 / 2)
phi[1] = np.sqrt(2) * y * phi[0]
phi[2] = y * phi[1] - np.sqrt(0.5) * phi[0]
cases = [(0, 1, "odd product:\ncancels by symmetry"), (0, 2, "even product:\nthe lobes cancel"), (1, 1, "a state with itself:\nall positive")]
fig, axes = plt.subplots(1, 3, figsize=(9.6, 3.3), sharey=True, gridspec_kw={"wspace": 0.08})
for ax, (m, n, note) in zip(axes, cases):
f = phi[m] * phi[n]
ax.fill_between(y, f, 0, where=f > 0, color=PURPLE, alpha=0.45, lw=0)
ax.fill_between(y, f, 0, where=f < 0, color=ORANGE, alpha=0.55, lw=0)
ax.plot(y, f, color="k", lw=1.6)
ax.axhline(0, color=GRAY, lw=0.8)
ax.set_title(rf"$\psi_{m}\,\psi_{n}$", fontsize=14)
area = np.sum(f) * dy
ax.set_xlabel(note + "\n" + rf"area $= {abs(area):.2f}$", fontsize=11.5)
ax.set_xticks([]); ax.spines["bottom"].set_visible(False)
ax.set_ylim(-0.42, 0.62); ax.set_xlim(-4.5, 4.5)
axes[0].set_yticks([0])
for ax in axes[1:]:
ax.spines["left"].set_visible(False); ax.tick_params(left=False)
fig.subplots_adjust(left=0.04, right=0.99, top=0.89, bottom=0.23)
plt.show()
Fig. Products of oscillator states, positive area purple and negative area orange. Left: is odd, and its two halves cancel. Middle: is even, but its negative middle exactly cancels its positive wings. Right: is never negative and has area 1.
Zero-point energy¶
The ladder starts at , not at the bottom of the well. Even at absolute zero a bond keeps this much vibrational energy, its zero-point energy. A classical spring can sit still at with . Why can’t the quantum one?
Sitting still at would mean a definite position and a definite momentum (zero), which the uncertainty principle of Lecture 3.6 forbids: . To see how much energy that costs, write the average energy in terms of the two spreads, in three steps.
The average energy is kinetic plus potential. Averaging over any state gives
For a centered state the averages are the spreads. Every oscillator level has (previous section). Since , this means , and likewise :
The kinetic energy is set by how spread out the momentum is, the potential energy by how spread out the position is.
The uncertainty principle ties the two spreads together. , with equality for a Gaussian (Lecture 3.6). So a state of width has an average energy of at least
Now vary the width. Take Gaussians centered at the bottom of the well, a narrow one, a wide one and every width in between. For each of them exactly, so the right-hand side is its actual average energy. The figure shows three of these trial states and the average energy of every width:
Source
# synced: zpe_tradeoff
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})
xs = np.linspace(-3, 3, 400)
trials = [("a", 0.3, "squeezed", GRAY), ("b", np.sqrt(0.5), "the ground state", CARDINAL), ("c", 1.4, "spread out", GRAY)]
fig = plt.figure(figsize=(9.6, 5.6))
for j, (tag, sig, name, col) in enumerate(trials):
ax = fig.add_axes([0.07 + 0.31 * j, 0.6, 0.27, 0.33])
rho = np.exp(-xs**2 / (2 * sig**2)) / (sig * np.sqrt(2 * np.pi)) # |psi|^2 of a Gaussian of width sigma_x
ax.plot(xs, 0.5 * xs**2, color="k", lw=1.6)
ax.fill_between(xs, 0, 1.4 * rho, color=TEAL, alpha=0.3, lw=0)
ax.plot(xs, 1.4 * rho, color=TEAL, lw=2)
h = 1.4 * rho.max() * np.exp(-0.5)
ax.annotate("", xy=(sig, h), xytext=(0, h), arrowprops=dict(arrowstyle="<->", color="k", lw=1.2, shrinkA=0, shrinkB=0))
ax.set_xlim(-3, 3); ax.set_ylim(0, 2.1); ax.axis("off")
ax.set_title(f"({tag}) {name}: " + rf"$\sigma_x = {sig:.2f}$", fontsize=12, color=col)
K, V = 1 / (8 * sig**2), 0.5 * sig**2
ax.text(-2.9, -0.2, f"kinetic {K:.2f}", color=ORANGE, fontsize=11.5, va="top")
ax.text(0.15, -0.2, f"potential {V:.2f}", color=TEAL, fontsize=11.5, va="top")
ax = fig.add_axes([0.08, 0.1, 0.9, 0.38])
s = np.linspace(0.2, 1.75, 400)
ax.plot(s, 1 / (8 * s**2), color=ORANGE, lw=2.4, label=r"kinetic $\sigma_p^2/2\mu$, with $\sigma_p = \hbar/2\sigma_x$")
ax.plot(s, 0.5 * s**2, color=TEAL, lw=2.4, label=r"potential $\frac{1}{2}k\sigma_x^2$")
ax.plot(s, 1 / (8 * s**2) + 0.5 * s**2, color="k", lw=2.8, label=r"total $\langle E\rangle$")
for tag, sig, name, col in trials:
E = 1 / (8 * sig**2) + 0.5 * sig**2
ax.plot([sig], [E], "o", color=col, ms=9, zorder=5)
ax.text(sig + 0.03, E + 0.1, f"({tag})", fontsize=12, color=col)
ax.annotate(r"lowest: $\frac{1}{2}\hbar\omega$, the ground state", xy=(np.sqrt(0.5), 0.5), xytext=(0.98, 0.2), fontsize=12,
color=CARDINAL, arrowprops=dict(arrowstyle="-|>", color=CARDINAL, lw=1.2, mutation_scale=12, shrinkB=6))
ax.set_xlim(0.2, 1.75); ax.set_ylim(0, 2.2)
ax.set_yticks([0, 0.5, 1, 1.5, 2]); ax.set_yticklabels(["0", r"$\frac{1}{2}$", "1", r"$\frac{3}{2}$", "2"])
ax.set_xlabel(r"width $\sigma_x$ of the Gaussian (units of $1/\sqrt{\alpha}$)", fontsize=11.5)
ax.set_ylabel(r"average energy ($\hbar\omega$)", fontsize=11.5)
ax.legend(loc="upper center", frameon=False, fontsize=11, ncol=3, bbox_to_anchor=(0.5, 1.12))
plt.show()
Fig. Top: three Gaussian trial states, drawn as in the well, with widths , 0.71 and 1.40 in units of (arrows mark ), and their average kinetic and potential energies in units of . Bottom: the average energy of a centered Gaussian of every width, split into kinetic (orange) and potential (teal), with the total in black and the three states above marked. The squeezed state (a) pays in kinetic energy, the spread-out state (c) in potential energy, and the total is lowest at (b).
Find the minimum: the derivative with respect to is , so . Each term is then , and the sum is .
The best width, (b), is exactly the ground state of the solution box: is a Gaussian with . No state of any shape can do better, because the bound of step 3 holds for every state, and reaches it. It is a minimum-uncertainty state, exactly (Problem 1), with its energy split evenly between kinetic and potential.
Heavier isotopes sit lower. falls as grows, while , set by the electrons, stays the same. A C-D bond has less zero-point energy than a C-H bond, so it takes more energy to break, and a reaction that breaks it runs slower: the kinetic isotope effect (Problem 4).
Beyond the turning points, and back to the classical spring¶
A classical oscillator of energy turns around where . For , using , the turning points are
Every state in the ladder figure leaks past them (orange). The oscillator is found where its kinetic energy would be negative, like the particle in the finite well.
For the turning points are exactly where the Gaussian changes from bending down to bending up (its inflection points, Appendix A.1). This is the curvature rule of the finite well: bends toward the axis where and away from it where .
Lecture 4.1 found where a classical spring spends its time: most often near the turning points, where it moves slowest, . The quantum ground state does the opposite and peaks in the middle. The animation climbs the ladder, with the classical curve of the same energy fixed in place:
Source
# synced: classical_limit
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})
vmax = 24
yg = np.linspace(-12, 12, 6001)
phi = np.zeros((vmax + 1, yg.size))
phi[0] = np.pi**-0.25 * np.exp(-yg**2 / 2)
phi[1] = np.sqrt(2) * yg * phi[0]
for v in range(1, vmax):
phi[v + 1] = np.sqrt(2 / (v + 1)) * yg * phi[v] - np.sqrt(v / (v + 1)) * phi[v - 1]
vs = [0, 0, 0] + list(range(1, vmax + 1)) + [vmax] * 4 # hold the first and last frames
u = np.linspace(-1.55, 1.55, 900) # position in units of the turning point
uc = np.linspace(-0.995, 0.995, 400)
fig, ax = plt.subplots(figsize=(8.6, 4.0))
ax.axvspan(-1.55, -1, color=GRAY, alpha=0.12, lw=0); ax.axvspan(1, 1.55, color=GRAY, alpha=0.12, lw=0)
ax.axvline(-1, color=GRAY, lw=1, ls=":"); ax.axvline(1, color=GRAY, lw=1, ls=":")
ax.plot(uc, 1 / (np.pi * np.sqrt(1 - uc**2)), color=CARDINAL, lw=2.4, label="classical spring, same energy")
fill = [ax.fill_between(u, 0, 0 * u, color=TEAL, alpha=0.3, lw=0)]
(line,) = ax.plot([], [], color=TEAL, lw=2.2, label=r"quantum $|\psi_v|^2$")
ax.set_xlim(-1.55, 1.55); ax.set_ylim(0, 1.8)
ax.set_xticks([-1, 0, 1]); ax.set_xticklabels(["left turning point", "0", "right turning point"], fontsize=11)
ax.set_yticks([]); ax.set_ylabel("probability density", fontsize=11.5)
ax.legend(loc="lower center", frameon=False, fontsize=11.5, ncol=2, bbox_to_anchor=(0.5, 1.0))
ax.text(-1.52, 1.5, "classically\nforbidden", fontsize=10.5, color=GRAY)
ax.text(1.52, 1.5, "classically\nforbidden", fontsize=10.5, color=GRAY, ha="right")
lab = ax.text(0, 1.3, "", ha="center", fontsize=13)
fig.subplots_adjust(left=0.06, right=0.98, top=0.89, bottom=0.11)
def update(i):
v = vs[i]
yt = np.sqrt(2 * v + 1)
dens = np.interp(u * yt, yg, phi[v]**2) * yt # density per unit of u = x / x_turn
line.set_data(u, dens)
fill[0].remove()
fill[0] = ax.fill_between(u, 0, dens, color=TEAL, alpha=0.3, lw=0)
yi = np.linspace(-yt, yt, 4001)
out = 1 - np.trapezoid(np.interp(yi, yg, phi[v]**2), yi)
lab.set_text(rf"$v = {v}$: {100 * out:.1f}% beyond the turning points")
ani = FuncAnimation(fig, update, frames=len(vs), interval=330, blit=False)
plt.close(fig)
HTML(ani.to_jshtml())Fig. The probability density of state (teal) and of a classical spring with the same energy (red), with position measured in units of the turning point so that both share one scale. As grows the quantum density piles up near the turning points, its wiggles become finer, and the share beyond the turning points shrinks.
At large the wiggles are too fine to resolve, and their average follows the classical curve: the correspondence principle, as for the particle in a box at large (Lecture 3.2).
Molecules live at the other end. At room temperature nearly every bond is in (Lecture 4.1), the most quantum state of all: a Gaussian centered where a classical spring is least likely to be.
Why the steps are equal: raising and lowering operators¶
The Hermite polynomials give the states one at a time. An idea of Dirac’s gives the whole ladder at once, and shows why the rungs are exactly apart. Work in the dimensionless position , where
Factor the Hamiltonian¶
For numbers, a sum of two squares factors: . The Hamiltonian is a sum of two squares too, and with . Since , the same factors would be . Define
is the adjoint of : is Hermitian, and changes sign under the adjoint (Operators 2).
The leftover is a commutator¶
Multiply in the order and act on a test function , as in Operators 2:
The comes from the product rule, . It is the only thing that keeps the operators from factoring like numbers. Reading off the operator:
The other order gives instead of (Problem 6), so .
The zero-point energy is exactly the piece left over because and do not commute. If they did, the factorization would be exact and the lowest energy would be zero, as for a classical spring.
Climbing the ladder¶
Let be an eigenfunction with energy . Apply to , using in the middle:
so
is again an eigenfunction, with energy exactly higher: is the raising operator. The same steps show that has energy (Problem 6): is the lowering operator.
The ladder has a bottom¶
Kinetic and potential energy are never negative, so no state has energy below zero, and stepping down cannot go on forever. It must stop at a state that turns into nothing:
the Gaussian again, this time from a first-order equation. Its energy is . Climbing rungs from there adds , which gives . No other energies exist: a state at any other energy could be lowered below zero.
One step up from the Gaussian, using : , the state .
Source
# synced: ladder_climb
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})
y = np.linspace(-3.6, 3.6, 500)
phi = np.zeros((3, y.size))
phi[0] = np.pi**-0.25 * np.exp(-y**2 / 2)
phi[1] = np.sqrt(2) * y * phi[0]
phi[2] = y * phi[1] - np.sqrt(0.5) * phi[0]
fig, (ax, bx) = plt.subplots(1, 2, figsize=(9.6, 4.6), gridspec_kw={"width_ratios": [1.15, 1], "wspace": 0.08})
yy = np.linspace(-3.03, 3.03, 300)
ax.plot(yy, 0.5 * yy**2, color="k", lw=2.2)
for v in range(5):
E = v + 0.5
ax.hlines(E, -np.sqrt(2 * E), np.sqrt(2 * E), color=TEAL, lw=2.4)
ax.text(np.sqrt(2 * E) + 0.12, E, rf"$\psi_{v}$", va="center", fontsize=12, color=TEAL)
for v in range(4):
ax.annotate("", xy=(-0.55, v + 1.45), xytext=(-0.55, v + 0.55),
arrowprops=dict(arrowstyle="-|>", color=ORANGE, lw=2, mutation_scale=16, shrinkA=0, shrinkB=0))
ax.annotate("", xy=(0.55, v + 0.55), xytext=(0.55, v + 1.45),
arrowprops=dict(arrowstyle="-|>", color=PURPLE, lw=2, mutation_scale=16, shrinkA=0, shrinkB=0))
ax.text(-3.3, 5.0, r"$\hat{a}^\dagger$: up one rung, $+\hbar\omega$", ha="left", fontsize=12, color=ORANGE)
ax.text(0.45, 5.0, r"$\hat{a}$: down one rung, $-\hbar\omega$", ha="left", fontsize=12, color=PURPLE)
ax.annotate("", xy=(0.55, 0.06), xytext=(0.55, 0.45),
arrowprops=dict(arrowstyle="-|>", color=CARDINAL, lw=1.6, ls="--", mutation_scale=13, shrinkA=0, shrinkB=0))
ax.plot([0.55], [0.22], "x", color=CARDINAL, ms=12, mew=2.6)
ax.text(0.8, 0.12, r"$\hat{a}\psi_0 = 0$: the ladder has a bottom", fontsize=11.5, color=CARDINAL, va="center")
ax.set_xlim(-3.4, 3.9); ax.set_ylim(0, 5.4); ax.axis("off")
ax.set_title(r"rungs $\hbar\omega$ apart, starting at $\frac{1}{2}\hbar\omega$", loc="left", fontsize=12.5)
names = [r"$\psi_0 \propto e^{-y^2/2}$", r"$\hat{a}^\dagger\psi_0 \propto y\,e^{-y^2/2} \propto \psi_1$",
r"$\hat{a}^\dagger\psi_1 \propto (2y^2 - 1)\,e^{-y^2/2} \propto \psi_2$"]
for v in range(3):
off = 1.9 * v
bx.axhline(off, color="#dee2e6", lw=0.8)
bx.plot(y, off + 0.95 * phi[v], color=TEAL, lw=2.2)
bx.text(-3.6, off + 0.98, names[v], fontsize=12, va="center")
for v in range(2):
bx.annotate("", xy=(3.25, 1.9 * v + 1.6), xytext=(3.25, 1.9 * v + 0.35),
arrowprops=dict(arrowstyle="-|>", color=ORANGE, lw=2, mutation_scale=16, shrinkA=0, shrinkB=0))
bx.text(3.4, 1.9 * v + 0.95, r"$\hat{a}^\dagger$", color=ORANGE, fontsize=13, va="center")
bx.set_xlim(-3.7, 3.9); bx.set_ylim(-0.75, 5.3); bx.axis("off")
bx.set_title(r"each step up adds one node", loc="left", fontsize=12.5)
fig.subplots_adjust(left=0.01, right=0.99, top=0.92, bottom=0.02)
plt.show()
Fig. Left: the raising operator moves a state up one rung (orange) and the lowering operator down one (purple); ends the ladder at . Right: raising the Gaussian once gives , and twice gives . Each step multiplies by a polynomial one degree higher and adds a node.
The square roots keep the states normalized. The length squared of is .
: the product counts the rungs, and is called the number operator.
Position connects only neighboring levels¶
Add the two operators: . Position is therefore one step down plus one step up:
(This is the Hermite recursion in disguise.) Multiply by and integrate; orthonormality keeps two terms at most:
In the matrix language of Lecture 3.5: in the oscillator’s own eigenbasis is diagonal, and has entries only next to the diagonal.
Source
# synced: x_matrix
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 Rectangle
n = 7
Hm = np.diag(np.arange(n) + 0.5)
Xm = np.diag(np.sqrt(np.arange(1, n) / 2), 1)
Xm = Xm + Xm.T # <v'|x|v> in units of 1/sqrt(alpha)
fig, axes = plt.subplots(1, 2, figsize=(9.6, 4.5), gridspec_kw={"wspace": 0.16})
titles = [r"$\langle v'|\hat{H}|v\rangle$ in units of $\hbar\omega$", r"$\langle v'|\hat{x}|v\rangle$ in units of $1/\sqrt{\alpha}$"]
for ax, M, title in zip(axes, (Hm, Xm), titles):
for i in range(n):
for j in range(n):
val = M[i, j]
fc, tc = ("white", "#adb5bd") if val == 0 else (("#ebe3f4", PURPLE) if i == j else ("#fde9d2", ORANGE))
ax.add_patch(Rectangle((j, n - 1 - i), 1, 1, fc=fc, ec="#ced4da", lw=0.8))
ax.text(j + 0.5, n - 0.5 - i, "0" if val == 0 else f"{val:.2f}", ha="center", va="center",
fontsize=10.5, color=tc, weight="normal" if val == 0 else "bold")
ax.set_xlim(0, n); ax.set_ylim(0, n); ax.set_aspect("equal")
ax.set_xticks(np.arange(n) + 0.5); ax.set_xticklabels(range(n)); ax.xaxis.tick_top()
ax.set_yticks(np.arange(n) + 0.5); ax.set_yticklabels(range(n - 1, -1, -1))
ax.tick_params(length=0, labelsize=11)
for sp in ax.spines.values():
sp.set_visible(False)
ax.set_xlabel(title, fontsize=12.5, labelpad=8)
ax.text(-0.75, n / 2, r"$v'$", fontsize=13, ha="center", va="center")
ax.text(n / 2, n + 0.85, r"$v$", fontsize=13, ha="center", va="center")
fig.subplots_adjust(left=0.05, right=0.99, top=0.88, bottom=0.1)
plt.show()
Fig. The Hamiltonian (left) and position (right) as matrices in the basis of oscillator states to 6. is diagonal, with the energies . has a zero diagonal, since in every state, and nonzero entries only where and differ by one.
numpy builds the same matrix from the grid states of the earlier cell: sandwich each pair of eigenvectors around the diagonal matrix of positions, as in Lecture 3.5.
V = psi[:, :6] * np.sqrt(dx) # the first six eigenvectors, unit length again
X = V.T @ np.diag(x) @ V # <v'|x|v>, in units of 1/sqrt(alpha)
print(np.round(X, 3) + 0) # + 0 turns -0.0 into 0.0[[0. 0.707 0. 0. 0. 0. ]
[0.707 0. 1. 0. 0. 0. ]
[0. 1. 0. 1.224 0. 0. ]
[0. 0. 1.224 0. 1.414 0. ]
[0. 0. 0. 1.414 0. 1.58 ]
[0. 0. 0. 0. 1.58 0. ]]
The entries next to the diagonal are , 1, , and , up to the grid error; everything else is zero.
This is the origin of the infrared selection rule of Lecture 4.3. Light couples to a vibrating molecule through its dipole moment, which to first order changes in proportion to the stretch . Light can drive only the transitions for which : in a harmonic molecule, one rung at a time, . Every allowed jump has the same energy , so each vibration gives one strong infrared line.
Powers of reach further: moves up to two rungs, and gives in any state in two lines (Problem 8).
Real bonds are softer than a parabola at large stretch. Their levels bunch together toward the dissociation limit, and weak overtones with appear; Lecture 4.3 adds this anharmonicity.
Problems¶
Problem 1: The minimum-uncertainty state¶
Compute and for the ground state and show that . Use and .
Solution
Problem 2: How sharp is a bond?¶
Carbon monoxide, CO, vibrates at cm and has a bond length pm. Find the spread of its bond length in the ground vibrational state, and compare it with .
Solution
Problem 3: The second excited state¶
Show that is an eigenfunction of and find its energy. Where are its nodes, and how do they compare with the classical turning points of the same energy? Compare with .
Problem 4: The kinetic isotope effect¶
A C-H stretch absorbs near 2900 cm. (a) Predict the wavenumber of the C-D stretch, treating each as a diatomic with the carbon mass 12 u and the same force constant. (b) Find the difference in zero-point energy. (c) Suppose a reaction breaks this bond, and its transition state has lost the stretch, so both isotopes reach the same barrier top. By about what factor does replacing H with D slow the reaction at 300 K ( cm)?
Solution
(a) The force constant is set by the electrons and does not change, so :
(b) , so the zero-point energies are 1450 and 1065 cm, a difference of 385 cm. The C-D bond sits 385 cm lower in the same well.
(c) Both climb to the same barrier top, so the C-D bond needs 385 cm more activation energy. The Boltzmann factor makes the rate ratio
Measured primary isotope effects are typically 2 to 7. An effect this large is the standard sign that a C-H bond breaks in the slowest step of a reaction, and it is purely a zero-point energy effect.
Problem 5: Beyond the turning points, higher up¶
Adapt the numpy line of the example to find how often the oscillator is found beyond its turning points in and in , using the grid x and states psi from the code cell that checks the Hermite formula. (The turning points are at in these units.) Why does the fraction fall as grows?
Problem 6: Ladder algebra¶
(a) Act with on a test function and show that . (b) Show that if , then . (c) Apply to and show that the result is proportional to .
Solution
Problem 7: Half an oscillator¶
A particle moves in for , with an impenetrable wall at , so . Which states of the full oscillator also solve this problem? What are the allowed energies, and how far apart are they?
Problem 8: Average energies in any level¶
Write in terms of and (keep the order of the factors). Use the ladder relations to show that , and from this that the average potential energy in any level is half the total, . What is the average kinetic energy?