codex/internal-conversion-populations.py

internal-conversion-populations.py
"""Illustrative two-state kinetics; Python 3.14, NumPy 2.3.5, Matplotlib 3.10.7.

Run from output directory; writes internal-conversion-populations.png to cwd.
"""
from pathlib import Path
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np


def main():
    t = np.geomspace(1e-14, 1e-7, 800)
    k21, k1 = 1e12, 1e8
    n2 = np.exp(-k21 * t)
    n1 = k21 / (k21 - k1) * np.exp(-k1 * t) * (-np.expm1(-(k21 - k1) * t))
    fig, ax = plt.subplots(figsize=(8, 3.5), dpi=120, facecolor='white')
    ax.semilogx(t * 1e12, n2, color='#225ea8', lw=2, label=r'Upper state $S_2$')
    ax.semilogx(t * 1e12, n1, color='#b44d2b', lw=2, label=r'Lower state $S_1$')
    ax.axvline(1, color='0.5', ls='--', lw=0.9)
    ax.axvline(1e4, color='0.5', ls='--', lw=0.9)
    ax.text(1.3, .55, '1 ps transfer scale', fontsize=9)
    ax.text(150, .55, '10 ns lower-state lifetime', fontsize=9)
    ax.set(xlim=(.01, 1e5), ylim=(0, 1.04), xlabel='Time after excitation (ps, logarithmic scale)',
           ylabel='Population / initial upper-state population',
           title='Fast internal conversion can precede slower excited-state decay')
    ax.legend(loc='upper left', fontsize=9)
    ax.grid(alpha=.2)
    fig.tight_layout()
    fig.savefig(Path('internal-conversion-populations.png'), facecolor='white', dpi=120)
    plt.close(fig)


if __name__ == '__main__':
    main()