codex/weak-electrolyte-fraction-and-ion-concentration.py

weak-electrolyte-fraction-and-ion-concentration.py
"""Compare fraction with concentration in a stipulated ideal acid model.

Run from _media. Tested with Python 3.14, matplotlib 3.10.7, numpy 2.3.5.
Ka is dimensionless; numerical molarity equals C_T / (1 mol/L).
Water autoionization and nonideal activities are omitted.
"""

from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np


def main():
    ka = 1e-5
    concentration = np.geomspace(1e-2, 1e-6, 401)
    fraction = 2 * ka / (ka + np.sqrt(ka**2 + 4 * ka * concentration))
    ions = concentration * fraction
    assert np.all(np.diff(fraction) > 0)
    assert np.all(np.diff(ions) < 0)
    plt.rcParams.update({"font.size": 11, "figure.facecolor": "white"})
    fig, (left, right) = plt.subplots(1, 2, figsize=(10.8, 4.7), dpi=120)
    left.semilogx(concentration, 100 * fraction, color="#8660a8", linewidth=2.5)
    left.set_ylabel("Ionized acid units / total acid units (%)")
    left.set_title("Fraction rises", fontweight="bold")
    left.set_ylim(0, 101)
    right.loglog(concentration, ions, color="#3278b5", linewidth=2.5, label="Weak acid: $C_T\\alpha$")
    right.loglog(concentration, concentration, color="#a76d25", linestyle="--", linewidth=1.7, label="Complete ionization: $C_T$")
    right.set_ylabel("Hydronium concentration (mol/L)")
    right.set_title("Ion concentration falls", fontweight="bold")
    right.legend(loc="upper right", fontsize=10, framealpha=1)
    for ax in (left, right):
        ax.set_xlim(1e-2, 1e-6)
        ax.set_xlabel("Analytical concentration $C_T$ (mol/L)")
        ax.grid(True, which="major", alpha=0.25)
        ax.text(0.5, -0.26, "Dilution →", ha="center", transform=ax.transAxes, fontsize=12, fontweight="bold")
    for c_value, label_position in [(1e-2, (14, 15)), (1e-4, (12, 15))]:
        alpha = 2 * ka / (ka + np.sqrt(ka**2 + 4 * ka * c_value))
        left.plot(c_value, 100 * alpha, "o", color="#8660a8", clip_on=False)
        left.annotate(f"{100 * alpha:.2f}%", (c_value, 100 * alpha), xytext=label_position, textcoords="offset points", color="#644182", fontsize=11)
        right.plot(c_value, c_value * alpha, "o", color="#3278b5", clip_on=False)
    fig.suptitle("Same ideal weak acid, fixed $K_a=10^{-5}$", fontsize=16, y=0.97)
    fig.text(0.5, 0.032, "Calculated model curves, not measurements. Fixed temperature; no water autoionization or other ions.", ha="center", fontsize=10)
    fig.subplots_adjust(left=0.085, right=0.98, top=0.81, bottom=0.28, wspace=0.36)
    fig.savefig(Path(__file__).stem + ".png", facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()