codexlewis-electron-pair-counts.py
lewis-electron-pair-counts.py
"""Draw original Lewis bookkeeping diagrams; write the PNG to cwd.
Build: make _media/lewis-electron-pair-counts.png
Tested with Python 3.14.4, Matplotlib 3.10.7 (system +dfsg1).
Layouts are schematic, not molecular geometries or electron trajectories.
"""
from pathlib import Path
import matplotlib.pyplot as plt
from matplotlib.lines import Line2D
BOND_COLOR = "#216b97"
LONE_COLOR = "#a44f25"
def atom(axis, symbol, x, y):
axis.text(x, y, symbol, ha="center", va="center", fontsize=25)
def bond(axis, start, end, pairs=1):
x1, y1 = start
x2, y2 = end
dx, dy = x2 - x1, y2 - y1
length = (dx * dx + dy * dy) ** 0.5
trim = 0.23 / length
for offset in ([0] if pairs == 1 else [-0.055, 0.055]):
ox, oy = -dy / length * offset, dx / length * offset
axis.plot([x1 + trim * dx + ox, x2 - trim * dx + ox],
[y1 + trim * dy + oy, y2 - trim * dy + oy],
color=BOND_COLOR, lw=3, solid_capstyle="round")
def lone_pair(axis, x, y, vertical=False):
gap = 0.065
xs = [x, x] if vertical else [x - gap, x + gap]
ys = [y - gap, y + gap] if vertical else [y, y]
axis.scatter(xs, ys, s=27, color=LONE_COLOR, zorder=3)
def main():
# (formula, valence total, shared pairs, nonbonding pairs)
counts = [("H2", 2, 1, 0), ("H2O", 8, 2, 2),
("NH3", 8, 3, 1), ("O2", 12, 2, 4)]
for formula, total, shared, nonbonding in counts:
assert total == 2 * (shared + nonbonding), formula
plt.rcParams.update({"font.size": 11, "figure.facecolor": "white",
"axes.facecolor": "white", "savefig.facecolor": "white"})
fig, axes = plt.subplots(1, 4, figsize=(10, 4.3), dpi=120)
fig.subplots_adjust(left=0.025, right=0.98, bottom=0.26, top=0.78, wspace=0.16)
fig.suptitle("Lewis bookkeeping: count each electron once", fontsize=16, y=0.96)
headings = [r"Hydrogen: $\mathrm{H}_2$", r"Water: $\mathrm{H}_2\mathrm{O}$",
r"Ammonia: $\mathrm{NH}_3$", r"Oxygen: $\mathrm{O}_2$"]
budgets = ["1 + 1 = 2 electrons", "6 + 2 = 8 electrons",
"5 + 3 = 8 electrons", "6 + 6 = 12 electrons"]
for axis, heading, budget, (_, total, shared, nonbonding) in zip(
axes, headings, budgets, counts):
axis.set(xlim=(-1.25, 1.25), ylim=(-1.15, 1.05), aspect="equal")
axis.axis("off")
axis.set_title(heading, fontsize=12, pad=25)
axis.text(0.5, 1.015, budget, ha="center", transform=axis.transAxes, fontsize=10)
axis.text(0.5, -0.08, f"{shared} bond pair{'s' if shared != 1 else ''}\n"
f"{nonbonding} lone pair{'s' if nonbonding != 1 else ''}",
ha="center", va="top", transform=axis.transAxes, linespacing=1.5)
axis = axes[0]
atom(axis, "H", -0.65, 0)
atom(axis, "H", 0.65, 0)
bond(axis, (-0.65, 0), (0.65, 0))
axis = axes[1]
atom(axis, "O", 0, 0)
for x in [-0.95, 0.95]:
atom(axis, "H", x, 0)
bond(axis, (0, 0), (x, 0))
lone_pair(axis, 0, 0.45)
lone_pair(axis, 0, -0.45)
axis = axes[2]
atom(axis, "N", 0, 0)
for x, y in [(-0.95, 0), (0.95, 0), (0, -0.85)]:
atom(axis, "H", x, y)
bond(axis, (0, 0), (x, y))
lone_pair(axis, 0, 0.45)
axis = axes[3]
for x in [-0.6, 0.6]:
atom(axis, "O", x, 0)
lone_pair(axis, x, 0.45)
lone_pair(axis, x, -0.45)
bond(axis, (-0.6, 0), (0.6, 0), pairs=2)
axis.text(0.5, -0.44, "Paired drawing does not explain\nO2 paramagnetism", color="#555555",
ha="center", transform=axis.transAxes, fontsize=9)
fig.legend(handles=[Line2D([0], [0], color=BOND_COLOR, lw=3, label="Line = two shared electrons"),
Line2D([0], [0], color=LONE_COLOR, marker="o", ls="",
label="Two dots = one lone pair")],
loc="lower center", bbox_to_anchor=(0.5, 0.015), ncol=2, frameon=False, fontsize=10)
fig.text(0.5, 0.125, "Valence electrons only; schematic layouts, not shapes or trajectories.",
ha="center", fontsize=10)
fig.savefig(Path.cwd() / "lewis-electron-pair-counts.png", transparent=False)
plt.close(fig)
print("Valence totals verified: H2=2 (1+0 pairs); H2O=8 (2+2); "
"NH3=8 (3+1); O2=12 (2+4).")
if __name__ == "__main__":
main()