codexcatalysis-reversible-equilibrium-relaxation.py
catalysis-reversible-equilibrium-relaxation.py
"""Exact relaxation of a stipulated reversible catalytic mass-action model.
Tested with Python 3.14, matplotlib 3.10.7 and numpy 2.3.5.
Run from _media: MPLBACKEND=Agg python ../catalysis-reversible-equilibrium-relaxation.py
"""
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.lines import Line2D
def rates(catalyst_concentration):
"""Concentration in mol/L; output effective first-order constants in 1/s."""
return 0.030 + 270 * catalyst_concentration, 0.010 + 90 * catalyst_concentration
def fraction(t, x0, catalyst_concentration):
kf, kr = rates(catalyst_concentration)
equilibrium = kf / (kf + kr)
return equilibrium + (x0 - equilibrium) * np.exp(-(kf + kr) * t)
def main():
plt.rcParams.update({"font.size": 12, "figure.facecolor": "white"})
fig, axes = plt.subplots(1, 2, figsize=(9, 4.8), dpi=120, sharey=True)
fig.subplots_adjust(left=0.085, right=0.96, bottom=0.28, top=0.80, wspace=0.12)
for ax, end, title in zip(axes, [120, 15], ["Longer observation", "Early approach"]):
t = np.linspace(0, end, 1201)
for x0, color in [(0, "#176b9b"), (1, "#b64b27")]:
for c, style in [(0, "--"), (0.001, "-")]:
kf, kr = rates(c)
assert np.isclose(kf / kr, 3)
ax.plot(t, fraction(t, x0, c), color=color, linestyle=style, lw=2.3)
ax.axhline(0.75, color="#333333", ls=":", lw=1.8)
ax.set(xlim=(0, end), ylim=(-0.025, 1.025), xlabel="Time / s", title=title)
ax.set_yticks([0, 0.25, 0.5, 0.75, 1])
ax.grid(alpha=0.18)
ax.spines[["top", "right"]].set_visible(False)
axes[0].set_ylabel(r"B fraction: $[B]/([A]+[B])$")
axes[0].text(118, 0.77, "Common equilibrium: ¾", ha="right", fontsize=10)
handles = [Line2D([], [], color="#176b9b", lw=2.3, label="Initially all A"),
Line2D([], [], color="#b64b27", lw=2.3, label="Initially all B"),
Line2D([], [], color="#444444", lw=2.3, ls="--", label="No C: τ = 25 s"),
Line2D([], [], color="#444444", lw=2.3, label="With C: τ = 2.5 s")]
fig.legend(handles=handles, loc="lower center", bbox_to_anchor=(0.5, 0.075),
ncol=2, frameon=False, fontsize=11)
fig.suptitle("Catalysis changes relaxation time, not this model's equilibrium", fontsize=15)
fig.text(0.5, 0.88, "Illustrative exact model: both forward/reverse rate ratios equal 3",
ha="center", fontsize=11)
fig.text(0.5, 0.03, "With C: [C] = 0.001 mol/L. All parameters are stipulated, not fitted measurements.",
ha="center", fontsize=10)
fig.savefig(Path(__file__).stem + ".png", facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()