codex/sequential-kinetics-response-broadening.py

sequential-kinetics-response-broadening.py
"""Illustrative sequential photon arrivals convolved with a Gaussian response.
Tested with Python 3.14, NumPy 2.3.5 and Matplotlib 3.10.7.
"""
from pathlib import Path
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

def arrival_density(t, transfer_time, lower_lifetime=100.0):
    t = np.asarray(t)
    a, b = 1.0 / transfer_time, 1.0 / lower_lifetime
    u = np.maximum(t, 0.0)
    p = a * b / (a - b) * (np.exp(-b * u) - np.exp(-a * u))
    return np.where(t >= 0, p, 0.0)

def response(t, sigma=25.0):
    return np.exp(-0.5 * (np.asarray(t) / sigma)**2) / (sigma * np.sqrt(2 * np.pi))

def traces():
    dt = 0.2
    t = np.arange(-200, 1400 + dt / 2, dt)
    lag = np.arange(-125, 125 + dt / 2, dt)
    g = response(lag)
    g /= np.sum(g) * dt
    return t, [(tau, arrival_density(t, tau), np.convolve(arrival_density(t, tau), g, mode='same') * dt)
               for tau in (2.0, 10.0)]

def main():
    t, curves = traces()
    fig, axes = plt.subplots(1, 2, figsize=(9, 4.1), dpi=100, facecolor='white')
    colors = ['#225eaa', '#c65d24']
    for (tau, ideal, measured), color in zip(curves, colors):
        for ax, y in zip(axes, [ideal, measured]):
            ax.plot(t, y, color=color, lw=2, label=f'Transfer time {tau:g} ps')
    for ax, title, limits in zip(axes,
            ['Intrinsic arrival density', 'After Gaussian instrument response'],
            [(-10, 170), (-70, 230)]):
        ax.set_facecolor('white')
        ax.set(title=title, xlabel='Time after excitation (ps)', xlim=limits, ylim=(0, 0.0105))
        ax.grid(alpha=0.15)
        ax.legend(fontsize=9)
    axes[0].set_ylabel('Photon-arrival density (per ps)')
    fig.suptitle('Instrument broadening can obscure a fast population transfer', fontsize=13)
    fig.text(0.5, 0.015, 'Original models: lower-state lifetime 100 ps; response standard deviation 25 ps. No experimental data.',
             ha='center', fontsize=9)
    fig.tight_layout(rect=(0, 0.04, 1, 0.95))
    fig.savefig(Path('sequential-kinetics-response-broadening.png'), facecolor='white')
    plt.close(fig)

if __name__ == '__main__':
    main()