codex/electrolyte-strength-species-inventories.py

electrolyte-strength-species-inventories.py
"""Draw chosen species inventories; run from _media to write the PNG there.

Tested with Python 3.14, matplotlib 3.10.7 and numpy 2.3.5.
Every marker denotes the same chosen amount, rather than a literal molecule.
"""

from pathlib import Path

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


def main():
    plt.rcParams.update({"font.size": 11, "figure.facecolor": "white"})
    fig, axes = plt.subplots(1, 3, figsize=(10.8, 5.2), dpi=120)
    cases = [
        ("Strong: small inventory", 2, 2, 0, "2 units dissolved; all converted", "100% converted; 4 ionic units"),
        ("Weak: larger inventory", 5, 5, 15, "20 units dissolved; 5 converted", "25% converted; 10 ionic units"),
        ("Nonelectrolyte", 0, 0, 20, "20 units dissolved; none converted", "0% converted; 0 ionic units"),
    ]
    for ax, (title, positive, negative, neutral, inventory, tally) in zip(axes, cases):
        ax.set(xlim=(0, 5), ylim=(0, 6.5), aspect="equal")
        ax.axis("off")
        ax.set_title(title, fontsize=12, fontweight="bold", pad=12)
        ax.add_patch(Rectangle((0.1, 0.85), 4.8, 5.0, facecolor="#fbfcfe", edgecolor="#536174", linewidth=1.5))
        species = [("+", "#d95e39")] * positive + [("−", "#3278b5")] * negative + [("N", "#647684")] * neutral
        # A neutral parent unit yields one positive and one negative ion.
        assert neutral + positive == (2 if positive == 2 else 20)
        assert positive == negative
        for index, (label, color) in enumerate(species):
            col, row = index % 5, index // 5
            x, y = 0.58 + col * 0.96, 5.30 - row * 0.91
            ax.add_patch(Circle((x, y), 0.30, facecolor=color, edgecolor="white", linewidth=0.8))
            ax.text(x, y, label, ha="center", va="center", color="white", fontsize=13, fontweight="bold")
        ax.text(2.5, 0.45, inventory, ha="center", fontsize=10)
        ax.text(2.5, 0.05, tally, ha="center", fontsize=10, fontweight="bold")
    fig.suptitle("Converted fraction and ion amount are different", fontsize=16, y=0.97)
    fig.text(0.5, 0.115, "Equal solution volumes • one-to-one ion production • solvent omitted", ha="center", fontsize=11)
    fig.text(0.5, 0.037, "+ / − = ions; N = neutral unit. Each marker represents the same chosen amount.\nStipulated schematic inventories, not measured material data.", ha="center", fontsize=10, linespacing=1.4)
    fig.subplots_adjust(left=0.025, right=0.975, top=0.86, bottom=0.17, wspace=0.14)
    fig.savefig(Path(__file__).stem + ".png", facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()