codex/isotope-nucleus-neon-neutron-counts.py

isotope-nucleus-neon-neutron-counts.py
"""Render a counting diagram; Python 3.14, matplotlib 3.10.7, numpy 2.3.5.

Run from _media; the matching PNG is written to the current directory.
Dots are inventory tokens, not positions or a nuclear structure model.
"""
from pathlib import Path

import matplotlib.pyplot as plt
from matplotlib.patches import Circle, FancyBboxPatch


def main():
    fig, axes = plt.subplots(1, 3, figsize=(9.6, 4.2), dpi=110, facecolor="white")
    fig.suptitle("Neon isotopes: ten protons, different neutron counts", fontsize=16, y=0.96)
    for ax, mass_number in zip(axes, (20, 21, 22), strict=True):
        neutron_number = mass_number - 10
        assert 10 + neutron_number == mass_number
        ax.set(xlim=(-0.5, 4.5), ylim=(-1.0, 6.6), aspect="equal")
        ax.axis("off")
        ax.add_patch(FancyBboxPatch((-0.4, -0.85), 4.8, 7.20,
                                   boxstyle="round,pad=0.02", facecolor="white",
                                   edgecolor="#a9b4bf", linewidth=1.3))
        ax.text(2, 5.55, rf"${{}}^{{{mass_number}}}_{{10}}\mathrm{{Ne}}$", ha="center", fontsize=21)
        ax.text(2, 4.85, "10 protons", ha="center", color="#165c9e", fontsize=12)
        for k in range(10):
            ax.add_patch(Circle((k % 5, 4.25 - 0.55 * (k // 5)), 0.19,
                                facecolor="#2378b8", edgecolor="#165c9e"))
        ax.text(2, 2.95, f"{neutron_number} neutrons", ha="center", color="#5b626b", fontsize=12)
        for k in range(neutron_number):
            ax.add_patch(Circle((k % 5, 2.35 - 0.55 * (k // 5)), 0.19,
                                facecolor="#9ca7b1", edgecolor="#5b626b"))
        ax.text(2, 0.30, f"A = 10 + {neutron_number} = {mass_number}", ha="center", fontsize=12)
        ax.text(2, -0.25, "Z = 10 in every nucleus", ha="center", fontsize=10)
    fig.text(0.5, 0.075, "One dot = one nucleon. Layout is an inventory, not a spatial model.",
             ha="center", fontsize=11)
    fig.text(0.5, 0.026, "Electron removal changes ion charge; it does not change any of these nuclei.",
             ha="center", fontsize=11)
    fig.subplots_adjust(left=0.025, right=0.975, top=0.84, bottom=0.16, wspace=0.1)
    fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()