codex/parallel-plate-capacitor-field.py

parallel-plate-capacitor-field.py
"""Uniform interior field of a vacuum parallel-plate capacitor.

Tested with Python 3.14, matplotlib 3.10.7, numpy 2.3.5 as in pyproject.toml.
Write the 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
from matplotlib.patches import Rectangle
import numpy as np


def main():
    epsilon, area, gap, voltage = 8.854e-12, 0.01, 0.001, 10.0
    capacitance = epsilon * area / gap
    charge = capacitance * voltage
    field = voltage / gap
    np.testing.assert_allclose(
        [capacitance, charge, field], [8.854e-11, 8.854e-10, 1e4], rtol=1e-14
    )
    np.testing.assert_allclose(charge / (epsilon * area), field)

    positive, negative, blue, grey = "#b64827", "#225b96", "#176c84", "#52616c"
    plt.rcParams.update({"font.size": 12, "font.family": "DejaVu Sans"})
    fig = plt.figure(figsize=(9.2, 5.2), dpi=120, facecolor="white")
    ax = fig.add_axes((0.04, 0.20, 0.65, 0.64), facecolor="white")
    ax.set(xlim=(-0.7, 6.9), ylim=(-0.55, 3.95))
    ax.axis("off")

    # The gap is enlarged to make the electric field readable.
    ax.add_patch(Rectangle((0.4, 0.7), 5.0, 2.0, fc="#f0f7fa", ec="none"))
    for y, colour, sign in [(2.7, positive, "+"), (0.55, negative, "−")]:
        ax.add_patch(Rectangle((0.4, y), 5.0, 0.15, fc=colour, ec="none"))
        for x in np.linspace(0.7, 5.1, 9):
            ax.text(x, y + 0.075, sign, ha="center", va="center", color="white", fontsize=14)

    for x in np.linspace(0.85, 4.95, 7):
        ax.annotate(
            "", (x, 0.84), (x, 2.56),
            arrowprops={"arrowstyle": "-|>", "lw": 2.0, "color": blue},
        )
    ax.text(2.9, 3.48, r"Positive plate: $+Q$, potential $\phi_+$",
            ha="center", color=positive, fontsize=12)
    ax.text(2.9, 0.12, r"Negative plate: $-Q$, potential $\phi_-$",
            ha="center", color=negative, fontsize=12)
    ax.text(2.9, -0.35, "Plate area A = 0.01 m² (each)", ha="center", color=grey)
    ax.text(2.9, 1.68, r"$\mathbf{E}$", ha="center", va="center", fontsize=19,
            color=blue, bbox={"facecolor": "#f0f7fa", "edgecolor": "none", "pad": 4})

    # Dimension lines refer to the facing surfaces, not plate centres.
    for y in (0.7, 2.7):
        ax.plot([5.45, 5.85], [y, y], color=grey, lw=1)
    ax.annotate("", (5.70, 2.7), (5.70, 0.7),
                arrowprops={"arrowstyle": "<->", "color": grey, "lw": 1.2})
    ax.text(5.94, 1.7, "d = 1 mm", rotation=90, va="center", ha="center", color=grey)

    fig.suptitle("Opposite plate charges create a uniform interior field", fontsize=15, y=0.965)
    fig.text(0.5, 0.88, "Vacuum gap; plates shown edge-on; gap enlarged for clarity",
             ha="center", color=grey, fontsize=11)
    fig.text(0.71, 0.70, "Terminal difference", color=grey, fontsize=11)
    fig.text(0.71, 0.64, r"$V=\phi_+-\phi_-=10\;\mathrm{V}$", fontsize=13)
    fig.text(0.71, 0.52, r"$E\simeq V/d$", color=blue, fontsize=14)
    fig.text(0.71, 0.46, "  = 10,000 V/m", color=blue, fontsize=12)
    fig.text(0.71, 0.34, "C ≈ 88.54 pF", fontsize=12)
    fig.text(0.71, 0.28, "Q ≈ 0.8854 nC", fontsize=12)
    fig.text(0.5, 0.10, "Net device charge = 0; stored field energy ≈ 4.427 nJ",
             ha="center", fontsize=12)
    fig.text(0.5, 0.045, "ε₀ ≈ 8.854 × 10⁻¹² F/m. Edge fields neglected; schematic is not to scale.",
             ha="center", color=grey, fontsize=10)
    fig.savefig(Path.cwd() / Path(__file__).with_suffix(".png").name,
                facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()