codexbessel-lens-position-focal-length-sensitivity.py
bessel-lens-position-focal-length-sensitivity.py
"""Draw Bessel position and inference sensitivity, using stipulated ideal models.
Python 3.14, matplotlib 3.10.7, numpy 2.3.5. Run from _media; output uses script basename.
"""
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
def main():
plt.rcParams.update({"font.size": 11, "axes.spines.top": False,
"axes.spines.right": False})
fig, (ax, bx) = plt.subplots(1, 2, figsize=(10.2, 4.6), facecolor="white")
f = 10.0
L = np.linspace(4 * f, 80, 600)
d = np.sqrt(L * (L - 4 * f))
ax.axvspan(25, 40, color="#eee9e5")
ax.plot(L, (L - d) / 2, color="#2871a3", lw=2.2, label="$x_-$ (enlarged)")
ax.plot(L, (L + d) / 2, color="#d46b2b", lw=2.2, label="$x_+$ (reduced)")
ax.plot(L, L / 2, color="#999999", ls=":", label="Midpoint L/2")
ax.scatter([40], [20], color="#333333", s=35, zorder=5)
ax.annotate("One setting\nL = 4f", (40, 20), (29, 34), fontsize=10,
arrowprops={"arrowstyle": "->", "color": "#555555"})
ax.text(32.5, 7, "No real\nsetting", ha="center", color="#777777")
ax.scatter([45, 45], [15, 30], color=["#2871a3", "#d46b2b"], s=38, zorder=5)
ax.annotate("", (45, 15), (45, 30), arrowprops={"arrowstyle": "<->", "color": "#444444"})
ax.text(46.5, 22, "d = 15 cm", fontsize=10)
ax.set(xlim=(25, 80), ylim=(0, 73), xlabel="Object-screen separation L (cm)",
ylabel="Lens position x (cm)", title="Fixed focal length f = 10 cm")
ax.legend(loc="upper left", frameon=False, fontsize=9)
ax.grid(alpha=0.18)
L_fixed = 45.0
measured_d = np.linspace(0, L_fixed, 500)
inferred_f = (L_fixed**2 - measured_d**2) / (4 * L_fixed)
bx.plot(measured_d, inferred_f, color="#27773f", lw=2.2)
bx.axhline(L_fixed / 4, color="#aaaaaa", ls=":")
bx.text(29, 11.65, "Upper bound L/4", fontsize=9, color="#777777")
bx.scatter([15], [10], color="#27773f", s=42, zorder=5)
tangent_d = np.array([3.0, 29.0])
bx.plot(tangent_d, 10 - (tangent_d - 15) / 6, color="#7152a1", ls="--", lw=1.6)
bx.annotate("d = 15 cm → f = 10 cm", (15, 10), (19, 8.4), fontsize=10,
arrowprops={"arrowstyle": "->", "color": "#555555"})
bx.text(4, 3.6, "$\\partial f/\\partial d = -d/(2L)$\n\nAt d = 15 cm: slope = −1/6", fontsize=11,
bbox={"facecolor": "white", "edgecolor": "#dddddd", "pad": 7})
bx.set(xlim=(0, 45), ylim=(0, 13.4), xlabel="Lens displacement d (cm)",
ylabel="Inferred focal length f (cm)", title="Fixed separation L = 45 cm")
bx.grid(alpha=0.18)
fig.suptitle("Bessel method: position sensitivity and focal-length inference", fontsize=15)
fig.text(0.5, 0.025, "Original thin-lens predictions. Coloured dots mark stipulated values, not observations.",
ha="center", fontsize=10, color="#555555")
fig.subplots_adjust(left=0.075, right=0.98, top=0.83, bottom=0.18, wspace=0.30)
fig.savefig(Path(__file__).with_suffix(".png").name, dpi=130, facecolor="white")
plt.close(fig)
if __name__ == "__main__":
main()