Operators 2 showed that observables are Hermitian operators, with real eigenvalues and orthonormal eigenfunctions. This lecture is about what happens when we use one: postulates 3 and 4, what a measurement returns and how often. The map shows how the pieces connect.
Source
# synced: postulate_map
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 FancyBboxPatch, Circle
W, Ht = 11.5, 3.3 # draw in inches: one data unit = one inch
fig = plt.figure(figsize=(W, Ht))
ax = fig.add_axes([0, 0, 1, 1]); ax.set_xlim(0, W); ax.set_ylim(0, Ht); ax.axis("off")
rend = fig.canvas.get_renderer()
tint = {GRAY: "#f1f3f5", PURPLE: "#f1ecf7", TEAL: "#e3f1f5", CARDINAL: "#fbeaec", ORANGE: "#fdf0e0"}
ym, lh, gap = 1.95, 0.29, 0.26 # middle of the row, line height, gap between boxes
# (width, title, colour, lines); a line is (text, colour) or (text, colour, postulate number); None is a divider
steps = [(2.2, "state, observable", GRAY,
[(r"a state $\psi$", GRAY, 1), (r"$\langle\psi|\psi\rangle = 1$", "k"),
(r"an observable $\hat{A}$", GRAY, 2), (r"Hermitian: $\hat{A} = \hat{A}^\dagger$", "k")]),
(1.85, "eigenvalues", PURPLE,
[(r"$\hat{A}\,\phi_n = a_n\,\phi_n$", "k"), (r"$a_n$ real: the readings", PURPLE),
(r"$\phi_n$ orthogonal: axes", TEAL)]),
(1.85, "superposition", TEAL,
[(r"$\psi = c_1\phi_1 + c_2\phi_2 + \cdots$", "k"), (r"$c_n = \langle\phi_n|\psi\rangle$", "k")]),
(2.05, r"measure $A$", CARDINAL,
[("one copy", GRAY, 3), (r"a reading $a_n$", "k"), (r"then $\psi \to \phi_n$", "k"), None,
("many copies", GRAY, 4), (r"fraction $p_n = |c_n|^2$", "k"), (r"$\langle A\rangle = p_1a_1 + p_2a_2 + \cdots$", "k")]),
(2.4, "time evolution", ORANGE,
[(r"in the eigenstates of $\hat{H}$", GRAY, 5), (r"$c_n \to c_n\,e^{-iE_nt/\hbar}$", "k"),
(r"each $|c_n|^2$ stays fixed", "k")])]
x = (W - sum(w for w, *_ in steps) - gap * (len(steps) - 1)) / 2
edges = []
for w, title, col, lines in steps:
h = 0.62 + lh * sum(1 for l in lines if l) + 0.12 * sum(1 for l in lines if l is None)
top = ym + h / 2
ax.add_patch(FancyBboxPatch((x, ym - h / 2), w, h, boxstyle="round,pad=0,rounding_size=0.1",
fc=tint[col], ec=col, lw=1.6, zorder=2))
ax.text(x + w / 2, top - 0.1, title, ha="center", va="top", fontsize=12.5, color=col, weight="bold", zorder=3)
y = top - 0.5
for line in lines:
if line is None: # divider between one copy and many copies
ax.plot([x + 0.15, x + w - 0.15], [y + 0.1, y + 0.1], color=col, lw=0.9, ls=":", zorder=3)
y -= 0.12
continue
t = ax.text(x + w / 2, y, line[0], ha="center", va="center", fontsize=12, color=line[1], zorder=3,
style="italic" if line[1] == GRAY else "normal")
if len(line) == 3: # circled postulate number, just left of its line
bb = t.get_window_extent(rend).transformed(ax.transData.inverted())
cx = bb.x0 - 0.16
ax.add_patch(Circle((cx, y), 0.115, fc="white", ec=GRAY, lw=1, zorder=4))
ax.text(cx, y - 0.005, str(line[2]), ha="center", va="center", fontsize=9.5, color=GRAY, zorder=5)
y -= lh
edges.append((x, x + w))
x += w + gap
for (a0, a1), (b0, b1) in zip(edges[:-1], edges[1:]):
ax.annotate("", xy=(b0, ym), xytext=(a1, ym), zorder=1,
arrowprops=dict(arrowstyle="-|>", color=GRAY, lw=1.6, mutation_scale=16, shrinkA=1, shrinkB=1))
for i, j, lab in ((0, 1, "Lectures 3.5, 3.5b: operators"), (2, 3, "Lecture 3.6: measurement"),
(4, 4, "Lecture 3.7: time")): # which lecture covers which boxes
x0, x1 = edges[i][0] + 0.05, edges[j][1] - 0.05
ax.plot([x0, x0, x1, x1], [0.42, 0.34, 0.34, 0.42], color=GRAY, lw=1)
ax.text((x0 + x1) / 2, 0.24, lab, ha="center", va="top", fontsize=11, color=GRAY, style="italic")
plt.show()
Fig. How the postulates connect, in the order of these lectures. A state and a Hermitian observable give real eigenvalues, the possible readings, and orthogonal eigenfunctions, the axes. Any state is a superposition along those axes, with components . One measurement returns a single reading and leaves the state in . Repeated on many identically prepared copies, the reading turns up in a fraction of them, and the readings average to . Circled numbers are the postulates in the table of Operators 1. This lecture covers the two middle boxes; time evolution (5) is the subject of Time Dependence.
Eigenfunctions are a coordinate system¶
The eigenfunctions of a Hermitian operator work like the perpendicular axes of a plane. We start with a vector in a plane, then take the same two steps with a function, in the same Dirac notation: the structure is identical.
A vector: build it, then project¶
Take two perpendicular unit vectors and : and . Every vector in their plane is a linear combination of them:
To find , put the bra in front of both sides. Orthonormality keeps one term, and the bra gives the same way:
Source
# synced: vector_projection
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})
a = np.radians(30)
u1, u2 = np.array([np.cos(a), np.sin(a)]), np.array([-np.sin(a), np.cos(a)]) # tilted perpendicular unit vectors
c1, c2 = 3.0, 1.5
v = c1 * u1 + c2 * u2
arrow = lambda ax, tail, tip, col, lw, ms, z: ax.annotate("", xy=tip, xytext=tail, zorder=z, arrowprops=dict(
arrowstyle="-|>", color=col, lw=lw, mutation_scale=ms, shrinkA=0, shrinkB=0))
fig, axs = plt.subplots(1, 2, figsize=(9.6, 4.3), gridspec_kw={"wspace": 0.06})
for ax in axs:
for u in (u1, u2): # the two axes, faint
ax.plot(*np.outer([-0.75, 3.9], u).T, color=GRAY, lw=0.9, alpha=0.45, zorder=1)
arrow(ax, (0, 0), u1, "k", 2.2, 14, 5); arrow(ax, (0, 0), u2, "k", 2.2, 14, 5)
ax.text(*(u1 + np.array([0.0, -0.36])), r"$|u_1\rangle$", fontsize=14, ha="center")
ax.text(*(u2 + np.array([0.14, -0.06])), r"$|u_2\rangle$", fontsize=14, ha="left")
arrow(ax, (0, 0), v, TEAL, 3, 20, 4)
ax.text(*(v + np.array([0.1, 0.08])), r"$|v\rangle$", color=TEAL, fontsize=17)
ax.set_xlim(-2.5, 3.45); ax.set_ylim(-0.75, 3.3); ax.set_aspect("equal"); ax.axis("off")
b = axs[0] # build: tip to tail
arrow(b, (0, 0), c1 * u1, PURPLE, 2.8, 18, 3)
arrow(b, c1 * u1, v, ORANGE, 2.8, 18, 3)
b.text(*(0.62 * c1 * u1 + np.array([0.28, -0.42])), r"$c_1|u_1\rangle$", color=PURPLE, fontsize=15, ha="center")
b.text(*(c1 * u1 + 0.55 * c2 * u2 + np.array([0.2, 0.0])), r"$c_2|u_2\rangle$", color=ORANGE, fontsize=15, ha="left")
b.set_title(r"build: $|v\rangle = c_1|u_1\rangle + c_2|u_2\rangle$", loc="left", fontsize=14)
q = axs[1] # project: drop perpendiculars onto each axis
p1, p2 = c1 * u1, c2 * u2
q.plot(*np.array([v, p1]).T, color=PURPLE, ls="--", lw=1.5, zorder=2)
q.plot(*np.array([v, p2]).T, color=ORANGE, ls="--", lw=1.5, zorder=2)
q.plot(*np.array([[0, 0], p1]).T, color=PURPLE, lw=7, alpha=0.5, solid_capstyle="butt", zorder=2)
q.plot(*np.array([[0, 0], p2]).T, color=ORANGE, lw=7, alpha=0.5, solid_capstyle="butt", zorder=2)
for foot, along, up in ((p1, u1, u2), (p2, u2, u1)): # right-angle marks at the feet
sq = np.array([foot - 0.17 * along, foot - 0.17 * along + 0.17 * up, foot + 0.17 * up])
q.plot(*sq.T, color=GRAY, lw=1.1, zorder=2)
q.text(*(0.45 * p1 + 0.55 * (-u2)), r"$c_1 = \langle u_1|v\rangle = 3$", color=PURPLE, fontsize=15, ha="left", va="center")
q.text(*(0.75 * p2 + 0.3 * (-u1)), r"$c_2 = \langle u_2|v\rangle$" + "\n" + r"$= 1.5$", color=ORANGE, fontsize=15, ha="right", va="center")
q.set_title(r"project: $c_k = \langle u_k|v\rangle$, the shadow on axis $k$", loc="left", fontsize=14)
fig.subplots_adjust(left=0.01, right=0.99, top=0.9, bottom=0.02)
plt.show()
Fig. Left: the vector built from and , tip to tail. Right: the same coefficients found by projection; each is the shadow of on its own axis. Here and .
A function: the same two steps¶
A wavefunction in a box is built the same way, with the box states , , in place of the unit vectors. They are orthonormal, , and there are enough of them to build any state in the box:
The bra picks out one coefficient, exactly as for the vector. The only new step is how to compute a bracket of two functions: multiply them point by point and integrate.
Source
# synced: function_projection
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})
x = np.linspace(0, 1, 400)
psi = np.sqrt(30) * x * (1 - x) # the state of the worked example, L = 1
fig, axs = plt.subplots(1, 2, figsize=(9.6, 3.5), gridspec_kw={"wspace": 0.1})
for ax, n in zip(axs, (1, 3)): # n = 2 is zero by symmetry (see the Example)
phi = np.sqrt(2) * np.sin(n * np.pi * x)
prod = phi * psi
c = np.trapezoid(prod, x)
ax.fill_between(x, prod, where=prod >= 0, color=PURPLE, alpha=0.25, lw=0, interpolate=True)
ax.fill_between(x, prod, where=prod < 0, color=ORANGE, alpha=0.3, lw=0, interpolate=True)
ax.plot(x, psi, color=TEAL, lw=2.4, label=r"the state $\psi$")
ax.plot(x, phi, color=GRAY, lw=1.6, ls="--", label=r"box state $\psi_n$")
ax.plot(x, prod, color=PURPLE, lw=1.2, label=r"product $\psi_n\,\psi$")
ax.axhline(0, color=GRAY, lw=0.6)
ax.set_xlim(0, 1); ax.set_ylim(-2.2, 2.35); ax.set_yticks([])
ax.set_xticks([0, 0.5, 1]); ax.set_xticklabels(["0", "L/2", "L"], fontsize=11)
ax.spines["left"].set_visible(False)
ax.set_title(rf"$c_1 = \langle\psi_1|\psi\rangle = \int\psi_1\,\psi\,dx = {c:.3f}$" if n == 1
else rf"$c_3 = \langle\psi_3|\psi\rangle = {c:.3f}$: the lobes nearly cancel", loc="left", fontsize=13)
axs[1].legend(loc="lower left", frameon=False, fontsize=11, handlelength=1.6, borderaxespad=0.1)
fig.subplots_adjust(left=0.01, right=0.99, top=0.88, bottom=0.1)
plt.show()
Fig. The state in a box with , projected onto two box states. Each coefficient is the area under the product , purple where it is positive and orange where it is negative. Left: and nearly coincide, and . Right: the three lobes of almost cancel, leaving .
How large is 0.999? Squared components add up to the length squared, for the arrow () and for the function (, shown below). A normalized state has length 1, so no coefficient can exceed 1 in size, and would mean . Here leaves only 0.0014 for all the other box states together: the parabola is almost exactly the ground state, which is why the two curves in the left panel nearly coincide.
Side by side, the vector and the function follow one structure. Only the last row, how a bracket is computed, differs:
| a vector in a plane | a function | |
|---|---|---|
| axes | eigenfunctions | |
| orthonormal | ||
| build | ||
| project | ||
| the bracket | multiply matching components, add | multiply point by point, integrate |
The box states are one set of axes. The eigenfunctions of any Hermitian operator are orthonormal and complete (Operators 2), so every state can be expanded in them:
A smooth, spread-out state needs few eigenfunctions. A narrow one needs many, because it takes short wavelengths, high , to build a sharp feature:
Source
# synced: sine_series
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})
x = np.linspace(0, 1, 600)
psi = np.exp(-(x - 0.3)**2 / (4 * 0.045**2)) # |psi|^2 has width 0.045 L
psi = psi / np.sqrt(np.trapezoid(psi**2, x))
ns = np.arange(1, 31)
phis = np.sqrt(2) * np.sin(np.outer(ns, np.pi * x))
c = np.trapezoid(phis * psi, x, axis=1)
ks = np.concatenate([np.arange(1, 25), np.full(6, 24)])
fig, (ax, axb) = plt.subplots(2, 1, figsize=(8, 4.4), gridspec_kw={"height_ratios": [1.5, 1], "hspace": 0.55})
ax.plot(x, psi, color="k", lw=1.4, ls=":", label=r"the state $\psi$")
(part,) = ax.plot([], [], color=TEAL, lw=2.4, label=r"sum of the first $k$ terms $c_n\psi_n$")
band = [ax.fill_between(x, 0 * x, color=TEAL, alpha=0.15, lw=0)]
ax.axhline(0, color=GRAY, lw=0.6)
ax.set_xlim(0, 1); ax.set_ylim(-1.6, 3.6); ax.set_yticks([])
ax.set_xticks([0, 0.5, 1]); ax.set_xticklabels(["0", "L/2", "L"], fontsize=12)
ax.spines["left"].set_visible(False)
ax.legend(loc="upper right", frameon=False, fontsize=12)
bars = axb.bar(ns, c**2, color="#d9dde1", width=0.75)
axb.set_xlim(0.3, 30.7); axb.set_ylim(0, 1.15 * (c**2).max())
axb.set_xlabel(r"$n$", fontsize=12); axb.set_ylabel(r"$|c_n|^2$", fontsize=12)
axb.set_yticks([0, 0.1]); axb.tick_params(labelsize=11)
fig.subplots_adjust(left=0.08, right=0.98, top=0.9, bottom=0.12)
def update(i):
k = ks[i]
s = c[:k] @ phis[:k]
part.set_data(x, s)
band[0].remove(); band[0] = ax.fill_between(x, s, color=TEAL, alpha=0.15, lw=0)
for j, b in enumerate(bars):
b.set_color(TEAL if j < k else "#d9dde1")
ax.set_title(rf"$k = {k}$ box states", loc="left", fontsize=13)
axb.set_title(f"the first {k} states hold probability {np.sum(c[:k]**2):.3f}", loc="left", fontsize=12.5)
ani = FuncAnimation(fig, update, frames=len(ks), interval=140, blit=False)
plt.close(fig)
HTML(ani.to_jshtml())Fig. A narrow packet centered at rebuilt from box states one at a time. Top: the sum of the first terms against the packet. Bottom: for each state, lit once it is included. About ten states are needed before the sum holds 99 percent of the probability.
A measurement returns an eigenvalue¶
The probabilities add up to one because the state is normalized. Expand both sides of ; orthonormality removes every cross term:
Only the size of each coefficient matters for these probabilities. The coefficients and give the same ; the phases come back in Time Dependence.
A measurement does not reveal . It returns one number, and it changes the state (below). The probabilities show up only in the statistics of many identically prepared copies: many boxes, each holding one particle prepared in the same state , each measured once. Measuring one particle over and over does not work, because the first reading changes its state. It is the same story as the single detections that built up the pattern in The Schrödinger Equation. Chemistry gets its copies for free: a spectrum is recorded on a sample of something like 1020 molecules, and each molecule contributes its own reading.
Try it: pick a state and press +1 box a few times. Each press prepares a fresh box in that state and measures its energy once; after a single reading, measure it again reads the same box a second time. Then add a thousand boxes. Every reading is one of the energies of the box, never anything in between. The bars approach and the running mean approaches a value we compute next.
Averages and spreads¶
If the reading comes up with probability , the average over many readings is the weighted sum . The integral formula of The Schrödinger Equation gives the same number. Expand and let act on each eigenfunction:
The spread is a sum of squares weighted by probabilities, so it vanishes only when all the probability sits on one eigenvalue: only an eigenfunction gives a sharp value.
The two routes to , the sum over probabilities and the integral, are worth checking numerically. On a grid the box states are the eigenvectors of the Hamiltonian matrix, and the expansion coefficients are dot products:
import numpy as np
N = 1000 # interior points of a box with L = 1
x = np.linspace(0, 1, N + 2)[1:-1]; dx = x[1] - x[0]
H = (2 * np.eye(N) - np.eye(N, k=1) - np.eye(N, k=-1)) / (2 * dx**2) # hbar = m = 1, V = 0
E, phi = np.linalg.eigh(H)
phi = phi / np.sqrt(dx) # columns normalized as functions
E1 = np.pi**2 / 2 # exact ground-state energy
psi = np.sqrt(30) * x * (1 - x) # the parabola of the example
c = phi.T @ psi * dx # c_n = <phi_n|psi>
p = c**2
print("p_1, p_2, p_3:", np.round(p[:3], 5))
print("sum of p_n:", round(p.sum(), 6))
print("<E> = sum p_n E_n:", round(p @ E / E1, 4), "E1")
print("<E> = <psi|H|psi>:", round(psi @ H @ psi * dx / E1, 4), "E1 (exact: 10/pi^2 =", round(10 / np.pi**2, 4), "E1)")p_1, p_2, p_3: [0.99856 0. 0.00137]
sum of p_n: 1.0
<E> = sum p_n E_n: 1.0132 E1
<E> = <psi|H|psi>: 1.0132 E1 (exact: 10/pi^2 = 1.0132 E1)
The grid agrees with the exact coefficients of the example, and both routes give : the parabola costs only 1.3 percent more energy than the ground state.
The translation table of Operators 1 grows by the three rows this lecture used:
| integral | Dirac | numpy on a grid | |
|---|---|---|---|
| expansion coefficient | c = phi.conj().T @ psi * dx | ||
| probability of | p = np.abs(c)**2 | ||
| average | p @ a |
After the measurement¶
Source
# synced: collapse
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})
x = np.linspace(0, 1, 400)
ph = [np.sqrt(2) * np.sin(n * np.pi * x) for n in (1, 2, 3, 4)]
before = np.array([0.5, 0.5, np.sqrt(0.5), 0])
after = np.array([0, 0, 1.0, 0])
fig = plt.figure(figsize=(8.6, 3.9))
gs = fig.add_gridspec(2, 3, width_ratios=[1, 0.42, 1], height_ratios=[1.1, 1], hspace=0.35, wspace=0.05)
for col, cs, title in ((0, before, r"before: $\psi = \frac{1}{2}\psi_1 + \frac{1}{2}\psi_2 + \frac{1}{\sqrt{2}}\psi_3$"),
(2, after, r"after: $\psi = \psi_3$")):
ax, axb = fig.add_subplot(gs[0, col]), fig.add_subplot(gs[1, col])
w = sum(ci * p for ci, p in zip(cs, ph))
ax.fill_between(x, w**2, color=CARDINAL, alpha=0.15, lw=0)
ax.plot(x, w, color=TEAL, lw=2.4); ax.plot(x, w**2, color=CARDINAL, lw=1.6)
ax.axhline(0, color=GRAY, lw=0.6); ax.set_xlim(0, 1); ax.set_ylim(-2.2, 4.3)
ax.set_xticks([]); ax.set_yticks([]); ax.spines["left"].set_visible(False); ax.spines["bottom"].set_visible(False)
ax.set_title(title, loc="left", fontsize=12)
axb.bar([1, 2, 3, 4], cs**2, color=TEAL, width=0.6)
axb.set_xticks([1, 2, 3, 4]); axb.set_xticklabels([r"$E_1$", r"$4E_1$", r"$9E_1$", r"$16E_1$"], fontsize=12)
axb.set_ylim(0, 1.08); axb.set_yticks([0, 0.5, 1]); axb.tick_params(axis="y", labelsize=10.5)
if col == 0:
axb.set_ylabel("probability", fontsize=11)
else:
axb.set_yticklabels([])
axm = fig.add_subplot(gs[:, 1]); axm.axis("off")
axm.annotate("", xy=(0.95, 0.55), xytext=(0.05, 0.55), xycoords="axes fraction",
arrowprops=dict(arrowstyle="simple,head_length=0.9,head_width=0.9,tail_width=0.35", color=PURPLE, alpha=0.8))
axm.text(0.5, 0.68, "measure $E$", ha="center", fontsize=12.5, color=PURPLE)
axm.text(0.5, 0.49, "reading: $9E_1$", ha="center", va="top", fontsize=12.5, color=PURPLE)
fig.text(0.5, 0.01, r"measuring again right away gives $9E_1$ every time; a fresh copy of the old state gives $E_1$, $4E_1$ or $9E_1$",
ha="center", fontsize=11.5, color=GRAY)
fig.subplots_adjust(left=0.06, right=0.99, top=0.9, bottom=0.14)
plt.show()
Fig. One energy measurement on the superposition of the example. Before: three possible readings, with probabilities , and . The reading happens to be . After: the state is , and all the probability sits on .
Orthogonal eigenfunctions are mutually exclusive outcomes: a system found in has zero overlap with . A single copy never returns a mixture of readings; the mixture lives in the statistics of many copies.
What happens physically during the collapse is the oldest open question of quantum mechanics. The Copenhagen interpretation, worked out by Bohr and Heisenberg in the 1920s and the one most often taught, holds that a system has no definite value of before it is measured; the theory predicts only the probabilities, and the measurement produces one outcome. Schrödinger found this absurd and made his point with a cat:
Compatible and incompatible observables¶
If , the two operators share eigenfunctions (Operators 2). A reading of leaves the system in a shared eigenfunction, so is sharp too, and measuring does not disturb the value of . Such observables are compatible: energy and momentum of a free particle, or , and for hydrogen.
If they do not commute, they have no complete set of eigenfunctions in common. Then an eigenfunction of one is a superposition of many eigenfunctions of the other, and a state with a sharp value of one has a spread in the other. Position and momentum, with , are the cleanest case.
Position and momentum: spikes and plane waves¶
The eigenfunctions of momentum are the plane waves of Operators 2, . Each has one momentum, and is the same everywhere: a plane wave is spread evenly over all positions.
The eigenfunctions of position are spikes. A function that vanishes everywhere except at satisfies , because the factor only matters where is nonzero. The ideal spike, infinitely narrow with area 1, is the delta function of Appendix A.6. On a grid it is easier: is the diagonal matrix of Operators 1, and its eigenvectors are the columns with a single 1, one grid point each.
| operator | eigenfunctions | sharp value | spread over |
|---|---|---|---|
| spikes | position | every momentum | |
| plane waves | momentum | every position |
Each set is built from the other. A spike is a sum of plane waves of every momentum, all with the same weight, which add up at one point and cancel everywhere else. A plane wave is a sum of spikes at every position, all of the same size:
Source
# synced: two_bases
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.colors import to_rgb
plt.rcParams.update({"axes.spines.top": False, "axes.spines.right": False})
x = np.linspace(-6, 6, 1200) # hbar = 1
cols = [tuple((1 - f) * np.array(to_rgb(PURPLE)) + f * np.array(to_rgb(ORANGE))) for f in np.linspace(0, 1, 6)]
K, dp = 30, 0.25 # momenta -K dp ... K dp, equal weights
spike = sum(np.cos(k * dp * x) for k in range(-K, K + 1)) / (2 * K + 1)
p0, xj = 2.0, np.arange(-6, 6.01, 0.25) # the plane wave e^{i p0 x}, sampled on a grid
fig, axs = plt.subplots(2, 2, figsize=(10, 4.4), gridspec_kw={"hspace": 0.7, "wspace": 0.12})
for k, col in enumerate(cols):
axs[0, 0].plot(x, np.cos(0.6 * k * x), color=col, lw=1.3, alpha=0.9)
axs[0, 0].set_title(r"a few of the plane waves $e^{ipx/\hbar}$, all of equal weight", loc="left", fontsize=12.5)
axs[1, 0].fill_between(x, spike, color=TEAL, alpha=0.18, lw=0); axs[1, 0].plot(x, spike, color=TEAL, lw=2.4)
axs[1, 0].set_title(r"their sum: a spike, an eigenfunction of $\hat{x}$", loc="left", fontsize=12.5)
axs[0, 1].vlines(xj, 0, np.cos(p0 * xj), color=TEAL, lw=2.2)
axs[0, 1].plot(xj, np.cos(p0 * xj), "o", color=TEAL, ms=3.5)
axs[0, 1].set_title(r"spikes at every $x_j$, each weighted by $e^{ip_0x_j/\hbar}$", loc="left", fontsize=12.5)
axs[1, 1].plot(x, np.cos(p0 * x), color=ORANGE, lw=2.4)
axs[1, 1].set_title(r"their sum: a plane wave, an eigenfunction of $\hat{p}$", loc="left", fontsize=12.5)
for ax in axs.flat:
ax.axhline(0, color=GRAY, lw=0.6)
ax.set_xlim(-6, 6); ax.set_ylim(-1.15, 1.15); ax.set_yticks([]); ax.spines["left"].set_visible(False)
ax.set_xticks([-6, -3, 0, 3, 6]); ax.tick_params(labelsize=10.5)
for ax in axs[1]:
ax.set_xlabel(r"position $x$", fontsize=12)
for j, (head, col) in enumerate(((r"a spike = many momenta", TEAL), (r"a plane wave = many positions", ORANGE))):
fig.text(0.27 + 0.485 * j, 0.965, head, ha="center", va="center", fontsize=14, weight="bold", color=col)
fig.text(0.27 + 0.485 * j, 0.5, "add them up", ha="center", va="center", fontsize=11, color=GRAY, style="italic")
fig.text(0.27 + 0.485 * j, 0.465, "\u2193", ha="center", va="center", fontsize=15, color=GRAY)
fig.subplots_adjust(left=0.02, right=0.99, top=0.84, bottom=0.12)
plt.show()
Fig. Each kind of eigenfunction is a sum of the other kind (real parts drawn). Left: plane waves of many momenta with equal weights add up to a spike, a sharp position. Right: spikes at every grid point, each weighted by the value of there, add up to a plane wave, a sharp momentum.
So any state can be written either way. Write for the spike at and for the plane wave . In the spikes, the components of are simply its values, : the wavefunction we have drawn all along is the expansion in position eigenfunctions, just as the column of samples was on the grid.
In the plane waves, is a sum of many momenta, . Momentum can take any value, not just a list of them, so the sum becomes an integral over and the coefficients become a function . Each coefficient is still a projection, as for the box states, . This pair of integrals is the Fourier transform:
is the momentum wavefunction: the same state, written in the other coordinate system. The factor makes .
Derivation: complete the square
Put into the integral for and combine the exponents. With ,
The shifted Gaussian integrates to , so . That is the Gaussian above with , and the constants in front work out to its normalization.
Now squeeze and widen the Gaussian, and watch which plane waves it is made of:
Fig. A Gaussian built from plane waves. Drag the slider to squeeze or widen it. Top left: seven of the plane waves, each drawn with its weight; all of them crest together at . Below them: the sum over all momenta, the Gaussian itself. Right: the weights , with the seven waves marked in matching colours. Wide in position, the packet holds a narrow band of momenta and is nearly one plane wave. Squeezed, it needs a wide band: waves of very different wavelength agree only near and cancel everywhere else.
Squeeze the Gaussian, : it becomes a spike, an eigenfunction of , and flattens. A sharp position holds every momentum, with equal weight.
Widen it, : it becomes the plane wave , an eigenfunction of , and collapses onto . A sharp momentum is spread over every position.
Neither limit is a normalizable state: a plane wave never ends, and a spike is infinitely tall. A real particle is always in between, a wave packet: a bundle of plane waves with momenta near that add up near the center and cancel elsewhere. This is the packet promised in Waves, and the one that tunnels through barriers in Tunneling and spreads as it flies in Time Dependence. Its gives the momentum readings, and its center moves at .
Energy and momentum in a box¶
In a box, and share no eigenfunctions either: the plane waves do not vanish at the walls. Compare two states, each with its energy and momentum readings. A box state has a sharp energy; a Gaussian packet has a definite direction of motion:
Source
# synced: box_momentum
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})
x = np.linspace(0, 1, 2000) # L = hbar = 1: p in units of pi hbar / L
p = np.linspace(-10, 10, 1000)
kern = np.exp(-1j * np.outer(p * np.pi, x)) / np.sqrt(2 * np.pi)
ns = np.arange(1, 11)
phis = np.sqrt(2) * np.sin(np.outer(ns, np.pi * x))
packet = np.exp(-(x - 0.5)**2 / (4 * 0.1**2)) * np.exp(1j * 5 * np.pi * x) # |psi|^2 of width 0.1 L, p0 = 5 pi hbar / L
packet = packet / np.sqrt(np.trapezoid(np.abs(packet)**2, x))
rows = [(phis[4].astype(complex), r"box state $\psi_5$", r"energy: one value, sharp", "momentum: two humps, not sharp"),
(packet, "Gaussian packet, moving right", "energy: many values, spread", "momentum: one hump, moving right")]
fig, axs = plt.subplots(2, 3, figsize=(10, 4.9), gridspec_kw={"width_ratios": [1, 0.85, 1.1], "hspace": 0.62, "wspace": 0.22})
for r, (psi, name, e_note, p_note) in enumerate(rows):
axs_, axe, axp = axs[r]
dens = np.abs(psi)**2
axs_.fill_between(x, dens, color=CARDINAL, alpha=0.16, lw=0); axs_.plot(x, dens, color=CARDINAL, lw=1.4)
axs_.plot(x, psi.real, color=TEAL, lw=2.2, label=r"Re $\psi$")
axs_.axhline(0, color=GRAY, lw=0.6)
axs_.set_xlim(0, 1); axs_.set_ylim(-2.3, 4.4); axs_.set_yticks([]); axs_.spines["left"].set_visible(False)
axs_.set_xticks([0, 0.5, 1]); axs_.set_xticklabels(["0", "L/2", "L"], fontsize=11)
axs_.set_title(name, loc="left", fontsize=12.5)
c2 = np.abs(np.trapezoid(phis * psi, x, axis=1))**2
axe.bar(ns, c2, color=PURPLE, width=0.7)
axe.set_xlim(0.3, 10.7); axe.set_ylim(0, 1.12); axe.set_yticks([0, 0.5, 1]); axe.tick_params(labelsize=10.5)
axe.set_xticks([1, 5, 10])
axe.set_title(e_note, loc="left", fontsize=12.5)
amp = np.trapezoid(kern * psi, x, axis=1)
pd = np.abs(amp)**2 * np.pi # density per unit of p / (pi hbar / L)
axp.fill_between(p, pd, color=ORANGE, alpha=0.25, lw=0); axp.plot(p, pd, color=ORANGE, lw=2.2)
for sgn in (-5, 5):
axp.axvline(sgn, color=GRAY, lw=1, ls="--")
axp.set_xlim(-10, 10); axp.set_ylim(0, 1.25 * pd.max()); axp.set_yticks([]); axp.spines["left"].set_visible(False)
axp.set_xticks([-10, -5, 0, 5, 10]); axp.tick_params(labelsize=10.5)
axp.set_title(p_note, loc="left", fontsize=12.5)
axs[1, 0].plot([], [], color=CARDINAL, lw=1.4, label=r"$|\psi|^2$")
axs[1, 0].legend(loc="upper left", frameon=False, fontsize=11, handlelength=1.4, borderaxespad=0.1)
axs[1, 0].set_xlabel(r"position $x$", fontsize=11.5)
axs[1, 1].set_xlabel(r"level $n$ ($E_n = n^2E_1$)", fontsize=11.5)
axs[1, 2].set_xlabel(r"momentum $p$ (units of $\pi\hbar/L$)", fontsize=11.5)
fig.subplots_adjust(left=0.02, right=0.99, top=0.93, bottom=0.11)
plt.show()
Fig. Two states in a box, each with its energy and momentum readings. Top: the box state gives one energy, , every time. Its momentum readings fall in two humps near (dashed), because the sine is half a wave moving right and half moving left, ; the walls cut the wave off after a length , which broadens each hump by about . Bottom: a Gaussian packet moving right at about the same speed. Its momentum readings form one hump on the positive side, but its energy readings spread over several levels. Neither state is sharp in both.
The uncertainty principle¶
How much spread the commutator forces is the uncertainty principle, the general form of the relation met in Chapter 1:
For position and momentum the commutator is the constant , so its average is in every state, and the bound is the same for every state. Squeezing the position spread widens the momentum spread:
Source
# synced: uncertainty_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})
nf = 40
sx = np.exp(np.log(1.4) + (np.log(0.3) - np.log(1.4)) * 0.5 * (1 - np.cos(2 * np.pi * np.arange(nf) / nf)))
x = np.linspace(-4, 4, 500) # hbar = 1
fig = plt.figure(figsize=(9, 3.4))
gs = fig.add_gridspec(1, 3, width_ratios=[1, 1, 1.05], wspace=0.35)
axx, axp, axu = (fig.add_subplot(gs[0, j]) for j in range(3))
(lx,) = axx.plot([], [], color=TEAL, lw=2.4); bx = [axx.fill_between(x, 0 * x, color=TEAL, alpha=0.2, lw=0)]
(lp,) = axp.plot([], [], color=CARDINAL, lw=2.4); bp = [axp.fill_between(x, 0 * x, color=CARDINAL, alpha=0.2, lw=0)]
for ax, lab in ((axx, r"position $x$"), (axp, r"momentum $p$")):
ax.set_xlim(-4, 4); ax.set_ylim(0, 1.45); ax.set_yticks([]); ax.spines["left"].set_visible(False)
ax.set_xlabel(lab, fontsize=12); ax.tick_params(labelsize=10.5)
dx = np.geomspace(0.08, 3, 200)
axu.fill_between(dx, 0.04, 0.5 / dx, color=GRAY, alpha=0.25, lw=0)
axu.plot(dx, 0.5 / dx, color="k", lw=1.6)
axu.plot(dx, 0.568 / dx, color=PURPLE, lw=1.4, ls="--")
axu.text(0.1, 0.35, "forbidden:\n" + r"$\sigma_x\sigma_p < \hbar/2$", fontsize=11, color=GRAY, va="bottom")
(gdot,) = axu.plot([], [], "o", color=TEAL, ms=10, zorder=5, label=r"Gaussian: $\sigma_x\sigma_p = 0.50\,\hbar$")
(bdot,) = axu.plot([], [], "s", color=PURPLE, ms=9, zorder=5, label=r"box ground state: $0.57\,\hbar$")
axu.set_xscale("log"); axu.set_yscale("log"); axu.set_xlim(0.08, 3); axu.set_ylim(0.04, 12)
axu.set_xlabel(r"$\sigma_x$", fontsize=12); axu.set_ylabel(r"$\sigma_p$", fontsize=12); axu.tick_params(labelsize=10)
axu.legend(loc="upper right", frameon=False, fontsize=10.5, handletextpad=0.3, borderaxespad=0.1)
fig.subplots_adjust(left=0.02, right=0.98, top=0.86, bottom=0.17)
def update(i):
s = sx[i]; sp = 0.5 / s
rx = np.exp(-x**2 / (2 * s**2)) / np.sqrt(2 * np.pi * s**2)
rp = np.exp(-x**2 / (2 * sp**2)) / np.sqrt(2 * np.pi * sp**2)
lx.set_data(x, rx); bx[0].remove(); bx[0] = axx.fill_between(x, rx, color=TEAL, alpha=0.2, lw=0)
lp.set_data(x, rp); bp[0].remove(); bp[0] = axp.fill_between(x, rp, color=CARDINAL, alpha=0.2, lw=0)
axx.set_title(rf"$|\psi(x)|^2$: $\sigma_x = {s:.2f}$", loc="left", fontsize=12.5)
axp.set_title(rf"$|\phi(p)|^2$: $\sigma_p = {sp:.2f}$", loc="left", fontsize=12.5)
gdot.set_data([s], [sp]); bdot.set_data([s], [0.568 / s])
axu.set_title(rf"$\sigma_x\sigma_p = {s * sp:.2f}\,\hbar$", loc="left", fontsize=12.5)
ani = FuncAnimation(fig, update, frames=nf, interval=120, blit=False)
plt.close(fig)
HTML(ani.to_jshtml())Fig. A Gaussian squeezed and released. Left: the position distribution. Middle: the momentum distribution, which widens as the position narrows. Right: the two spreads on log axes. The gray region below is forbidden. The Gaussian rides exactly on the boundary, the least uncertain state possible; the ground state of a box of matching size (square) sits a little above it, at .
Problems¶
Problem 1: Complex coefficients¶
A particle in a box is in the state . What energies can be measured, with what probabilities? Find and .
Solution
Problem 2: Averages of observables are real¶
Show that is a real number whenever is Hermitian, for every state .
Solution
Take the complex conjugate and use , then the Hermitian property:
A number equal to its own conjugate is real. The expansion gives the same conclusion: is a sum of real eigenvalues with positive weights.
Problem 3: Two measurements in a row¶
A particle in a box is in the state . (a) What is the probability that an energy measurement gives ? (b) The reading is in fact . What is the state right after, and what does a second energy measurement give? (c) Next the position is measured. Where is the particle never found?
Solution
(a) .
(b) The reading leaves the particle in . A second energy measurement returns with probability one: is an eigenfunction of .
(c) The position distribution is now , which vanishes at the node (and at the walls). Before the energy measurement the distribution was , which does not vanish at the center: the first measurement changed what the second one sees.
Problem 4: The spread of the parabola¶
For the parabola of the worked example, 99.86 percent of the energy readings give exactly . Find , using and .
Solution
Problem 5: Expanding a tent¶
The tent state rises linearly from zero at to a peak at and falls back linearly to zero at . Normalize it, then find its expansion coefficients in box states, either by integrating by parts or numerically with the grid code above. What is the probability of measuring ? How many states hold 99.9 percent of the probability? Compare with the parabola, and explain the difference using the sharp corner of the tent.
Problem 6: Momentum of the ground state¶
Compute the momentum distribution of the box ground state numerically, from . Check that it is normalized and that its spread is . Why is it largest at rather than at ?
Problem 7: An uncertainty relation for energy and position¶
Use (Problem 5 of Operators 2 leads to it) in the general uncertainty principle to show . Evaluate the bound for a box eigenstate and for a free packet moving with average momentum . Why does a stationary state escape any constraint?
Problem 8: Phases you can see¶
The states in a box give identical energy statistics. Compute for each, and sketch . Which measurement tells them apart, and why can no energy measurement do it?