codexpolarimeter-apparatus-and-plane-orientation.py
polarimeter-apparatus-and-plane-orientation.py
"""Original polarimeter schematic; Python 3.14, matplotlib 3.10.7, numpy 2.3.5.
Run from _media; writes this script's basename.png to the working directory.
All numerical material properties are stipulated, not measured.
"""
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Circle, FancyArrowPatch, FancyBboxPatch
def main():
plt.rcParams.update({"font.family": "DejaVu Sans", "font.size": 12})
fig, ax = plt.subplots(figsize=(11, 6.4), dpi=100, facecolor="white")
fig.subplots_adjust(left=0.025, right=0.98, bottom=0.10, top=0.84)
ax.set(xlim=(0, 11), ylim=(0, 5), aspect="equal")
ax.axis("off")
blue, orange, gray = "#2463A6", "#C45B15", "#475569"
fig.suptitle("Optical activity: the field direction turns", fontsize=20, y=0.965)
fig.text(0.5, 0.905, "Original ideal schematic · horizontal layout for clarity · not to scale",
ha="center", color=gray)
stages = [(0.75, 1.15, "Light source", "589 nm"),
(2.75, 0.50, "Fixed polarizer", "pass axis: 0°"),
(5.10, 2.15, "Sample tube", "path: 1 dm"),
(7.70, 0.50, "Analyzer", "rotatable axis: θ"),
(9.75, 1.10, "Detector", "intensity readout")]
y = 3.65
for x, width, title, subtitle in stages:
ax.add_patch(FancyBboxPatch((x-width/2, y-0.36), width, 0.72,
boxstyle="round,pad=0.04", facecolor="#F1F5F9",
edgecolor=gray, linewidth=1.4))
ax.text(x, y+0.57, title, ha="center", weight="bold")
ax.text(x, y-0.68, subtitle, ha="center", fontsize=10.5, color=gray)
for left, right in [(1.37, 2.45), (3.05, 3.95), (6.25, 7.40), (8.0, 9.15)]:
ax.annotate("", xy=(right, y), xytext=(left, y),
arrowprops={"arrowstyle": "-|>", "color": blue, "lw": 2})
ax.text(5.10, y, "solution", ha="center", color=blue)
ax.text(5.5, 4.75, "Light travels from source to detector →", ha="center", color=blue)
for x, angle in [(2.75, 0), (7.70, 8)]:
phi = np.deg2rad(angle)
ax.plot([x-0.27*np.sin(phi), x+0.27*np.sin(phi)],
[y-0.27*np.cos(phi), y+0.27*np.cos(phi)], color=orange, lw=3)
cx, cy, radius = 2.15, 1.35, 0.95
ax.add_patch(Circle((cx, cy), radius, fill=False, edgecolor="#CBD5E1", lw=1.5))
ax.plot([cx, cx], [cy-radius, cy+radius], color=blue, lw=2, ls="--")
alpha = np.deg2rad(8)
ax.plot([cx-radius*np.sin(alpha), cx+radius*np.sin(alpha)],
[cy-radius*np.cos(alpha), cy+radius*np.cos(alpha)], color=orange, lw=3)
ax.add_patch(Circle((cx, cy), 0.09, facecolor="white", edgecolor=gray))
ax.plot(cx, cy, ".", color=gray, ms=4)
ax.text(cx-0.12, cy+radius+0.13, "0°", color=blue, ha="right")
ax.annotate("+8°", xy=(cx+radius*np.sin(alpha), cy+radius*np.cos(alpha)),
xytext=(cx+0.68, cy+0.90), color=orange, fontsize=13,
arrowprops={"arrowstyle": "-", "color": orange})
arc = np.linspace(-45, -100, 35)
arc_x = cx+1.13*np.cos(np.deg2rad(arc))
arc_y = cy+1.13*np.sin(np.deg2rad(arc))
ax.plot(arc_x, arc_y, color=gray, lw=1.4)
ax.add_patch(FancyArrowPatch((arc_x[-2], arc_y[-2]), (arc_x[-1], arc_y[-1]),
arrowstyle="-|>", mutation_scale=12, color=gray))
ax.text(cx, 0.02, "End view: light comes toward you (⊙)", ha="center", fontsize=10)
ax.text(4.25, 2.25, "View from the detector toward the source", weight="bold", fontsize=13)
ax.text(4.25, 1.78, "Positive angles turn clockwise in this view.", color=gray)
ax.plot([4.25, 4.80], [1.27, 1.27], color=blue, lw=2, ls="--")
ax.text(4.98, 1.27, "Blank / incident field direction", va="center", color=blue)
ax.plot([4.25, 4.80], [0.82, 0.82], color=orange, lw=3)
ax.text(4.98, 0.82, "Emergent field direction: α = +8°", va="center", color=orange)
ax.text(4.25, 0.30, "Lines show electric-field oscillations, not molecular motion.",
fontsize=10.5, color=gray)
fig.text(0.5, 0.045, "Chosen model: [α] = +40 deg mL/(g dm), c = 0.200 g/mL, T = 20°C",
ha="center", fontsize=11, color=gray)
fig.savefig(Path(__file__).stem + ".png", facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()