codexcoupled-rl-voltage-pulse-and-energy.py
coupled-rl-voltage-pulse-and-energy.py
"""Closed-return coupled RL model: exactly specified currents and energy budget."""
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
L = .1 # H, each self-inductance
M = .06 # H, positive mutual inductance in the stated current convention
R = 10. # ohm, each loop resistance
V = 1. # V, applied to loop 1 during the pulse
DURATION = .12 # s; the primary circuit stays closed when the drive becomes zero
def step_currents(time):
t = np.maximum(np.asarray(time), 0.)
plus = np.exp(-t / ((L + M) / R))
minus = np.exp(-t / ((L - M) / R))
return V / (2 * R) * np.array([2 - plus - minus, minus - plus])
def pulse_currents(time):
return step_currents(time) - step_currents(np.asarray(time) - DURATION)
def energy_budget(time):
t_on = np.clip(np.asarray(time), 0., DURATION)
t_after = np.maximum(np.asarray(time) - DURATION, 0.)
tau_p, tau_m = (L + M) / R, (L - M) / R
supplied = V**2 / R * (t_on - .5 * tau_p * (-np.expm1(-t_on / tau_p))
- .5 * tau_m * (-np.expm1(-t_on / tau_m)))
def integral_squared_step(t, tau):
return t - 2 * tau * (-np.expm1(-t / tau)) + .5 * tau * (-np.expm1(-2 * t / tau))
heat = V**2 / (2 * R) * (integral_squared_step(t_on, tau_p)
+ integral_squared_step(t_on, tau_m))
s_end = V / R * (-np.expm1(-DURATION / tau_p))
d_end = V / R * (-np.expm1(-DURATION / tau_m))
heat += R / 4 * (s_end**2 * tau_p * (-np.expm1(-2 * t_after / tau_p))
+ d_end**2 * tau_m * (-np.expm1(-2 * t_after / tau_m)))
i1, i2 = pulse_currents(time)
stored = .5 * L * (i1**2 + i2**2) + M * i1 * i2
return supplied, heat, stored
def main():
t = np.linspace(-.01, .25, 4001)
i1, i2 = pulse_currents(t)
supplied, heat, stored = energy_budget(t)
fig, axes = plt.subplots(2, 2, figsize=(10.5, 6.6), layout="constrained")
a, b, c, d = axes.ravel()
a.plot(t * 1000, np.where((t >= 0) & (t < DURATION), V, 0), color="#555555")
a.set(title="Voltage pulse; primary return stays closed", xlabel="Time (ms)", ylabel="Drive (V)", ylim=(-.08, 1.15))
b.plot(t * 1000, i1 * 1000, label="Primary current", color="#d55e00")
b.plot(t * 1000, i2 * 1000, label="Secondary current", color="#0072b2")
b.set(title="Receiver responds during changes", xlabel="Time (ms)", ylabel="Current (mA)")
b.legend(fontsize=9)
short = np.linspace(0, .045, 1000)
secondary = step_currents(short)[1]
tp, tm = (L + M) / R, (L - M) / R
peak_time = tp * tm / (tp - tm) * np.log(tp / tm)
c.plot(short * 1000, secondary * 1000, color="#0072b2")
c.scatter([peak_time * 1000], [step_currents(peak_time)[1] * 1000], color="#333333", zorder=4)
c.set(title="Step response has two decay times", xlabel="Time after voltage step (ms)", ylabel="Secondary current (mA)")
c.text(.95, .12, r"$\tau_+=16$ ms, $\tau_-=4$ ms", transform=c.transAxes, ha="right", fontsize=10)
d.plot(t * 1000, stored * 1000, label="Stored magnetic energy", color="#009e73", lw=2.7)
d.plot(t * 1000, (supplied - heat) * 1000, "--", label="Source work minus heat", color="#cc79a7", lw=1.6)
d.set(title="Independent energy expressions agree", xlabel="Time (ms)", ylabel="Energy (mJ)")
d.legend(fontsize=9)
for ax in axes.ravel():
ax.grid(alpha=.2)
ax.axhline(0, color="#555555", lw=.6)
fig.suptitle(r"Chosen linear model: $L_1=L_2=0.1$ H, $M=0.06$ H, $R_1=R_2=10\,\Omega$", fontsize=13)
fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), dpi=140, facecolor="white")
plt.close(fig)
if __name__ == "__main__":
main()