codexheat-engine-cyclic-energy-flows.py
heat-engine-cyclic-energy-flows.py
"""Generate heat-engine-cyclic-energy-flows.png in the current directory.
Run from _media with MPLBACKEND=Agg and
MPLCONFIGDIR=/tmp/codex-wiki-matplotlib.
Tested with Python 3.14.4, matplotlib 3.10.7 and numpy 2.3.5.
All numbers are a constructed two-reservoir cyclic-engine budget, not data.
"""
from fractions import Fraction
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.patches import FancyArrow, FancyBboxPatch
def main():
q_hot, q_cold = 1000, 600
work = q_hot - q_cold
t_hot, t_cold = 600, 300
efficiency = Fraction(work, q_hot)
entropy = Fraction(q_cold, t_cold) - Fraction(q_hot, t_hot)
assert work == 400 and efficiency == Fraction(2, 5)
assert entropy == Fraction(1, 3)
assert efficiency < 1 - Fraction(t_cold, t_hot)
plt.rcParams.update({"font.size": 12, "savefig.facecolor": "white"})
fig, ax = plt.subplots(figsize=(8.6, 5.8), dpi=110, facecolor="white")
ax.set(xlim=(0, 10), ylim=(0, 7))
ax.axis("off")
def box(x, y, width, height, color, label):
ax.add_patch(FancyBboxPatch(
(x, y), width, height, boxstyle="round,pad=0.06",
linewidth=1.5, edgecolor=color, facecolor="white"))
ax.text(x + width / 2, y + height / 2, label,
ha="center", va="center", color=color, fontsize=12)
def arrow(x, y, dx, dy, amount, color):
# Shaft widths, rather than arrow lengths, encode transferred energy.
width = 0.5 * amount / q_hot
ax.add_patch(FancyArrow(
x, y, dx, dy, width=width, head_width=width + 0.20,
head_length=0.28, length_includes_head=True,
facecolor=color, edgecolor=color))
hot, cold, output = "#ac342b", "#2166ac", "#287749"
box(1.0, 5.55, 4.2, 0.95, hot, "Hot reservoir\n600 K (fixed)")
box(1.0, 2.85, 4.2, 1.2, "#333333", "Cyclic device\nSame state after every cycle")
box(1.0, 0.8, 4.2, 0.95, cold, "Cold reservoir\n300 K (fixed)")
box(7.15, 2.85, 2.6, 1.2, output, "Net work output\n400 J")
arrow(3.1, 5.45, 0, -1.28, q_hot, hot)
arrow(3.1, 2.75, 0, -0.9, q_cold, cold)
arrow(5.33, 3.45, 1.7, 0, work, output)
ax.text(4.0, 4.79, r"$Q_h=1000\ \mathrm{J}$", color=hot,
ha="left", va="center")
ax.text(4.0, 2.27, r"$Q_c=600\ \mathrm{J}$", color=cold,
ha="left", va="center")
ax.text(6.15, 4.12, r"$W=400\ \mathrm{J}$", color=output,
ha="center", va="bottom")
fig.suptitle("A complete cycle divides the incoming energy", fontsize=15, y=0.97)
fig.text(0.5, 0.065,
r"$\Delta U=0\qquad W=Q_h-Q_c\qquad \eta=W/Q_h=40\%$",
ha="center", fontsize=14)
fig.text(0.5, 0.022,
"Constructed model • Transfers per cycle • Arrow widths show energy amounts",
ha="center", fontsize=10, color="#444444")
fig.subplots_adjust(left=0.025, right=0.98, bottom=0.15, top=0.90)
fig.savefig("heat-engine-cyclic-energy-flows.png", facecolor="white",
transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()