codexfirst-order-transfer-step-response.py
first-order-transfer-step-response.py
"""Plot two initial conditions for tau*y' + y = K*u with a unit step.
Run from _media; the opaque-white PNG is written by basename to cwd.
Tested with Python 3.14.4, NumPy 2.3.5, and Matplotlib 3.10.7.
"""
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
def main():
gain, tau = 2.0, 3.0
time = np.linspace(0.0, 18.0, 901)
decay = np.exp(-time / tau)
zero = gain * (1.0 - decay)
one = zero + decay
blue, orange = "#1769aa", "#bd5500"
plt.rcParams.update({"font.size": 11, "axes.spines.top": False,
"axes.spines.right": False, "figure.facecolor": "white",
"axes.facecolor": "white"})
fig, ax = plt.subplots(figsize=(8.0, 4.8), layout="constrained")
ax.plot(time, zero, color=blue, linewidth=2.4,
label=r"$y_0=0:\quad y=2(1-e^{-t/3})$")
ax.plot(time, one, color=orange, linewidth=2.4,
label=r"$y_0=1:\quad y=2-e^{-t/3}$")
ax.axhline(gain, color="#555555", linestyle="--", linewidth=1.1)
ax.text(17.7, 2.04, "Final value = 2", ha="right", fontsize=10)
for tmark, label, textpos in [
(tau, "63.2% of final value\nat one time constant", (4.6, 0.64)),
(3 * tau, "95.0% of final value\nat three time constants", (10.3, 1.22)),
]:
value = gain * (1.0 - np.exp(-tmark / tau))
ax.plot([tmark, tmark], [0.0, value], color="#777777",
linestyle=":", linewidth=1.2)
ax.scatter([tmark], [value], s=42, color=blue, zorder=4)
ax.annotate(label, xy=(tmark, value), xytext=textpos, color=blue,
arrowprops={"arrowstyle": "->", "color": blue}, fontsize=10)
ax.set(title="Same transfer function and unit step; different initial values",
xlabel="Time t (s)", ylabel="Output y (dimensionless)",
xlim=(0, 18), ylim=(0, 2.22), xticks=[0, 3, 6, 9, 12, 15, 18])
ax.legend(loc="lower right", framealpha=1.0)
ax.grid(alpha=0.2)
fig.suptitle(r"$G(s)=2/(3s+1)$; time constant $\tau=3$ s", fontsize=14)
fig.savefig(Path(__file__).with_suffix(".png").name, dpi=130,
facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()