codex/chemical-formula-and-coefficient.py

chemical-formula-and-coefficient.py
"""Write chemical-formula-and-coefficient.png to the current working directory.

Connections H-O-H and H-O-O-H are schematic, not molecular geometry models.
Tested with Python 3.14.4, NumPy 2.3.5, Matplotlib 3.10.7.
Run from _media: MPLCONFIGDIR=/tmp python3 ../chemical-formula-and-coefficient.py
"""

import matplotlib

matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
import numpy as np


COLORS = {"H": "#246baa", "O": "#c64b39"}


def molecule(ax, y, elements):
    x = np.linspace(-0.4 * (len(elements) - 1),
                     0.4 * (len(elements) - 1), len(elements))
    ax.plot(x, np.full(len(elements), y), color="#555555", lw=3, zorder=1)
    for position, element in zip(x, elements):
        ax.add_patch(Circle((position, y), 0.27, facecolor=COLORS[element],
                            edgecolor="white", lw=1.2, zorder=2))
        ax.text(position, y, element, fontsize=15, color="white", weight="bold",
                ha="center", va="center", zorder=3)


def main():
    groups = [["HOH"], ["HOH", "HOH"], ["HOOH"]]
    counts = np.array([[sum(group.count(element) for group in column)
                        for column in groups] for element in "HO"], dtype=int)
    np.testing.assert_array_equal(counts, [[2, 4, 2], [1, 2, 2]])
    plt.rcParams.update({"font.size": 14, "savefig.facecolor": "white"})
    fig = plt.figure(figsize=(10, 5.7), facecolor="white")
    fig.suptitle("Coefficient and subscript do different jobs", fontsize=21, y=0.96)
    centers = [0.18, 0.50, 0.82]
    titles = ["H₂O", "2 H₂O", "H₂O₂"]
    subtitles = ["One water molecule", "Two water molecules", "One hydrogen peroxide molecule"]
    for index, (center, title, subtitle, column) in enumerate(zip(centers, titles, subtitles, groups)):
        fig.text(center, 0.80, title, ha="center", fontsize=27, weight="bold")
        fig.text(center, 0.735, subtitle, ha="center", fontsize=12)
        ax = fig.add_axes([center - 0.145, 0.365, 0.29, 0.33])
        ax.set(xlim=(-1.6, 1.6), ylim=(-1.05, 1.05), aspect="equal")
        ax.axis("off")
        for group, y in zip(column, [0] if len(column) == 1 else [0.5, -0.5]):
            molecule(ax, y, group)
        fig.text(center, 0.295, f"H atoms: {counts[0, index]}", ha="center", fontsize=17, color=COLORS["H"])
        fig.text(center, 0.23, f"O atoms: {counts[1, index]}", ha="center", fontsize=17, color=COLORS["O"])
    for x in [0.34, 0.66]:
        fig.add_artist(plt.Line2D([x, x], [0.20, 0.86], transform=fig.transFigure,
                                 color="#dddddd", lw=1))
    fig.text(0.5, 0.135, "A coefficient multiplies the whole formula. A subscript changes its composition.",
             ha="center", fontsize=13)
    fig.text(0.5, 0.055, "H–O–H and H–O–O–H connections only; lengths and angles are schematic.",
             ha="center", fontsize=12, color="#555555")
    fig.savefig("chemical-formula-and-coefficient.png", dpi=110,
                facecolor="white", transparent=False)
    plt.close(fig)
    print("Verified columns: H atoms = [2, 4, 2]; O atoms = [1, 2, 2].")


if __name__ == "__main__":
    main()