codexmonoprotic-buffer-capacity-concentration.py
monoprotic-buffer-capacity-concentration.py
"""Ideal monoprotic capacity; run from _media with Python 3.14.
Dependencies: matplotlib 3.10.7, numpy 2.3.5 (root pyproject.toml).
Stipulated constants describe a model, not measured material data.
"""
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
def capacity(ph, concentration, ka=1e-5, kw=1e-14):
"""Return pair and free-ion terms in mol/L per pH (c° = 1 mol/L)."""
h = 10.0 ** (-np.asarray(ph))
return np.log(10) * concentration * ka * h / (ka + h) ** 2, np.log(10) * (h + kw / h)
def main():
plt.rcParams.update({"font.size": 11, "axes.spines.top": False, "axes.spines.right": False})
fig, axes = plt.subplots(1, 2, figsize=(10, 4.8), layout="constrained", facecolor="white")
ph = np.linspace(2, 8, 601)
for concentration, color in [(0.1, "#2466a8"), (0.01, "#d45d18")]:
pair, _ = capacity(ph, concentration)
axes[0].plot(ph, pair * 1000, lw=2.5, color=color, label=f"C = {concentration:.3f} mol/L")
axes[0].scatter([5], [np.log(10) * concentration / 4 * 1000], color=color, zorder=3)
axes[0].axvline(5, color="0.55", ls=":", lw=1)
axes[0].set(xlabel="pH", ylabel="Pair capacity (mmol/L per pH)", title="Same pKa, tenfold concentration difference", xlim=(2, 8), ylim=(0, 65))
axes[0].text(5.13, 60, "pH = pKa = 5", fontsize=10)
axes[0].legend(loc="upper left", frameon=False, fontsize=10)
ph = np.linspace(2, 12, 1001)
pair, water = capacity(ph, 0.01)
axes[1].semilogy(ph, pair, color="#d45d18", lw=2, label="Acid/base pair")
axes[1].semilogy(ph, water, color="#248471", lw=2, label="Free H⁺ and OH⁻")
axes[1].semilogy(ph, pair + water, color="#333333", lw=2.5, label="Total")
axes[1].set(xlabel="pH", ylabel="Capacity (mol/L per pH)", title="Water terms matter away from the pair peak", xlim=(2, 12), ylim=(1e-9, 0.05))
axes[1].legend(loc="lower left", frameon=False, fontsize=10)
for ax in axes:
ax.grid(alpha=0.18)
ax.set_facecolor("white")
fig.suptitle("Calculated ideal monoprotic buffer: pKa = 5, Kw = 10⁻¹⁴", fontsize=13)
fig.savefig(Path(__file__).stem + ".png", dpi=100, facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()