codex/past-exam-of-the-physics-a-course-of-the-university-of-cambridge/2010/paper-2-asymmetric-states.py

past-exam-of-the-physics-a-course-of-the-university-of-cambridge/2010/paper-2-asymmetric-states.py
"""Original illustrative asymmetric-well states; Python 3.14, numpy, matplotlib.

No source image/data are used. PNG output is a basename in the current directory.
"""
import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt


def model(n=700):
    length = 5.
    x = np.linspace(0, length, n+2)[1:-1]
    dx = length/(n+1)
    potential = np.where(x < 2, 0., np.where(x < 3, 30., 12.))
    matrix = (np.diag(2/dx**2 + potential)
              + np.diag(np.full(n-1, -1/dx**2), 1)
              + np.diag(np.full(n-1, -1/dx**2), -1))
    energy, states = np.linalg.eigh(matrix)
    return x, energy[:3], states[:, :3]


def main():
    x, energy, state = model()
    assert energy[0] < energy[1] < 12 < energy[2] < 30
    plt.rcParams.update({"font.size": 11, "figure.facecolor": "white"})
    fig, axes = plt.subplots(3, 1, figsize=(8.1, 6), sharex=True)
    colors = ["#175baa", "#bb4f14", "#136f63"]
    for i, (ax, color) in enumerate(zip(axes, colors)):
        psi = state[:, i]
        if psi[np.argmax(abs(psi))] < 0:
            psi = -psi
        psi = psi/max(abs(psi))
        xx = np.r_[0, x, 5]
        yy = np.r_[0, psi, 0]
        ax.axvspan(2, 3, color="#cbd5e1", alpha=.6)
        if i < 2:
            ax.axvspan(3, 5, color="#e2e8f0", alpha=.7)
        ax.plot(xx, yy, color=color, lw=2.2)
        ax.axhline(0, color=".6", lw=.6)
        for wall in [0, 5]:
            ax.axvline(wall, color=".3", lw=1.5)
        sig = np.sign(psi[abs(psi) > 1e-7])
        assert np.count_nonzero(sig[1:] != sig[:-1]) == i
        ax.text(.03, .83, f"State {i}: {i} interior node" + ("s" if i != 1 else ""),
                transform=ax.transAxes)
        ax.set(ylim=(-1.2, 1.45), yticks=[-1, 0, 1], ylabel=r"$\psi$ (scaled)")
        ax.grid(axis="x", alpha=.13)
        if i == 2:
            zoom = ax.inset_axes([.13, .06, .37, .48])
            selected = x <= 2
            zoom.plot(x[selected], psi[selected], color=color, lw=1.5)
            zoom.axhline(0, color=".5", lw=.6)
            zoom.set_xlim(0, 2)
            zoom.set_ylim(-.012, .012)
            zoom.set_xticks([0, 1, 2])
            zoom.set_yticks([])
            zoom.tick_params(labelsize=8)
            zoom.set_title("Left well (magnified)", fontsize=9, pad=2)
    axes[0].text(2.5, .55, "Barrier", ha="center", fontsize=10)
    axes[0].text(4, .55, "Forbidden", ha="center", fontsize=10)
    axes[2].text(4, .7, "Right well allowed", ha="center", fontsize=10)
    axes[-1].set(xlabel="Position (arbitrary units)", xlim=(-.05, 5.05))
    fig.suptitle("An original asymmetric-well model: zero, one and two nodes")
    fig.tight_layout()
    fig.savefig("paper-2-asymmetric-states.png", dpi=160, facecolor="white")
    print({"illustrative_energies": energy.tolist(), "right_floor": 12.,
           "barrier_height": 30., "outer_boundary": "Dirichlet"})


if __name__ == "__main__":
    main()