"""Original ideal RC pulse-threshold calculation; times normalized by RC.""" from pathlib import Path import matplotlib.pyplot as plt import numpy as np def response(time, duration): return np.where(time <= duration, -np.expm1(-time), -np.expm1(-duration) * np.exp(-(time - duration))) def main(): fraction = 0.5 first = -np.log1p(-fraction) duration = 2.0 last = duration + np.log(-np.expm1(-duration) / fraction) width = last - first assert np.isclose(response(first, duration), fraction) assert np.isclose(response(last, duration), fraction) assert np.isclose(response(first, first), fraction) assert response(0.4, 0.4) < fraction assert np.isclose(last, 2.5477337226910866) assert np.isclose(width, 1.8545865421311413) epsilon = 1e-6 near = first + epsilon near_width = near + np.log(-np.expm1(-near) * (1 - fraction) / fraction) assert np.isclose(near_width / epsilon, 1 / fraction, rtol=2e-6) fig, (traces, widths) = plt.subplots(1, 2, figsize=(10, 4.3), dpi=100, layout="constrained") fig.set_facecolor("white") time = np.sort(np.r_[np.linspace(0, 5, 2001), 0.4, first, duration, last]) for pulse, label, color in [ (0.4, "T = 0.40τ: never reaches threshold", "#8a8a8a"), (first, "T = 0.693τ: touches at peak", "#7d903b"), (duration, "T = 2τ: finite interval above threshold", "#286f99"), ]: traces.plot(time, response(time, pulse), color=color, label=label) traces.scatter([pulse], [response(pulse, pulse)], color=color, s=20, zorder=5) traces.axhline(fraction, color="#9f4b27", linestyle="--", label="Threshold = 0.5 V₀") traces.scatter([first, last], [fraction, fraction], color="#286f99", s=32, zorder=6) traces.annotate("Time above threshold", xy=((first + last) / 2, fraction), xytext=(1.6, 0.68), ha="center", fontsize=9, arrowprops={"arrowstyle": "->", "color": "#444444"}) traces.set(xlim=(0, 5), ylim=(0, 1), xlabel="Time / τ", ylabel="Output / input pulse height", title="A short pulse can be entirely rejected") traces.legend(fontsize=8, loc="upper right") pulse_widths = np.linspace(first, 4, 1001) high_widths = pulse_widths + np.log(-np.expm1(-pulse_widths) * (1 - fraction) / fraction) assert np.all(high_widths >= -1e-14) widths.plot(np.r_[0, pulse_widths], np.r_[0, high_widths], color="#286f99", label="Ideal time above threshold") widths.plot([0, 4], [0, 4], "--", color="#888888", label="Input pulse duration") widths.axvline(first, color="#7d903b", linestyle=":", label="Minimum duration: 0.693τ") widths.scatter([duration], [width], color="#286f99", zorder=5) widths.annotate("T = 2τ → W = 1.855τ", xy=(duration, width), xytext=(0.9, 2.8), fontsize=9, arrowprops={"arrowstyle": "->", "color": "#444444"}) widths.set(xlim=(0, 4), ylim=(0, 4.15), xlabel="Input pulse duration T / τ", ylabel="Above-threshold duration W / τ", title="The comparator pulse width also changes") widths.legend(fontsize=8, loc="upper left") for ax in (traces, widths): ax.grid(alpha=0.15) fig.savefig(Path(__file__).with_suffix(".png").name, facecolor="white") plt.close(fig) if __name__ == "__main__": main()