codex/characteristic-x-ray-line-filtering-model.py

characteristic-x-ray-line-filtering-model.py
"""Stipulated shell differences and attenuation; Python 3.14 / mpl 3.10.7.

No measured atomic energies or material attenuation coefficients are used.
Run from _media; output is an opaque white PNG in the current directory.
"""
from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np


def main():
    plt.rcParams.update({"font.size": 10, "font.family": "DejaVu Sans"})
    fig, axes = plt.subplots(1, 3, figsize=(11.4, 4.7), dpi=120,
                             facecolor="white", layout="constrained")
    fig.suptitle("Chosen shell and filter model — not Barkla's measured spectrum", fontsize=14)
    ax = axes[0]
    for energy, label in [(0, "free electron"), (-.3, "M"), (-1.5, "L"), (-10, "K")]:
        ax.hlines(energy, .15, .85, color="#475569")
        label_y = .55 if energy == 0 else (-.45 if energy == -.3 else energy)
        ax.text(.9, label_y, label, va="center", fontsize=10)
    ax.annotate("", (.35, -10), (.35, -1.5),
                arrowprops={"arrowstyle": "->", "color": "#2563eb", "lw": 2})
    ax.annotate("", (.68, -10), (.68, -.3),
                arrowprops={"arrowstyle": "->", "color": "#b45309", "lw": 2})
    ax.text(.25, -5.5, r"$K\alpha$: 8.5", rotation=90, color="#2563eb", ha="center")
    ax.text(.59, -5.5, r"$K\beta$: 9.7", rotation=90, color="#b45309", ha="center")
    ax.set(xlim=(0, 1.5), ylim=(-11, 1.1), xticks=[],
           ylabel="Bound-state energy (keV)", title="Shell-energy differences")
    ax.text(.55, .6, "0", ha="center")
    ax.text(.1, -.35, "−0.3", va="top", fontsize=9)
    ax.text(.76, -1.7, "−1.5", va="top", fontsize=9)
    ax.text(.1, -10.2, "−10", va="top", fontsize=9)

    ax = axes[1]
    energies = np.array([8.5, 9.7])
    rates = np.array([4., 1.])
    mu = np.array([.2, .8])
    transmitted = rates * np.exp(-mu*2)
    for energy, rate, out in zip(energies, rates, transmitted):
        ax.vlines(energy-.035, 0, rate, color="#64748b", lw=3)
        ax.vlines(energy+.035, 0, out, color="#2563eb", lw=3)
    ax.plot([], [], color="#64748b", lw=3, label="input")
    ax.plot([], [], color="#2563eb", lw=3, label="after 2 mm")
    ax.legend(loc="upper right", frameon=False)
    ax.set(xlim=(8.15, 10.08), ylim=(0, 4.6), xticks=energies,
           xlabel="Photon energy (keV)", ylabel="Line rate (arbitrary unit)",
           title="Filtering changes weights")
    ax.text(8.5, 4.15, r"$K\alpha$", ha="center")
    ax.text(9.7, 1.15, r"$K\beta$", ha="center")

    ax = axes[2]
    thickness = np.linspace(0, 5, 251)
    alpha = 4*np.exp(-.2*thickness)
    beta = np.exp(-.8*thickness)
    ax.plot(thickness, alpha/4, color="#2563eb", lw=2,
            label=r"$K\alpha$ rate / input $K\alpha$ rate")
    ax.plot(thickness, alpha/(alpha+beta), color="#15803d", lw=2,
            label=r"$K\alpha$ fraction of surviving lines")
    ax.scatter([2, 2], [transmitted[0]/4, transmitted[0]/transmitted.sum()],
               c=["#2563eb", "#15803d"], zorder=3)
    ax.axvline(2, color="#94a3b8", lw=1, ls="--")
    ax.set(xlim=(0, 5), ylim=(0, 1.03), xlabel="Filter thickness (mm)",
           ylabel="Fraction", title="Purity versus remaining signal")
    ax.legend(loc="lower left", fontsize=8, frameon=False)
    ax.text(.2, .16, r"Chosen $\mu_\alpha=0.20$, $\mu_\beta=0.80\ \mathrm{mm}^{-1}$",
            fontsize=9)
    for ax in axes:
        ax.spines[["top", "right"]].set_visible(False)
    fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), facecolor="white")
    plt.close(fig)


if __name__ == "__main__":
    main()