codexthermal-electrical-noise-spectrum-and-bandwidth.py
thermal-electrical-noise-spectrum-and-bandwidth.py
"""Original ideal resistor-noise spectra; Python 3.14, NumPy 2.3, Matplotlib 3.10.
Run from _media; writes this script's basename with a .png extension.
"""
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
def main():
k_b, temperature, resistance, capacitance = 1.380649e-23, 300, 1e4, 1e-9
corner = 1 / (2 * np.pi * resistance * capacitance)
bandwidth = 1 / (4 * resistance * capacitance)
frequency = np.linspace(0, 100e3, 2500)
response = 1 / (1 + (frequency / corner) ** 2)
spectrum = 4 * k_b * temperature * resistance
plt.rcParams.update({"font.size": 11, "axes.spines.top": False,
"axes.spines.right": False})
fig, axes = plt.subplots(1, 2, figsize=(10, 4.7), layout="constrained")
fig.patch.set_facecolor("white")
ax = axes[0]
ax.plot(frequency / 1e3, np.full_like(frequency, spectrum * 1e18),
color="#555555", linestyle="--", label="Open-circuit source")
ax.plot(frequency / 1e3, spectrum * response * 1e18, color="#176a9b",
linewidth=2.5, label="Voltage across C")
ax.fill_between(frequency / 1e3, spectrum * response * 1e18,
color="#176a9b", alpha=.14)
ax.set(xlabel="Frequency f (kHz)", ylabel=r"One-sided PSD ($\mathrm{nV^2/Hz}$)",
title="Flat source, filtered output", xlim=(0, 100), ylim=(0, 190))
ax.legend(loc="upper right", frameon=True, facecolor="white", framealpha=1)
ax.text(.48, .47, "R = 10 kΩ\nC = 1 nF\nT = 300 K", transform=ax.transAxes,
bbox={"facecolor": "white", "edgecolor": ".85", "pad": 7})
ax = axes[1]
ax.fill_between([0, bandwidth / 1e3], [1, 1], color="#d67b1f", alpha=.19)
ax.plot([0, bandwidth / 1e3, bandwidth / 1e3, 100], [1, 1, 0, 0],
color="#d67b1f", linestyle="--", linewidth=2, label="Equal-area rectangle")
ax.fill_between(frequency / 1e3, response, color="#176a9b", alpha=.18)
ax.plot(frequency / 1e3, response, color="#176a9b", linewidth=2.5,
label=r"RC: $|H(f)|^2$")
ax.axvline(corner / 1e3, ymax=.5 / 1.25, color="#176a9b", linestyle=":")
ax.scatter([corner / 1e3], [.5], color="#176a9b", s=25)
ax.annotate("Nominal corner\n15.915 kHz", xy=(corner / 1e3, .5),
xytext=(40, .67), arrowprops={"arrowstyle": "->", "color": ".3"})
ax.text(44, .37, r"$B_n=(\pi/2)f_c$" + "\n= 25.000 kHz", fontsize=12)
ax.text(42, .14, "RC area includes the\ntail beyond this plot", color="#176a9b")
ax.set(xlabel="Frequency f (kHz)", ylabel="Squared voltage gain",
title="Noise bandwidth is an area", xlim=(0, 100), ylim=(0, 1.25))
ax.legend(loc="upper right", frameon=False, fontsize=10)
fig.suptitle("Thermal noise: filter the PSD, then integrate", fontsize=15)
fig.supxlabel("Original ideal-model calculation • no measured data", fontsize=10)
fig.savefig(Path(__file__).stem + ".png", dpi=110, facecolor="white")
if __name__ == "__main__":
main()