codex/heat-engine-efficiency-temperature-model.py

heat-engine-efficiency-temperature-model.py
"""Generate heat-engine-efficiency-temperature-model.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.
The lower curve is stipulated at 80% of the reversible bound, not measured.
"""

from fractions import Fraction

import matplotlib

matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.ticker import PercentFormatter
import numpy as np


def main():
    t_cold = 300
    t_hot = np.linspace(t_cold, 900, 401)
    carnot = 1 - t_cold / t_hot
    model = 0.8 * carnot
    for temperature, expected in [(300, Fraction(0)), (450, Fraction(1, 3)),
                                   (600, Fraction(1, 2)), (900, Fraction(2, 3))]:
        assert 1 - Fraction(t_cold, temperature) == expected
    assert np.all(model <= carnot) and np.all(model >= 0)
    assert np.all(np.diff(carnot) > 0)

    plt.rcParams.update({"font.size": 12, "axes.spines.top": False,
                         "axes.spines.right": False, "savefig.facecolor": "white"})
    fig, ax = plt.subplots(figsize=(8.2, 5.6), dpi=110, facecolor="white")
    ax.fill_between(t_hot, carnot, 0.8, color="#f6e6e5")
    ax.fill_between(t_hot, 0, carnot, color="#edf3f8")
    ax.plot(t_hot, carnot, color="#204a87", lw=2.5,
            label=r"Reversible bound: $\eta_C=1-300/T_h$")
    ax.plot(t_hot, model, color="#287749", lw=2.3, ls="--",
            label=r"Stipulated model: $\eta=0.8\eta_C$")
    ax.plot([600, 600], [0, 0.5], color="#666666", lw=1.1, ls=":")
    ax.scatter([600, 600], [0.5, 0.4], s=48,
               c=["#204a87", "#287749"], zorder=4)
    ax.annotate("600 K: 50%", (600, 0.5), xytext=(655, 0.48),
                fontsize=11, color="#204a87",
                arrowprops={"arrowstyle": "-", "color": "#204a87"})
    ax.annotate("600 K: 40%", (600, 0.4), xytext=(655, 0.31),
                fontsize=11, color="#287749",
                arrowprops={"arrowstyle": "-", "color": "#287749"})
    ax.text(735, 0.71, "Above bound: forbidden", color="#922f2f",
            ha="center", fontsize=11)
    ax.text(735, 0.15, "Below the bound", color="#35536c", ha="center", fontsize=11)
    ax.set(xlim=(300, 900), ylim=(0, 0.8),
           xlabel=r"Hot-reservoir temperature $T_h$ (K)",
           ylabel=r"Thermal efficiency $\eta$",
           title="Fixed cold reservoir at 300 K")
    ax.set_xticks([300, 450, 600, 750, 900])
    ax.yaxis.set_major_formatter(PercentFormatter(1))
    ax.grid(axis="y", alpha=0.18)
    ax.legend(loc="upper left", bbox_to_anchor=(0.0, 1.0), fontsize=10,
              framealpha=1, facecolor="white", edgecolor="#dddddd")
    fig.text(0.5, 0.027,
             "Calculated curves, not data • Cyclic device • Heat exchange only with two fixed reservoirs",
             ha="center", fontsize=9.5)
    fig.subplots_adjust(left=0.12, right=0.965, top=0.89, bottom=0.18)
    fig.savefig("heat-engine-efficiency-temperature-model.png",
                facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()