codexoxidation-state-electron-bookkeeping.py
oxidation-state-electron-bookkeeping.py
"""Draw oxidation-state allocations; run from _media with Python 3.14.
Tested with matplotlib 3.10.7 and numpy 2.3.5. These are bookkeeping
diagrams, not electron-density maps or measured partial charges.
"""
from pathlib import Path
import matplotlib.pyplot as plt
from matplotlib.patches import Circle, FancyArrowPatch
def atom(ax, x, y, symbol, state, assigned):
ax.add_patch(Circle((x, y), 0.15, facecolor="white", edgecolor="#34495e", lw=1.7))
ax.text(x, y, symbol, ha="center", va="center", fontsize=18)
ax.text(x, y - 0.26, f"OS = {state}", ha="center", fontsize=13, color="#095c80")
ax.text(x, y - 0.43, f"assigned: {assigned} e⁻", ha="center", fontsize=10.5)
def allocation(ax, a, b):
ax.add_patch(FancyArrowPatch(a, b, arrowstyle="-|>", mutation_scale=16,
color="#095c80", linewidth=1.8))
def panel(ax, title, note):
ax.set(xlim=(0, 2.5), ylim=(0, 1.7))
ax.axis("off")
ax.text(1.25, 1.50, title, ha="center", fontsize=16, weight="bold")
ax.text(1.25, 0.11, note, ha="center", fontsize=11)
def main():
plt.rcParams.update({"font.family": "DejaVu Sans", "figure.facecolor": "white"})
fig, axs = plt.subplots(2, 2, figsize=(9.6, 6.7), dpi=120)
fig.suptitle("Oxidation states count assigned electrons", fontsize=19, y=0.97)
ax = axs[0, 0]
panel(ax, "Water · H₂O", "O: 4 lone-pair e⁻ + 4 bonding e⁻ = 8")
for x, symbol, state, count in [(0.42, "H", "+1", 0), (1.25, "O", "−2", 8),
(2.08, "H", "+1", 0)]:
atom(ax, x, 0.93, symbol, state, count)
allocation(ax, (0.60, 0.93), (1.06, 0.93))
allocation(ax, (1.90, 0.93), (1.44, 0.93))
ax.text(1.25, 1.21, "O receives both O–H pairs", ha="center", fontsize=11)
ax = axs[0, 1]
panel(ax, "Hydrogen peroxide · H₂O₂", "Each O: 4 + 2 + 1 = 7 assigned e⁻")
for x, symbol, state, count in [(0.28, "H", "+1", 0), (0.94, "O", "−1", 7),
(1.56, "O", "−1", 7), (2.22, "H", "+1", 0)]:
atom(ax, x, 0.93, symbol, state, count)
allocation(ax, (0.45, 0.93), (0.76, 0.93))
allocation(ax, (2.05, 0.93), (1.74, 0.93))
ax.plot([1.12, 1.38], [0.93, 0.93], color="#34495e", lw=1.8)
ax.text(1.25, 1.21, "O–O pair split equally", ha="center", fontsize=11)
ax = axs[1, 0]
panel(ax, "Molecular oxygen · O₂", "Equal allocation leaves 6 e⁻ on each O")
atom(ax, 0.80, 0.93, "O", "0", 6)
atom(ax, 1.70, 0.93, "O", "0", 6)
ax.plot([0.98, 1.52], [0.93, 0.93], color="#34495e", lw=1.8)
ax.text(1.25, 1.21, "Same element: equal splitting", ha="center", fontsize=11)
ax = axs[1, 1]
panel(ax, "Sodium hydride · NaH", "An ionic formula unit, not a molecule")
atom(ax, 0.80, 0.93, "Na", "+1", 0)
atom(ax, 1.70, 0.93, "H", "−1", 2)
allocation(ax, (1.00, 0.93), (1.50, 0.93))
ax.text(1.25, 1.21, "H receives the valence inventory", ha="center", fontsize=11)
fig.text(0.5, 0.035, "OS = neutral valence count − assigned count · Not measured partial charges",
ha="center", fontsize=12, weight="bold")
fig.subplots_adjust(left=0.015, right=0.985, top=0.90, bottom=0.095, hspace=0.12, wspace=0.02)
fig.savefig(Path.cwd() / f"{Path(__file__).stem}.png", facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()