codex/refraction-interface-matched-wavefronts.py

refraction-interface-matched-wavefronts.py
"""Matched monochromatic phase at a planar air/water boundary.

Tested with Python 3.14, matplotlib 3.10.7 and numpy 2.3.5.
Run from _media; coordinates use lambda_0 as their length unit.
This stipulated n_air=1, n_water=4/3 model is not measured data.
"""

from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Arc


def main():
    plt.rcParams.update({"font.size": 12, "axes.titlesize": 14})
    fig, axes = plt.subplots(1, 2, figsize=(10.8, 6.2), layout="constrained")
    colors = ["#176b92", "#bd5125", "#65791b", "#843b91"]
    for ax, qx, title in zip(
        axes, [0.5, 0.0], ["Oblique incidence", "Normal incidence: no bending"]
    ):
        ax.axhspan(0, 2.9, color="#edf6fb")
        ax.axhline(0, color="#333333", lw=1.8)
        ax.axvline(0, color="#666666", ls=":", lw=1.4)
        x = np.linspace(-2.65, 2.65, 1001)
        qs = []
        for n, lo, hi in [(1, -2.9, 0), (4 / 3, 0, 2.9)]:
            qz = np.sqrt(n * n - qx * qx)
            qs.append(qz)
            for m in range(-6, 7):
                z = (m - qx * x) / qz
                mask = (z >= lo) & (z <= hi)
                ax.plot(x[mask], z[mask], color=colors[m % 4], lw=1.5)
            start, end = ((-2.3, -0.6) if lo < 0 else (0.5, 2.5))
            ray = lambda z: (qx * z / qz, z)
            ax.annotate("", xy=ray(end), xytext=ray(start),
                        arrowprops={"arrowstyle": "-|>", "lw": 3, "color": "#111111"})
        if qx:
            crossings = np.arange(-1, 2) / qx
            ax.scatter(crossings, np.zeros_like(crossings), color="#111111", s=25, zorder=5)
            for qz, side in zip(qs, [-1, 1]):
                theta = np.degrees(np.arctan2(qx, qz))
                angles = (270 - theta, 270) if side < 0 else (90 - theta, 90)
                ax.add_patch(Arc((0, 0), 1.2, 1.2, theta1=angles[0], theta2=angles[1],
                                 color="#111111", lw=1.5))
            ax.text(-0.73, -0.58, r"$30^\circ$", ha="right", bbox={"facecolor": "white", "edgecolor": "none", "pad": 1})
            ax.text(0.75, 0.64, r"$22.0243^\circ$", bbox={"facecolor": "white", "edgecolor": "none", "pad": 1})
            ax.text(0, 3.25, r"Same interface crossings: $\Delta x=2\lambda_0$", ha="center", fontsize=11)
        else:
            ax.text(0, 3.25, "No phase variation along the interface", ha="center", fontsize=11)
        for y, label in [(-2.68, r"Air: $n=1$, $\lambda_{\rm a}=\lambda_0$"),
                         (2.70, r"Water: $n=4/3$, $\lambda_{\rm w}=3\lambda_0/4$")]:
            ax.text(-2.48, y, label, va="center", fontsize=11,
                    bbox={"facecolor": "white", "edgecolor": "none", "pad": 3})
        ax.set(xlim=(-2.65, 2.65), ylim=(3.55, -3.12), title=title,
               xlabel=r"Along interface $x/\lambda_0$", ylabel=r"Into water $z/\lambda_0$")
        ax.set_aspect("equal")
        ax.set_yticks([-2, -1, 0, 1, 2])
    fig.suptitle("Refraction: matched phase, changed wavefront spacing", fontsize=17)
    fig.savefig(Path(__file__).stem + ".png", dpi=120, facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()