codex/dielectric-polarization-fixed-charge-fields.py

dielectric-polarization-fixed-charge-fields.py
"""Draw the fixed-charge dielectric model; tested with Python 3.14.

Dependencies: matplotlib 3.10.7 and numpy 2.3.5, as in pyproject.toml.
Run from _media; the opaque PNG is written to the current directory.
"""

from pathlib import Path

import matplotlib.pyplot as plt
from matplotlib.patches import Circle, Rectangle
import numpy as np


def main():
    epsilon_r = 4.0
    field_fraction = 1.0 / epsilon_r
    polarization_fraction = 1.0 - field_fraction
    assert np.isclose(field_fraction + polarization_fraction, 1.0)
    plt.rcParams.update({"font.size": 11, "savefig.facecolor": "white"})
    fig, (ax, bars) = plt.subplots(
        1, 2, figsize=(10.8, 5.0), dpi=110,
        gridspec_kw={"width_ratios": [1.6, 1]},
    )
    fig.patch.set_facecolor("white")
    fig.suptitle("Polarization at fixed free plate charge", fontsize=16, y=0.96)
    ax.set(xlim=(0, 6.5), ylim=(-0.2, 6.1))
    ax.axis("off")
    ax.text(3.25, 5.55, r"Uniform dielectric: $\epsilon_r=4$", ha="center", fontsize=13)
    ax.add_patch(Rectangle((1.65, 2.1), 3.2, 2.8, fc="#eef4fa", ec="#a5b7c9"))
    for x, sign, color in [(1.2, "+", "#ba4239"), (5.3, "−", "#2866a3")]:
        ax.plot([x, x], [1.9, 5.05], color="#4b515b", lw=7)
        for y in np.linspace(2.25, 4.65, 5):
            sign_x = x - 0.18 if sign == "+" else x + 0.18
            ax.text(sign_x, y, sign, color=color, ha="center", va="center", fontsize=15)
    ax.text(1.2, 1.65, r"$+Q$", ha="center", color="#ba4239")
    ax.text(5.3, 1.65, r"$-Q$", ha="center", color="#2866a3")
    for x, sign, color in [(1.75, "−", "#2866a3"), (4.75, "+", "#ba4239")]:
        for y in np.linspace(2.4, 4.6, 5):
            ax.text(x, y, sign, color=color, ha="center", va="center", fontsize=14)
    for y in [2.85, 3.65, 4.45]:
        for x in [2.4, 3.65]:
            ax.add_patch(Circle((x, y), 0.11, fc="#2866a3", ec="none"))
            ax.add_patch(Circle((x + 0.55, y), 0.11, fc="#ba4239", ec="none"))
            ax.text(x, y, "−", ha="center", va="center", color="white", fontsize=10)
            ax.text(x + 0.55, y, "+", ha="center", va="center", color="white", fontsize=10)
            ax.annotate("", xy=(x + 0.48, y), xytext=(x + 0.13, y),
                        arrowprops={"arrowstyle": "->", "color": "#56616c", "lw": 1.2})
    ax.text(3.25, 2.35, r"Dipole moments and $\mathbf{P}$ point right", ha="center", fontsize=10)
    ax.text(3.25, 5.1, "Bound surface charges oppose plate charges", ha="center", fontsize=10)
    # Field arrows have lengths in the exact ratio 1 : 3/4 : 1/4.
    for y, start, end, label, color in [
        (1.1, 2.25, 5.25, r"$E_{\rm free}$", "#626b77"),
        (0.6, 5.25, 5.25 - 3 * polarization_fraction, r"$E_{\rm pol}$", "#b57b13"),
        (0.1, 2.25, 2.25 + 3 * field_fraction, r"$E$ (total)", "#2866a3"),
    ]:
        ax.text(1.95, y, label, ha="right", va="center")
        ax.annotate("", xy=(end, y), xytext=(start, y),
                    arrowprops={"arrowstyle": "-|>", "lw": 2.2, "color": color,
                                "mutation_scale": 13})
    bars.set_title(r"$D=Q/A$ stays fixed", fontsize=13, pad=15)
    bars.barh([1, 0], [1, field_fraction], color="#2866a3", height=0.36,
              label=r"$\epsilon_0 E$")
    bars.barh([1, 0], [0, polarization_fraction], left=[1, field_fraction],
              color="#d8a13c", height=0.36, label=r"$P$")
    bars.text(0.5, 1, "1", ha="center", va="center", color="white", fontsize=13)
    bars.text(field_fraction / 2, 0, "1/4", ha="center", va="center", color="white", fontsize=11)
    bars.text(field_fraction + polarization_fraction / 2, 0, "3/4", ha="center", va="center", fontsize=13)
    bars.set(yticks=[0, 1], yticklabels=["Dielectric", "Vacuum"], xlim=(0, 1.04),
             ylim=(-0.7, 1.7), xticks=[0, 0.25, 0.5, 0.75, 1],
             xlabel=r"Fraction of $D_{\rm ref}=Q/A$ (dimensionless)")
    bars.grid(axis="x", alpha=0.18)
    bars.set_axisbelow(True)
    bars.spines[["top", "right"]].set_visible(False)
    bars.legend(loc="upper center", ncol=2, frameon=False, bbox_to_anchor=(0.5, 1.03))
    fig.text(0.5, 0.075, r"$D=\epsilon_0E+P$; all three terms have units C/m².",
             ha="center", fontsize=10)
    fig.text(0.5, 0.025, "Ideal plates; uniform static scalar response; schematic not to microscopic scale.",
             ha="center", fontsize=10, color="#4b515b")
    fig.subplots_adjust(left=0.025, right=0.975, bottom=0.22, top=0.84, wspace=0.26)
    fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()