codex/bohr-balmer-line-calibration-comparison.py

bohr-balmer-line-calibration-comparison.py
"""Original synthetic line calibration exercise; Python 3.14, matplotlib 3.10, numpy 2.3."""

from pathlib import Path
import math

import matplotlib.pyplot as plt
import numpy as np


def main():
    e, h, c = 1.602176634e-19, 6.62607015e-34, 299792458.0
    epsilon = 8.8541878128e-12
    electron, proton = 9.1093837015e-31, 1.67262192369e-27
    mu = electron * proton / (electron + proton)
    hbar = h / (2 * math.pi)
    kappa = e**2 / (4 * math.pi * epsilon)
    binding = mu * kappa**2 / (2 * hbar**2)
    upper = np.arange(3, 7)
    finite = h * c / (binding * (1 / 4 - 1 / upper**2)) * 1e9
    fixed = finite * mu / electron
    chosen_offset = 0.200
    synthetic = finite + chosen_offset
    fitted_finite = synthetic[0] - finite[0]
    fitted_fixed = synthetic[0] - fixed[0]
    residual_fixed = synthetic - fitted_fixed - fixed
    residual_finite = synthetic - fitted_finite - finite
    assert np.allclose(residual_finite, 0, atol=1e-10)
    assert np.allclose(residual_fixed, (1 - mu / electron) * (finite - finite[0]), atol=1e-10)

    plt.rcParams.update({"font.size": 11, "axes.spines.top": False,
                         "axes.spines.right": False, "figure.facecolor": "white"})
    fig, axes = plt.subplots(1, 2, figsize=(11, 5.8), dpi=110,
                             gridspec_kw={"width_ratios": [1.10, 1]})
    fig.subplots_adjust(left=0.19, right=0.98, bottom=0.23, top=0.77, wspace=0.32)
    fig.suptitle("One calibration line does not test the whole series", fontsize=17, y=0.97)
    fig.text(0.5, 0.885, "Chosen synthetic example · additive wavelength offset b = +0.200 nm",
             ha="center", color="#444444")

    local, residual = axes
    rows = np.arange(4)
    local.axvline(0, color="#999999", linewidth=1)
    for row in rows:
        local.hlines(row, -0.42, 0.28, color="#eeeeee", zorder=0)
    local.scatter(fixed - finite, rows, marker="s", color="#ad512f", s=50,
                  label="Fixed nucleus prediction", zorder=3)
    local.scatter(np.zeros(4), rows, marker="o", facecolors="white", edgecolors="#235b88",
                  linewidths=2, s=65, label="Finite mass prediction", zorder=4)
    local.scatter(synthetic - finite, rows, marker="x", color="#333333", s=65,
                  label="Synthetic position", zorder=4)
    local.set_yticks(rows, [f"{n} → 2\nλ = {value:.3f} nm" for n, value in zip(upper, finite)])
    local.set_ylim(3.5, -0.5)
    local.set_xlim(-0.43, 0.29)
    local.set_xlabel("Local offset from each finite mass λ (nm)")
    local.set_title("Predictions before calibration", pad=17)
    handles, labels = local.get_legend_handles_labels()
    fig.legend(handles, labels, loc="lower center", bbox_to_anchor=(0.5, 0.075),
               ncols=3, fontsize=10, frameon=False)

    residual.axhline(0, color="#888888", linewidth=1)
    residual.plot(upper, residual_fixed, "s--", color="#ad512f", label="Fixed nucleus")
    residual.plot(upper, residual_finite, "o-", color="#235b88", label="Finite mass")
    for n, value in zip(upper[1:], residual_fixed[1:]):
        residual.annotate(f"{value:.5f}", (n, value), xytext=(0, -17),
                          textcoords="offset points", ha="center", fontsize=9, color="#ad512f")
    residual.set_xticks(upper, [f"{n} → 2" for n in upper])
    residual.set_xlim(2.7, 6.3)
    residual.set_ylim(-0.18, 0.055)
    residual.set_xlabel("Transition (3 → 2 is the calibration anchor)")
    residual.set_ylabel("Synthetic − calibrated prediction (nm)")
    residual.set_title("Test the other three lines", pad=17)
    residual.legend(loc="lower left", bbox_to_anchor=(0, 1.0), ncols=2, fontsize=9,
                    frameon=False, borderaxespad=0)
    fig.text(0.5, 0.035, "No measured spectrum or intensity trace. Dispersion scale is assumed known; only the offset is fitted.",
             ha="center", fontsize=10, color="#444444")
    fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), facecolor="white")
    plt.close(fig)


if __name__ == "__main__":
    main()