codex/fizeau-counterflow-paths.py

fizeau-counterflow-paths.py
"""Original Fizeau path-accounting schematic; Python 3.14, matplotlib 3.10.7.

Run from _media to emit fizeau-counterflow-paths.png. The optical exchange
block is a schematic connection, not a scaled ray trace or historical copy.
"""
from pathlib import Path

import matplotlib.pyplot as plt
from matplotlib.patches import FancyArrowPatch, FancyBboxPatch, Rectangle


BLUE = '#1265a8'
ORANGE = '#b94713'
GREY = '#52616b'


def arrow(ax, start, end, color, lw=2.5, size=15):
    ax.add_patch(FancyArrowPatch(start, end, arrowstyle='-|>',
                                mutation_scale=size, linewidth=lw,
                                color=color, zorder=5))


def main():
    plt.rcParams.update({'font.size': 12, 'font.family': 'DejaVu Sans'})
    fig, ax = plt.subplots(figsize=(11, 6.5), dpi=100, facecolor='white')
    fig.subplots_adjust(left=.025, right=.985, top=.91, bottom=.035)
    ax.set(xlim=(0, 12), ylim=(0, 6.7))
    ax.axis('off')
    fig.suptitle('Moving water: count both passages of each beam',
                 fontsize=18, fontweight='bold', y=.975)
    ax.text(6, 6.35, 'Tube exchange on return makes the two flow effects add',
            ha='center', fontsize=13, color=GREY)

    for low in (4.0, 1.7):
        ax.add_patch(Rectangle((3.1, low), 5.25, .70,
                               facecolor='#edf6fa', edgecolor='#859ca8', lw=1.5))
        for x in (3.14, 8.31):
            ax.plot([x, x], [low, low + .7], color='#859ca8', lw=3)
    ax.annotate('', xy=(8.35, 5.22), xytext=(3.1, 5.22),
                arrowprops={'arrowstyle': '|-|', 'color': GREY})
    ax.text(5.72, 5.35, r'one tube length $\ell$', ha='center', color=GREY)
    arrow(ax, (4.2, 4.95), (6.7, 4.95), GREY, lw=3, size=18)
    ax.text(6.95, 4.94, r'water $+u$', va='center', color=GREY)
    arrow(ax, (6.7, 1.37), (4.2, 1.37), GREY, lw=3, size=18)
    ax.text(6.95, 1.37, r'water $-u$', va='center', color=GREY)

    # Separate the counterpropagating rays slightly so their directions are legible.
    for y, color, direction in ((4.52, BLUE, 1), (4.18, ORANGE, -1),
                                 (2.22, BLUE, -1), (1.88, ORANGE, 1)):
        ax.plot([2.75, 8.7], [y, y], color=color, lw=2.5, zorder=4)
        x1, x2 = (5.0, 6.0) if direction == 1 else (6.0, 5.0)
        arrow(ax, (x1, y), (x2, y), color)
    ax.text(3.5, 4.56, 'A', color=BLUE, va='bottom', fontweight='bold')
    ax.text(7.7, 4.12, 'B', color=ORANGE, va='top', fontweight='bold')
    ax.text(7.7, 2.26, 'A', color=BLUE, va='bottom', fontweight='bold')
    ax.text(3.5, 1.82, 'B', color=ORANGE, va='top', fontweight='bold')

    ax.add_patch(FancyBboxPatch((8.78, 1.45), 2.6, 3.72,
                                boxstyle='round,pad=.06',
                                facecolor='#fafafa', edgecolor='#a9a9a9', lw=1.3))
    ax.plot([8.7, 9.1, 10.8, 9.1, 8.7], [4.52, 4.52, 3.2, 2.22, 2.22],
            color=BLUE, lw=2.5)
    ax.plot([8.7, 9.25, 10.6, 9.25, 8.7], [1.88, 1.88, 3.2, 4.18, 4.18],
            color=ORANGE, lw=2.5, linestyle='--')
    ax.text(10.08, 5.34, 'Far telescope\n+ focal mirror', ha='center', fontsize=12)
    ax.text(10.07, 1.02, 'Exchange tubes\non return', ha='center', fontsize=12)
    arrow(ax, (9.18, 4.45), (9.73, 4.02), BLUE, size=13)
    arrow(ax, (9.2, 1.91), (9.73, 2.43), ORANGE, size=13)

    ax.add_patch(FancyBboxPatch((1.48, 1.6), 1.15, 3.25,
                                boxstyle='round,pad=.06',
                                facecolor='#fafafa', edgecolor='#a9a9a9', lw=1.3))
    for y, color in ((4.52, BLUE), (4.18, ORANGE), (2.22, BLUE), (1.88, ORANGE)):
        ax.plot([2.63, 2.75], [y, y], color=color, lw=2.5)
    ax.text(2.06, 3.2, 'Near\ntelescope\n+ slits', ha='center', va='center', fontsize=11)
    ax.text(.70, 5.45, 'Sunlight', ha='center', fontsize=12)
    arrow(ax, (.70, 5.08), (.70, 3.26), '#333333', lw=1.8)
    ax.plot([.40, 1.0], [2.9, 3.5], color='#333333', lw=2)
    arrow(ax, (.84, 3.2), (1.43, 3.2), '#333333', lw=1.8)
    ax.text(.72, 2.35, 'Partial\nmirror', ha='center', fontsize=11)
    arrow(ax, (1.43, 2.75), (.68, 1.25), '#333333', lw=1.8)
    ax.text(.72, .72, 'Recombined\nfringe readout', ha='center', fontsize=11)
    ax.text(5.70, .72, 'A: with flow twice    B: against flow twice',
            ha='center', fontsize=13, fontweight='bold')
    ax.text(5.70, .25, r'Water length per beam: $L=\ell+\ell=2\ell$',
            ha='center', fontsize=13)
    ax.text(11.85, .12, 'Schematic; paths\nseparated for clarity',
            ha='right', fontsize=9, color=GREY)
    fig.savefig(Path(__file__).stem + '.png', facecolor='white', transparent=False)
    plt.close(fig)


if __name__ == '__main__':
    main()