codexetendue-area-angle-exchange.py
etendue-area-angle-exchange.py
"""Draw filled endpoint bundles of equal etendue; output PNG to the CWD.
Tested with Python 3.14, matplotlib 3.10.7 and numpy 2.3.5.
Run from _media with MPLBACKEND=Agg and a writable MPLCONFIGDIR.
"""
from pathlib import Path
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
import numpy as np
def main():
plt.rcParams.update({"font.size": 11, "figure.facecolor": "white"})
fig, axes = plt.subplots(2, 2, figsize=(9.2, 7.0))
colors = ["#2166ac", "#16846b"]
diameters = [10.0, 20.0]
direction_radii = [0.5, 0.25]
angles = np.degrees(np.arcsin(direction_radii))
for col, (diameter, p, color) in enumerate(
zip(diameters, direction_radii, colors)
):
ax = axes[0, col]
ax.add_patch(Circle((0, 0), diameter / 2, facecolor=color, alpha=0.18))
ax.add_patch(Circle((0, 0), diameter / 2, fill=False, edgecolor=color, lw=2))
ax.plot([0, diameter / 2], [0, 0], color=color, lw=2)
ax.set(xlim=(-12, 12), ylim=(-12, 12), xlabel="x (mm)", ylabel="y (mm)")
ax.set_title(
("Entrance" if col == 0 else "Expanded output")
+ f": D = {diameter:g} mm\n"
+ (r"$A_1=25\pi\ \mathrm{mm}^2$" if col == 0
else r"$A_2=100\pi\ \mathrm{mm}^2=4A_1$")
)
ax = axes[1, col]
ax.add_patch(Circle((0, 0), p, facecolor=color, alpha=0.18))
ax.add_patch(Circle((0, 0), p, fill=False, edgecolor=color, lw=2))
ax.plot([0, p], [0, 0], color=color, lw=2)
ax.set(xlim=(-0.65, 0.65), ylim=(-0.65, 0.65), xlabel=r"$p_x=n s_x$", ylabel=r"$p_y=n s_y$")
ax.set_title(
f"Direction disk: half-angle {angles[col]:.4g}°\n"
+ (r"$\pi\sin^2\theta_1=\pi/4$" if col == 0
else r"$\pi\sin^2\theta_2=\pi/16$")
)
for ax in axes.flat:
ax.set_aspect("equal")
ax.axhline(0, color="#888888", lw=0.6, zorder=0)
ax.axvline(0, color="#888888", lw=0.6, zorder=0)
ax.grid(alpha=0.15)
ax.spines[["top", "right"]].set_visible(False)
fig.suptitle("Lossless area–angle exchange: filled bundles in n = 1", fontsize=15)
fig.text(
0.5, 0.025,
r"$G=A\,\pi\sin^2\theta=25\pi^2/4=61.685\ \mathrm{mm}^2\,\mathrm{sr}$ at both ports"
+ "\nSpatial area × 4; direction-disk area ÷ 4. Endpoint schematic, not a lens prescription.",
ha="center", va="bottom", fontsize=11,
)
fig.subplots_adjust(top=0.86, bottom=0.16, hspace=0.69, wspace=0.42)
fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), dpi=120, facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()