codexesipt-population-rise.py
esipt-population-rise.py
"""Illustrative irreversible excited-state proton-transfer kinetics (Python 3.14)."""
from pathlib import Path
import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
def populations(t, a=10.0, b=1.0, transfer=8.0):
normal = np.exp(-a * t)
if np.isclose(a, b):
tautomer = transfer * t * normal
else:
tautomer = transfer * (normal - np.exp(-b * t)) / (b - a)
return normal, tautomer
def main():
t = np.linspace(0.0, 3.0, 1000)
normal, tautomer = populations(t)
assert np.all(normal >= 0) and np.all(tautomer >= 0)
assert np.all(normal + tautomer <= 1.0 + 1e-12)
peak_time = np.log(10.0) / 9.0
peak = populations(np.array([peak_time]))[1][0]
fig, ax = plt.subplots(figsize=(8.0, 4.2), dpi=100, facecolor="white")
ax.set_facecolor("white")
ax.plot(t, normal, color="#235d9c", lw=2.3, label=r"Normal state $N^*$")
ax.plot(t, tautomer, color="#a64919", lw=2.3, label=r"Transferred state $T^*$")
ax.plot(peak_time, peak, "o", color="#a64919", ms=5)
ax.annotate(f"Tautomer peaks at {peak_time:.3f} ns", xy=(peak_time, peak),
xytext=(0.85, 0.71), arrowprops={"arrowstyle": "->", "color": "#555555"},
fontsize=10)
ax.set(xlim=(0, 3), ylim=(0, 1.04), xlabel="Time after ideal light pulse (ns)",
ylabel="Population / initially excited population")
ax.set_title("An excited-state tautomer can rise after a light pulse", fontsize=13, pad=26)
ax.text(0.5, 1.025, r"Illustrative model: $a=10$, $b=1$, $k_p=8$ ns$^{-1}$; no reverse transfer",
transform=ax.transAxes, ha="center", va="bottom", fontsize=10, color="#444444")
ax.grid(alpha=0.20)
ax.legend(loc="upper right", frameon=False)
fig.tight_layout()
fig.savefig(Path(__file__).with_suffix(".png").name, facecolor="white", dpi=100)
plt.close(fig)
if __name__ == "__main__":
main()