codexelectrical-substitution-dark-light-heater-balance.py
electrical-substitution-dark-light-heater-balance.py
"""Original stipulated heat balance; run from _media to create matching CWD PNG.
Tested with Python 3.14.4, Matplotlib 3.10.7 and NumPy 2.3.5.
All powers and temperatures are chosen model values, not device measurements.
"""
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
def main():
dark, light, absorptance = 2.0, 1.005, 0.995
absorbed = dark - light
incident = absorbed / absorptance
escaped = incident - absorbed
conductance, temperature, reference = 5.0, 5.4, 5.0
assert abs(incident - 1.0) < 1e-12
assert abs(conductance * (temperature - reference) - dark) < 1e-12
plt.rcParams.update({"font.family": "DejaVu Sans", "font.size": 11})
fig, (ax, note) = plt.subplots(1, 2, figsize=(9.6, 5.1), dpi=120,
facecolor="white", gridspec_kw={"width_ratios": [1.5, 1]})
fig.subplots_adjust(left=0.085, right=0.975, top=0.79, bottom=0.14, wspace=0.28)
orange, blue, green, ink = "#c76c1c", "#186d9b", "#197b61", "#243745"
fig.suptitle("Dark / light balance at fixed receiver temperature", fontsize=16, color=ink, y=0.97)
fig.text(0.5, 0.89, "STIPULATED MODEL — not measured device performance",
ha="center", fontsize=11, color="#5a6b78")
ax.bar([0, 1], [dark, light], width=0.56, color=orange)
ax.bar(1, absorbed, bottom=light, width=0.56, color=blue)
ax.text(0, dark / 2, "Heater\n2.000 mW", ha="center", va="center", color="white", fontsize=12)
ax.text(1, light / 2, "Heater\n1.005 mW", ha="center", va="center", color="white", fontsize=12)
ax.text(1, light + absorbed / 2, "Absorbed light\n0.995 mW", ha="center", va="center", color="white", fontsize=11)
ax.axhline(dark, color=green, lw=1.6, linestyle="--")
ax.text(-0.48, 2.11, "Outward heat flow: 2.000 mW in either state", fontsize=10, color=green)
ax.set(xticks=[0, 1], xticklabels=["Dark\nbeam blocked", "Light\nbeam admitted"],
ylabel="Power deposited in receiver (mW)", ylim=(0, 2.32), xlim=(-0.65, 1.65))
ax.set_yticks([0, 0.5, 1, 1.5, 2])
ax.spines[["top", "right"]].set_visible(False)
ax.grid(axis="y", color="#e5eaed", linewidth=0.7)
ax.set_axisbelow(True)
note.axis("off")
note.set(xlim=(0, 1), ylim=(0, 1))
note.text(0.0, 0.97, "Same thermal operating point", color=ink, weight="bold")
note.text(0.0, 0.84, r"$T=5.4\ \mathrm{K},\quad T_b=5.0\ \mathrm{K}$", color=ink)
note.text(0.0, 0.74, r"$G=5\ \mathrm{mW\,K^{-1}}$", color=ink)
note.text(0.0, 0.62, "Incident light: 1.000 mW\nAbsorptance: 0.995", color=blue, linespacing=1.7)
note.text(0.0, 0.44, f"Absorbed: {absorbed:.3f} mW\nEscapes: {escaped:.3f} mW (not heat)",
color=blue, linespacing=1.7)
note.text(0.0, 0.23, r"$P_{\mathrm{in}}=\dfrac{2.000-1.005}{0.995}=1.000\ \mathrm{mW}$", color=ink, fontsize=12)
note.text(0.0, 0.04, "Zero parasitic-input change;\nperfect thermal equivalence stipulated.",
color="#5a6b78", fontsize=10)
destination = Path.cwd() / (Path(__file__).stem + ".png")
fig.savefig(destination, facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()