codex/cherenkov-wavefront-cone-geometry.py

cherenkov-wavefront-cone-geometry.py
"""Original nondispersive Cherenkov construction. Python 3.14, NumPy 2.3.5,
Matplotlib 3.10.7. Run from _media; output is this script's basename.png.
"""
from pathlib import Path
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Arc, Circle


def main():
    beta, n = 0.9, 1.5
    r = 1 / (beta * n)  # distance unit = v*Delta t
    theta = np.arccos(r)
    q = np.array([r*r, r*np.sqrt(1-r*r)])
    p = np.array([1.0, 0.0])
    fig, ax = plt.subplots(figsize=(9.6, 5.3), dpi=130, facecolor='white')
    for center in np.linspace(0, 0.85, 6):
        ax.add_patch(Circle((center, 0), r*(1-center), fill=False,
                            color='#8eacc3', lw=1.2))
    for sign in [1, -1]:
        ax.plot([1, 0.12], [0, sign*0.88*r/np.sqrt(1-r*r)],
                color='#007c83', lw=2.4)
    ax.annotate('', (1.32, 0), (-0.85, 0),
                arrowprops=dict(arrowstyle='->', lw=1.5, color='#333333'))
    ax.annotate('', q, (0, 0),
                arrowprops=dict(arrowstyle='->', lw=2.5, color='#c45121'))
    ax.plot([q[0], 1], [q[1], 0], color='#007c83', lw=2.4)
    ax.add_patch(Arc((0, 0), .50, .50, theta1=0,
                     theta2=np.degrees(theta), color='#c45121', lw=1.8))
    eta = 90-np.degrees(theta)
    ax.add_patch(Arc((1, 0), .43, .43, theta1=180-eta,
                     theta2=180, color='#007c83', lw=1.5))
    # Right-angle marker at the tangency point.
    a = -q/np.linalg.norm(q)*.055
    b = (p-q)/np.linalg.norm(p-q)*.055
    marker = np.array([q+a, q+a+b, q+b])
    ax.plot(marker[:, 0], marker[:, 1], color='#444444', lw=1)
    ax.scatter([0, 1, q[0]], [0, 0, q[1]], color='#333333', s=23, zorder=5)
    ax.text(-.03, -.12, 'O: earlier position', ha='center', fontsize=10)
    ax.text(1.05, -.12, 'P: charge now', ha='left', fontsize=10)
    ax.text(q[0]+.025, q[1]+.03, 'Q: tangency', fontsize=10)
    ax.text(.23, .08, r'$\theta$', color='#c45121', fontsize=14)
    ax.text(.73, .075, r'$\eta$', color='#007c83', fontsize=14)
    ax.text(.08, .39, r'wave normal $\mathbf{k}$', color='#c45121',
            rotation=np.degrees(theta), fontsize=11)
    ax.text(-.66, .86, 'Tangent envelope\n(cone in 3D)', color='#007c83', fontsize=11)
    ax.text(.50, -.25, r'$OP=v\,\Delta t,\quad OQ=(c/n)\,\Delta t$',
            ha='center', fontsize=12)
    ax.text(.50, -1.18,
            r'$\cos\theta=OQ/OP=1/(\beta n)$'+'\n'
            + rf'Chosen $\beta=0.90$, $n=1.50$: $\theta={np.degrees(theta):.2f}^\circ$, '
            + rf'$\eta={eta:.2f}^\circ$', ha='center', fontsize=12)
    ax.set_title('A charge outruns phase fronts in a transparent medium', fontsize=15, pad=15)
    ax.text(.50, 1.13, 'Original constant-index construction; circles are phase fronts, not particle paths',
            ha='center', fontsize=10, color='#444444')
    ax.set_aspect('equal')
    ax.set_xlim(-.85, 1.65)
    ax.set_ylim(-1.36, 1.20)
    ax.axis('off')
    fig.tight_layout()
    fig.savefig(Path(__file__).stem+'.png', facecolor='white')
    plt.close(fig)


if __name__ == '__main__':
    main()