codexsynthetic-dielectric-capacitance-frequency-response.py
synthetic-dielectric-capacitance-frequency-response.py
"""Draw synthetic static and frequency-dependent capacitance; Python 3.14.
Dependencies: matplotlib 3.10.7 and numpy 2.3.5, as in pyproject.toml.
Run from _media; this models hypothetical values, not measured material data.
"""
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
def main():
epsilon_0 = 8.854e-12 # Stipulated rounded value, F/m; not an exact SI constant.
area = 0.010 # m²
gap = 1.00e-3 # m
c0_pf = epsilon_0 * area / gap * 1e12
stray_pf = 20.0
epsilon_r = np.linspace(1, 5, 200)
frequency = np.logspace(1, 5, 401) # Hz
characteristic_frequency = 1000.0 # Hz
epsilon_complex = 2.0 + 2.0 / (1.0 + 1j * frequency / characteristic_frequency)
storage_pf = c0_pf * epsilon_complex.real
loss_pf = c0_pf * (-epsilon_complex.imag)
assert np.isclose(c0_pf, 88.54)
assert np.all(loss_pf >= 0)
plt.rcParams.update({"font.size": 11, "savefig.facecolor": "white"})
fig, (static, dynamic) = plt.subplots(1, 2, figsize=(10.8, 4.8), dpi=110)
fig.patch.set_facecolor("white")
fig.suptitle("Synthetic dielectric capacitance response", fontsize=16, y=0.965)
static.plot(epsilon_r, c0_pf * epsilon_r, color="#2866a3", lw=2.5,
label=r"Ideal: $C=C_0\epsilon_r$")
static.plot(epsilon_r, c0_pf * epsilon_r + stray_pf, color="#b57b13", lw=2,
linestyle="--", label="With +20 pF stray capacitance")
static.scatter([1, 4], [c0_pf, 4 * c0_pf], color="#2866a3", zorder=4)
static.annotate("88.54 pF", (1, c0_pf), xytext=(1.3, 63), fontsize=10)
static.annotate("354.16 pF", (4, 4 * c0_pf), xytext=(2.9, 315), fontsize=10)
static.set(xlim=(0.9, 5.1), ylim=(0, 520), xticks=[1, 2, 3, 4, 5],
xlabel=r"Static relative permittivity $\epsilon_r$", ylabel="Capacitance (pF)")
static.set_title("Same area and gap", fontsize=13)
static.legend(loc="upper left", frameon=False, fontsize=10)
dynamic.semilogx(frequency, storage_pf, color="#2866a3", lw=2.5,
label=r"Storage: $C_p=C_0\epsilon_r'$ ")
dynamic.semilogx(frequency, loss_pf, color="#b57b13", lw=2, linestyle="--",
label=r"Loss component: $C_0\epsilon_r''$")
dynamic.axvline(characteristic_frequency, color="#a7a7a7", linestyle=":", lw=1.5)
dynamic.scatter([1000, 1000], [3 * c0_pf, c0_pf], color=["#2866a3", "#b57b13"], zorder=4)
dynamic.annotate("265.62 pF", (1000, 3 * c0_pf), xytext=(1700, 285), fontsize=10)
dynamic.annotate("88.54 pF", (1000, c0_pf), xytext=(1600, 105), fontsize=10)
dynamic.set(xlim=(10, 100000), ylim=(0, 430), xlabel="Frequency (Hz)",
ylabel="Capacitance / loss component (pF)")
dynamic.set_title(r"Assumed $\epsilon_r^*=2+2/(1+if/f_0)$", fontsize=13)
dynamic.legend(loc="upper right", frameon=False, fontsize=10)
for ax in [static, dynamic]:
ax.grid(alpha=0.2)
ax.spines[["top", "right"]].set_visible(False)
fig.text(0.5, 0.055,
"A = 0.010 m², d = 1.00 mm, ε₀ ≈ 8.854 × 10⁻¹² F/m; C₀ = 88.54 pF; f₀ = 1 kHz.",
ha="center", fontsize=10)
fig.text(0.5, 0.015, "All curves are model calculations, not experimental observations.",
ha="center", fontsize=10, color="#4b515b")
fig.subplots_adjust(left=0.075, right=0.98, bottom=0.2, top=0.84, wspace=0.29)
fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()