codex/reactance-frequency-cancellation.py

reactance-frequency-cancellation.py
"""Toy series RLC predictions, never measured data.

Run from _media: MPLCONFIGDIR=/tmp/codex-wiki-matplotlib python ../reactance-frequency-cancellation.py
Writes reactance-frequency-cancellation.png to CWD.
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
import numpy as np


def main():
    L, C, R, Rs, Vrms = 10e-3, 1e-6, 100.0, 10.0, 1.0
    f0 = 1 / (2 * np.pi * np.sqrt(L * C))
    f = np.unique(np.r_[np.geomspace(200, 8000, 900), f0])
    w = 2 * np.pi * f
    xl, xc = w * L, -1 / (w * C)
    x = xl + xc
    current = Vrms / np.hypot(R + Rs, x)
    phase = np.degrees(np.arctan2(x, R + Rs))
    plt.rcParams.update({"font.size": 11, "axes.spines.top": False,
                         "axes.spines.right": False, "figure.facecolor": "white",
                         "axes.facecolor": "white"})
    fig = plt.figure(figsize=(10.4, 7.2), layout="constrained")
    gs = fig.add_gridspec(2, 2, height_ratios=[1.15, 1])
    top, left, right = fig.add_subplot(gs[0, :]), fig.add_subplot(gs[1, 0]), fig.add_subplot(gs[1, 1])
    fig.suptitle("Opposite reactances select a frequency\n"
                 "Toy prediction: L = 10 mH, C = 1 μF, RΣ = 100 + 10 = 110 Ω, Vᵣₘₛ = 1.00 V\n"
                 "Ideal calculated curves • no measured data", fontsize=13)
    top.semilogx(f, xl, color="#b7560b", label=r"$X_L=2\pi fL$", linewidth=2)
    top.semilogx(f, xc, color="#2855a5", label=r"$X_C=-1/(2\pi fC)$", linewidth=2)
    top.semilogx(f, x, color="#177352", label=r"$X=X_L+X_C$", linewidth=2.5)
    top.axhline(0, color="0.4", linewidth=0.8)
    top.plot(f0, 0, "o", color="#177352")
    top.annotate("Cancellation\nf₀ = 1.592 kHz", xy=(f0, 0), xytext=(2300, -470),
                 arrowprops={"arrowstyle": "->", "color": "0.3"}, fontsize=11)
    top.set(ylabel="Signed reactance (Ω)", ylim=(-820, 570))
    top.legend(loc="upper left", ncols=3, fontsize=11)
    left.semilogx(f, 1000 * current, color="#177352", linewidth=2.3)
    left.plot(f0, 1000 * Vrms / (R + Rs), "o", color="#177352")
    left.annotate("9.091 mA", xy=(f0, 1000 * Vrms / (R + Rs)), xytext=(2500, 9.6),
                  arrowprops={"arrowstyle": "->", "color": "0.3"})
    left.set(ylabel="RMS current (mA)", ylim=(0, 11), xlabel="Frequency f (Hz)")
    right.semilogx(f, phase, color="#653c96", linewidth=2.3)
    right.axhline(0, color="0.4", linewidth=0.8)
    right.text(260, -43, "Current leads\nX < 0", color="#2855a5")
    right.text(2700, 21, "Current lags\nX > 0", color="#b7560b")
    right.set(ylabel="Voltage − current phase (degrees)", ylim=(-90, 90),
              yticks=[-90, -45, 0, 45, 90], xlabel="Frequency f (Hz)")
    for ax in (top, left, right):
        ax.axvline(f0, color="0.45", linestyle=":", linewidth=1.2)
        ax.set_xlim(200, 8000)
        ax.set_xticks([200, 500, 1000, 2000, 5000, 8000])
        ax.set_xticklabels(["200", "500", "1000", "2000", "5000", "8000"])
        ax.grid(alpha=0.17)
    fig.savefig(Path.cwd() / "reactance-frequency-cancellation.png", dpi=105,
                facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()