codexbragg-angular-error-doublet-resolution.py
bragg-angular-error-doublet-resolution.py
"""Chosen Bragg-error and equal-Gaussian doublet models, not measured spectra.
Tested with Python 3.14, matplotlib 3.10.7 and numpy 2.3.5.
Run from _media; writes the matching PNG in the current directory.
"""
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
def main():
fig, axes = plt.subplots(1, 2, figsize=(11.6, 5.3), dpi=110)
fig.patch.set_facecolor("white")
theta = np.linspace(8, 85, 400)
angle_error = np.deg2rad(10/3600)
axes[0].semilogy(theta, angle_error/np.tan(np.deg2rad(theta)),
color="#176b9c", lw=2.2, label="Angular term: 10 arcsec")
axes[0].axhline(2e-5, color="#b44724", ls="--", label="Chosen spacing error: 20 ppm")
for deg in (30, 70):
y = angle_error/np.tan(np.deg2rad(deg))
axes[0].plot(deg, y, "o", color="#176b9c")
axes[0].annotate(f"{deg}°: {y*1e6:.1f} ppm", (deg, y), xytext=(-35, 16),
textcoords="offset points", fontsize=10)
axes[0].set(xlabel=r"Bragg angle $\theta$ (degrees)",
ylabel=r"Fractional error magnitude", title="Larger angles reduce angular error")
axes[0].legend(loc="upper right", fontsize=9)
axes[0].grid(alpha=0.2, which="both")
d = 0.2000 # nm: chosen model plane spacing, not a historical crystal value
wavelengths = np.array([0.2000, 0.2002]) # nm, invented example doublet
centers = np.rad2deg(np.arcsin(wavelengths/(2*d)))
mid = centers.mean()
x = np.linspace(mid-0.12, mid+0.12, 1601)
for sigma, color in ((0.040, "#888888"), (0.008, "#176b9c")):
components = np.exp(-0.5*((x[:, None]-centers)/sigma)**2)
axes[1].plot(x-mid, components.sum(axis=1), color=color, lw=2,
label=rf"$\sigma_\theta={sigma:.3f}^\circ$, $\Delta/\sigma={np.diff(centers)[0]/sigma:.2f}$")
if sigma == 0.008:
for col in components.T:
axes[1].plot(x-mid, col, color=color, ls=":", lw=1)
for c in centers:
axes[1].axvline(c-mid, color="#b44724", ls="--", alpha=0.55, lw=1)
axes[1].set(xlabel=r"Bragg-angle offset from midpoint (degrees)",
ylabel="Response (each isolated peak = 1)",
title="Same doublet, two assumed angular widths", ylim=(0, 2.12))
axes[1].legend(fontsize=9, loc="upper right")
axes[1].grid(alpha=0.2)
fig.suptitle("Illustrative models • chosen d = 0.2000 nm, n = 1", fontsize=14, weight="bold")
fig.text(0.5, 0.035,
"Chosen lines: 0.2000 and 0.2002 nm. Equal Gaussian peaks; no noise or background.\n"
"Two maxima appear for separation > 2σ. This is not the Rayleigh criterion.",
ha="center", fontsize=11)
fig.subplots_adjust(left=0.085, right=0.98, bottom=0.25, top=0.82, wspace=0.30)
fig.savefig(Path(__file__).stem + ".png", facecolor="white")
plt.close(fig)
if __name__ == "__main__":
main()