codexsequential-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()