codexrl-current-and-voltage.py
rl-current-and-voltage.py
"""Plot a series RL response and the positive terminal voltage drops.
Tested with Python 3.14.4, numpy 2.3.5 and matplotlib 3.10.7.
Write an opaque PNG with this script's basename to the current directory.
"""
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
def main():
resistance, inductance, voltage = 2.0, 0.5, 6.0
tau = inductance / resistance
final_current = voltage / resistance
time = np.linspace(0.0, 5.0 * tau, 1001)
decay = np.exp(-time / tau)
current = final_current * (1.0 - decay)
current_rate = final_current * decay / tau
resistor_drop = resistance * current
inductor_drop = inductance * current_rate
current_tau = final_current * (1.0 - np.exp(-1.0))
drops_tau = [resistance * current_tau, voltage * np.exp(-1.0)]
np.testing.assert_allclose(resistor_drop + inductor_drop, voltage)
np.testing.assert_allclose([tau, final_current, current[0], inductor_drop[0]],
[0.25, 3.0, 0.0, 6.0])
np.testing.assert_allclose([current_tau, *drops_tau],
[1.896361676485673, 3.792723352971346,
2.207276647028654], rtol=1e-14)
# The analytic solution also satisfies the integrated inductor law.
np.testing.assert_allclose(inductance * current,
voltage * tau * (1.0 - decay))
blue, orange, green, grey = "#176997", "#b85d24", "#287d59", "#65717b"
plt.rcParams.update({"font.size": 11, "font.family": "DejaVu Sans",
"axes.spines.top": False, "axes.spines.right": False})
fig, axes = plt.subplots(1, 2, figsize=(10.5, 5.3), dpi=120,
facecolor="white")
axes[0].plot(time, current, color=blue, lw=2.6,
label=r"$I=3(1-e^{-t/\tau})$")
axes[0].axhline(final_current, color=grey, ls=":", lw=1.2)
axes[0].text(0.76, 3.08, "Final current: 3 A", color=grey)
axes[0].scatter([tau], [current_tau], color=blue, zorder=5)
axes[0].annotate("1.89636 A", (tau, current_tau), xytext=(0.49, 1.3),
color=blue, arrowprops={"arrowstyle": "->", "color": blue})
axes[0].set(title="Current rises continuously", ylabel="Current I (A)",
ylim=(0, 3.45))
axes[0].legend(loc="lower right", frameon=False)
axes[1].plot(time, resistor_drop, color=orange, lw=2.6,
label=r"$v_R=RI$")
axes[1].plot(time, inductor_drop, color=green, lw=2.6,
label=r"$v_L=L\,dI/dt$")
axes[1].axhline(voltage, color=grey, ls=":", lw=1.2,
label=r"$v_R+v_L=6$ V")
for value, colour, label, label_position in [
(drops_tau[0], orange, "3.79272 V", (0.50, 4.2)),
(drops_tau[1], green, "2.20728 V", (0.50, 2.1)),
]:
axes[1].scatter([tau], [value], color=colour, zorder=5)
axes[1].annotate(label, (tau, value), xytext=label_position,
color=colour,
arrowprops={"arrowstyle": "->", "color": colour})
axes[1].set(title="Terminal drops share the 6 V", ylabel="Terminal drop (V)",
ylim=(0, 6.6))
axes[1].legend(loc="center right", bbox_to_anchor=(1.0, 0.57),
frameon=False, fontsize=10)
for ax in axes:
ax.axvline(tau, color=grey, ls="--", lw=1.1, zorder=0)
ax.text(tau, -0.20, r"$\tau$", transform=ax.get_xaxis_transform(),
ha="center", color=grey)
ax.set(xlim=(0, 5 * tau), xlabel="Time t (s)")
ax.set_xticks(np.arange(6) * tau)
ax.grid(alpha=0.16)
fig.suptitle("Series RL response: R = 2 Ω, L = 0.5 H, V = 6 V", fontsize=15)
fig.text(0.5, 0.03,
"Initial current: 0 A • Time constant: 0.25 s • "
"Positive current enters the positive-drop terminal",
ha="center", fontsize=10, color=grey)
fig.subplots_adjust(left=0.07, right=0.98, top=0.82, bottom=0.22, wspace=0.27)
fig.savefig(Path(__file__).with_suffix(".png").name,
facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()