codex/past-exam-of-the-physics-a-course-of-the-university-of-cambridge/2011/paper-2-stepped-state.py

past-exam-of-the-physics-a-course-of-the-university-of-cambridge/2011/paper-2-stepped-state.py
"""Original matched fourth eigenstate; Python 3.14, NumPy, Matplotlib.

Dimensionless illustrative model, not reconstructed source-image data.
Output is a basename in the current working directory.
"""
import json
import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt


def propagate(v, energy, potential, length):
    q = energy - potential
    if abs(q) < 1e-12:
        return np.array([v[0] + length * v[1], v[1]])
    if q > 0:
        k = np.sqrt(q)
        c, s = np.cos(k * length), np.sin(k * length)
        return np.array([c * v[0] + s * v[1] / k,
                         -k * s * v[0] + c * v[1]])
    k = np.sqrt(-q)
    c, s = np.cosh(k * length), np.sinh(k * length)
    return np.array([c * v[0] + s * v[1] / k,
                     k * s * v[0] + c * v[1]])


def boundary(energy, odd):
    v = np.array([0., 1.]) if odd else np.array([1., 0.])
    v = propagate(v, energy, 0., 1.)
    v = propagate(v, energy, 5., 1.)
    return v[1] + np.sqrt(18. - energy) * v[0]


def levels():
    roots = []
    for odd in (False, True):
        grid = np.linspace(.00001, 17.99999, 3001)
        for lo, hi in zip(grid[:-1], grid[1:]):
            if boundary(lo, odd) * boundary(hi, odd) < 0:
                for _ in range(50):
                    mid = (lo + hi) / 2
                    if boundary(lo, odd) * boundary(mid, odd) <= 0:
                        hi = mid
                    else:
                        lo = mid
                roots.append(((lo + hi) / 2, odd))
    return sorted(roots)


def state(x, energy, odd):
    q = abs(x)
    start = np.array([0., 1.]) if odd else np.array([1., 0.])
    at_one = propagate(start, energy, 0., 1.)
    at_two = propagate(at_one, energy, 5., 1.)
    if q <= 1:
        val = propagate(start, energy, 0., q)[0]
    elif q <= 2:
        val = propagate(at_one, energy, 5., q - 1)[0]
    else:
        val = at_two[0] * np.exp(-np.sqrt(18. - energy) * (q - 2))
    return -val if odd and x < 0 else val


def main():
    spectrum = levels()
    energy, odd = spectrum[3]
    assert odd and 5 < energy < 18
    x = np.linspace(-3.5, 3.5, 2801)
    psi = np.array([state(t, energy, odd) for t in x])
    psi /= np.max(abs(psi))
    plt.rcParams.update({"font.size": 11, "figure.facecolor": "white"})
    fig, (ax, bx) = plt.subplots(2, 1, figsize=(8.2, 5.7), sharex=True,
                               gridspec_kw={"height_ratios": [1, 1.6]})
    xx = [-3.5, -2, -2, -1, -1, 1, 1, 2, 2, 3.5]
    vv = [18, 18, 5, 5, 0, 0, 5, 5, 18, 18]
    ax.plot(xx, vv, color="#64748b", lw=2)
    ax.axhline(energy, color="#d97706", ls="--", label="Fourth energy")
    ax.set_ylabel("Illustrative potential")
    ax.set_yticks([0, 5, 18])
    ax.legend(loc="upper center", frameon=False)
    bx.plot(x, psi, color="#175baa", lw=2.2)
    bx.axhline(0, color=".5", lw=.7)
    for a, b in [(-3.5, -2), (2, 3.5)]:
        bx.axvspan(a, b, color="#e2e8f0", alpha=.8)
    for a in [-2, -1, 1, 2]:
        bx.axvline(a, color=".6", ls=":", lw=.8)
    zeros = []
    for i in np.where(psi[:-1] * psi[1:] < 0)[0]:
        zeros.append(x[i] - psi[i] * (x[i+1]-x[i])/(psi[i+1]-psi[i]))
    zeros.append(0.)
    bx.scatter(zeros, np.zeros(len(zeros)), color="#b91c1c", zorder=4, s=28)
    bx.text(0, 1.12, "Odd parity; three nodes", ha="center")
    bx.set_ylim(-1.25, 1.35)
    bx.set_ylabel(r"$\psi$ (arbitrary scale)")
    bx.set_xlabel("Position (arbitrary length units)")
    bx.set_yticks([-1, 0, 1])
    bx.text(-2.75, -.95, "Decaying tail", ha="center", fontsize=9)
    bx.text(2.75, .95, "Decaying tail", ha="center", fontsize=9)
    fig.suptitle("A matched bound state in an illustrative stepped well")
    fig.tight_layout()
    fig.savefig("paper-2-stepped-state.png", dpi=160, facecolor="white")
    print(json.dumps({"levels": spectrum, "fourth_energy": energy,
                      "odd": odd, "nodes": zeros,
                      "boundary_residual": boundary(energy, odd)}))


if __name__ == "__main__":
    main()