codexfaraday-changing-flux-and-lenz-current.py
faraday-changing-flux-and-lenz-current.py
"""Changing flux and signed induction; write the PNG to the current directory.
Tested with Python 3.14.4, NumPy 2.3.5, Matplotlib 3.10.7.
"""
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
import numpy as np
def main():
t = np.linspace(0.0, 1.0, 101)
area, resistance = 0.020, 2.0
field = 0.10 + 0.30 * t
flux = area * field
emf = -np.gradient(flux, t)
current = emf / resistance
np.testing.assert_allclose(flux[[0, -1]], [0.002, 0.008])
np.testing.assert_allclose(emf, -0.006, atol=1e-14)
np.testing.assert_allclose(current, -0.003, atol=1e-14)
# Independently check the sign of the field at the loop center using
# Biot-Savart direction dl x (center - source) for a clockwise circle.
angle = np.linspace(0, -2 * np.pi, 721)
points = np.column_stack((np.cos(angle), np.sin(angle), np.zeros_like(angle)))
dl = np.diff(points, axis=0)
to_center = -(points[1:] + points[:-1]) / 2
assert np.all(np.cross(dl, to_center)[:, 2] < 0)
blue, orange, green = "#176997", "#bd5723", "#337b56"
plt.rcParams.update({"font.size": 11, "axes.spines.top": False, "axes.spines.right": False})
fig = plt.figure(figsize=(10, 5.8), dpi=120, facecolor="white")
grid = fig.add_gridspec(2, 2, width_ratios=(1.0, 1.15), hspace=0.53, wspace=0.35)
diagram = fig.add_subplot(grid[:, 0])
diagram.add_patch(Rectangle((0, 0), 1, 1, fill=False, lw=3, edgecolor="#59636c"))
for x in (0.2, 0.5, 0.8):
for y in (0.2, 0.5, 0.8):
diagram.plot(x, y, "o", ms=10, mfc="white", mec=blue, mew=1.4)
diagram.plot(x, y, ".", ms=3, color=blue)
for x, y in ((0.35, 0.35), (0.65, 0.35), (0.35, 0.65), (0.65, 0.65)):
diagram.plot(x, y, "x", ms=8, mew=1.6, color=orange)
for start, end in (((0.3, 1), (0.7, 1)), ((1, 0.7), (1, 0.3)),
((0.7, 0), (0.3, 0)), ((0, 0.3), (0, 0.7))):
diagram.annotate("", end, start, arrowprops={"arrowstyle": "->", "color": orange, "lw": 3})
diagram.annotate("", (0.18, 1.22), (0.82, 1.22),
arrowprops={"arrowstyle": "->", "color": green, "lw": 1.7})
diagram.text(0.5, 1.29, "Positive boundary: CCW", ha="center", color=green)
diagram.text(0.5, 1.52, "Fixed loop; normal out of page", ha="center", fontsize=12)
diagram.text(0.5, -0.16, "Actual current: clockwise (−3 mA)", ha="center", color=orange)
diagram.text(0.5, -0.36, "Dots: external B out, increasing", ha="center", color=blue)
diagram.text(0.5, -0.52, "Crosses: induced B into page", ha="center", color=orange)
diagram.text(0.5, -0.73, "A = 0.020 m² R = 2.0 Ω", ha="center")
diagram.set(xlim=(-0.3, 1.3), ylim=(-0.88, 1.68), aspect="equal")
diagram.axis("off")
flux_ax = fig.add_subplot(grid[0, 1])
flux_ax.plot(t, flux * 1000, lw=2.5, color=blue)
flux_ax.scatter([0, 1], flux[[0, -1]] * 1000, color=blue, s=25)
flux_ax.set(xlim=(-0.03, 1.03), ylim=(0, 10), xlabel="Time t (s)", ylabel="External flux (mWb)")
flux_ax.set_title("Flux: 2 → 8 mWb", fontsize=12)
flux_ax.grid(alpha=0.18)
drive_ax = fig.add_subplot(grid[1, 1])
drive_ax.axhline(0, color="#96a1a8", lw=1)
drive_ax.plot(t, emf * 1000, lw=2.5, color=orange, label="Emf (mV)")
drive_ax.plot(t, current * 1000, lw=2.5, color=green, ls="--", label="Current (mA)")
drive_ax.set(xlim=(-0.03, 1.03), ylim=(-8, 1), xlabel="Time t (s)", ylabel="Signed emf / current")
drive_ax.legend(loc="lower right", fontsize=10, framealpha=1)
drive_ax.set_title("CCW-positive: emf = −6 mV, current = −3 mA", fontsize=11)
drive_ax.grid(alpha=0.18)
fig.suptitle("A steady flux ramp produces a constant opposing drive", fontsize=15, y=0.98)
fig.text(0.5, 0.915, "External B(t) = 0.10 T + (0.30 T/s)t; 0 ≤ t ≤ 1 s", ha="center", fontsize=11)
fig.text(0.5, 0.03, "Quasistatic resistive toy model: neglect self-inductance. Field symbols show direction, not magnitude.",
ha="center", fontsize=10)
fig.subplots_adjust(left=0.035, right=0.97, bottom=0.15, top=0.79)
fig.savefig(Path.cwd() / Path(__file__).with_suffix(".png").name, facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()