codexetendue-aperture-truncation-concentration.py
etendue-aperture-truncation-concentration.py
"""Compare opaque aperture clipping with the filled-cone concentration bound.
Output the matching PNG basename to the CWD. Tested with Python 3.14,
matplotlib 3.10.7 and numpy 2.3.5; run from _media with MPLBACKEND=Agg.
"""
from pathlib import Path
import matplotlib.pyplot as plt
from matplotlib.patches import Arc, Circle
import numpy as np
def main():
plt.rcParams.update({"font.size": 11, "figure.facecolor": "white"})
fig, (clip, port) = plt.subplots(1, 2, figsize=(11, 5.5), gridspec_kw={"width_ratios": [1, 1.55]})
blue, green, red = "#2166ac", "#16846b", "#a23b3b"
clip.add_patch(Circle((0, 0), 5, facecolor="#f6dcdc", edgecolor=red, lw=2))
clip.add_patch(Circle((0, 0), 3, facecolor="#d7e9f6", edgecolor=blue, lw=2))
clip.annotate("6 mm stop", xy=(1.5, 1.7), xytext=(0, 6.5), ha="center", color=blue,
arrowprops={"arrowstyle": "->", "color": blue})
clip.text(0, 0, "transmitted\n36%", color=blue, ha="center", va="center", weight="bold")
clip.text(0, -4, "blocked annulus", color=red, ha="center", fontsize=10)
clip.set(xlim=(-6, 6), ylim=(-6, 7.5), xlabel="x (mm)", ylabel="y (mm)")
clip.set_aspect("equal")
clip.set_title("Clipping in the aperture plane\nIncident diameter: 10 mm", pad=15)
clip.spines[["top", "right"]].set_visible(False)
clip.text(0.5, -0.19, "Same 30° directional cone\n"
+ r"$G_{\rm transmitted}=0.36\,G_{\rm incident}$",
transform=clip.transAxes, ha="center", va="top")
# The dashed envelope connects ports symbolically: no reflecting shape is asserted.
port.plot([1.8, 5.5], [2, 1], "--", color="#999999", lw=1.5)
port.plot([1.8, 5.5], [-2, -1], "--", color="#999999", lw=1.5)
port.plot([1.8, 1.8], [-2, 2], color=blue, lw=4)
port.plot([5.5, 5.5], [-1, 1], color=green, lw=4)
for angle in [-30, 0, 30]:
t = np.radians(angle)
port.annotate("", xy=(1.8, 0), xytext=(1.8 - 1.6 * np.cos(t), 1.6 * np.sin(t)),
arrowprops={"arrowstyle": "->", "color": blue, "lw": 1.5})
port.add_patch(Arc((1.8, 0), 1.4, 1.4, theta1=150, theta2=180, color=blue))
port.text(0.72, 0.33, "30°", color=blue, fontsize=10)
for angle in [-90, -60, -30, 0, 30, 60, 90]:
t = np.radians(angle)
port.annotate("", xy=(5.5 + 1.45 * np.cos(t), 1.45 * np.sin(t)), xytext=(5.5, 0),
arrowprops={"arrowstyle": "->", "color": green, "lw": 1.2})
port.add_patch(Arc((5.5, 0), 2.2, 2.2, theta1=-90, theta2=90, color=green, lw=1))
port.text(1.8, 2.43, "Entrance: 10 mm", color=blue, ha="center")
port.text(5.5, 2.43, "Receiver: 5 mm", color=green, ha="center")
port.text(3.65, 0.15, "Symbolic\nconnection", color="#666666", ha="center", va="center", fontsize=10)
port.text(1.8, -2.95, "Filled input cone\nat every position", color=blue, ha="center")
port.text(5.5, -2.95, "Full output hemisphere\n(limit: half-angle 90°)", color=green, ha="center")
port.set(xlim=(-0.25, 7.3), ylim=(-3.65, 3.1))
port.set_aspect("equal")
port.axis("off")
port.set_title("Concentration needs angular room\nAll input rays retained; n = 1 at both ports", pad=15)
port.text(0.5, -0.13,
r"$C=A_{\rm in}/A_{\rm out}\leq1/\sin^2(30^\circ)=4$",
transform=port.transAxes, ha="center", va="top", fontsize=12)
fig.suptitle("Two different operations on a 10 mm, 30° filled bundle", fontsize=15)
fig.subplots_adjust(left=0.07, right=0.98, top=0.80, bottom=0.23, wspace=0.24)
fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), dpi=120, facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()