codexlimiting-reagent-amounts.py
limiting-reagent-amounts.py
"""Draw limiting-reagent-amounts.png into the current working directory.
Single-reaction stoichiometric progress; the horizontal axis is not time.
Tested with Python 3.14.4, NumPy 2.3.5, Matplotlib 3.10.7.
From _media: MPLBACKEND=Agg MPLCONFIGDIR=/tmp python3 ../limiting-reagent-amounts.py
"""
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
def main():
initial = np.array([3.0, 1.0, 0.0])
stoichiometric = np.array([-2.0, -1.0, 2.0])
limit = np.min(initial[:2] / -stoichiometric[:2])
extent = np.linspace(0, limit, 201)
amounts = initial[:, None] + stoichiometric[:, None] * extent
atom_composition = np.array([[2, 0, 2], [0, 2, 1]])
np.testing.assert_allclose(atom_composition @ stoichiometric, [0, 0])
np.testing.assert_allclose(atom_composition @ amounts,
np.tile([[6], [2]], (1, extent.size)))
np.testing.assert_allclose(amounts[:, -1], [1, 0, 2])
assert limit == 1 and np.all(amounts >= 0)
assert np.any(initial + stoichiometric * (limit + 0.01) < 0)
plt.rcParams.update({"font.size": 14, "savefig.facecolor": "white"})
fig, ax = plt.subplots(figsize=(8.4, 5.4), facecolor="white")
fig.subplots_adjust(left=0.11, right=0.79, top=0.77, bottom=0.23)
fig.suptitle("Oxygen limits water production", fontsize=22, y=0.96)
fig.text(0.5, 0.855, "2 H₂ + O₂ → 2 H₂O | Initially: 3 mol H₂ + 1 mol O₂", ha="center", fontsize=15)
for values, color, style, label in zip(
amounts, ["#246baa", "#c64b39", "#268452"], ["-", "--", "-."],
["H₂: 1 mol remains", "O₂: exhausted", "H₂O: 2 mol formed"]):
ax.plot(extent, values, lw=2.8, color=color, ls=style)
ax.scatter([limit], [values[-1]], color=color, s=50, zorder=4)
ax.annotate(label, xy=(limit, values[-1]), xytext=(10, 0),
textcoords="offset points", color=color, fontsize=13, va="center",
annotation_clip=False)
ax.axvline(limit, color="#777777", lw=1.2, ls=":")
ax.set(xlim=(0, 1.025), ylim=(-0.12, 3.2), xlabel="Extent of reaction ξ (mol)",
ylabel="Amount of substance n (mol)", xticks=np.linspace(0, 1, 6), yticks=[0, 1, 2, 3])
ax.spines[["top", "right"]].set_visible(False)
ax.grid(axis="y", color="#dddddd", lw=0.7)
fig.text(0.5, 0.10, "Atom totals throughout: 6 mol H atoms and 2 mol O atoms.", ha="center", fontsize=14)
fig.text(0.5, 0.045, "Complete-conversion model for one reaction; ξ measures progress, not time.",
ha="center", fontsize=12, color="#555555")
fig.savefig("limiting-reagent-amounts.png", dpi=100, facecolor="white", transparent=False)
plt.close(fig)
print("Verified ξ_max = 1 mol, final amounts (1, 0, 2) mol, nonnegativity,")
print("and constant atom totals (6, 2) mol across all plotted points.")
if __name__ == "__main__":
main()