"""Generate the ideal-model figure for clipping and finite integrator reset.""" from pathlib import Path import numpy as np import matplotlib.pyplot as plt t = np.linspace(-0.2, 1.2, 1401) # milliseconds active = (t >= 0) & (t < 1) ideal = 4.0 * active clipped = np.clip(ideal, -2.0, 2.0) assert np.all(clipped[active] == 2) assert np.all(clipped[~active] == 0) assert 2 / 4 == 0.5 reset_us = np.linspace(0, 900, 1000) residual_mv = 500 * np.exp(-reset_us / 100) threshold_us = 100 * np.log(500) assert abs(500 * np.exp(-threshold_us / 100) - 1) < 1e-12 assert abs(10e-12 / 10e-9 - 1e-3) < 1e-15 fig, ax = plt.subplots(1, 2, figsize=(9.0, 3.8), dpi=100, layout='constrained') fig.set_facecolor('white') ax[0].plot(t, ideal, '--', color='#777777', label='Unclipped: gain 10') ax[0].plot(t, clipped, color='#1769aa', label='Output limited to 2 V') ax[0].fill_between(t, 0, clipped, color='#1769aa', alpha=0.15) ax[0].set(xlabel='Time (ms)', ylabel='Output (V)', title='Clipping loses pulse area', ylim=(-0.2, 4.8)) ax[0].text(0.5, 2.8, 'Half the ideal area remains', ha='center', fontsize=9) ax[0].legend(loc='upper right', fontsize=8) ax[1].semilogy(reset_us, residual_mv, color='#1769aa', label='Residual: 0.5 V initial value') ax[1].axhline(1, color='#b24a20', ls='--', label='1 mV injection step (separate)') ax[1].plot(threshold_us, 1, 'o', color='#1769aa', ms=5) ax[1].annotate('621 µs to reach 1 mV', (threshold_us, 1), (260, 8), arrowprops={'arrowstyle': '->', 'color': '#444444'}, fontsize=9) ax[1].set(xlabel='Time with reset switch closed (µs)', ylabel='Residual magnitude (mV)', title='Reset settling: time constant 100 µs', ylim=(0.05, 2000)) ax[1].legend(loc='upper right', fontsize=8) for a in ax: a.grid(alpha=0.2) fig.savefig(Path.cwd() / 'amplifier-clipping-and-integrator-reset.png', facecolor='white') plt.close(fig)