codex/state-function-two-paths-pv.py

state-function-two-paths-pv.py
"""Original equilibrium paths; Python 3.14, matplotlib 3.10.7, numpy 2.3.5.

Run from _media; emits the matching opaque PNG into the current directory.
"""
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np


def main():
    plt.rcParams.update({"font.size": 12, "font.family": "DejaVu Sans"})
    blue, orange = "#1768a5", "#bf5b17"
    fig = plt.figure(figsize=(9, 6), dpi=120, facecolor="white")
    ax = fig.add_axes((0.11, 0.29, 0.84, 0.57), facecolor="white")
    x = np.linspace(1, 3, 600)
    ax.fill_between(x, 1 / x, 2 / x, color="#d9e8f5", zorder=0)
    ax.plot([1, 1], [1, 2], color=blue, lw=3)
    ax.plot(x, 2 / x, color=blue, lw=3)
    ax.plot(x, 1 / x, color=orange, lw=3)
    ax.plot([3, 3], [1 / 3, 2 / 3], color=orange, lw=3)
    for x1, y1, x2, y2, color in (
        (1, 1.33, 1, 1.68, blue),
        (1.55, 2 / 1.55, 1.92, 2 / 1.92, blue),
        (1.55, 1 / 1.55, 1.92, 1 / 1.92, orange),
        (3, 0.40, 3, 0.61, orange),
    ):
        ax.annotate("", (x2, y2), (x1, y1), arrowprops={"arrowstyle": "-|>", "color": color, "lw": 2.3, "mutation_scale": 18})
    points = ((1, 1, "A", (12, 1)), (1, 2, "C", (10, 3)), (3, 2 / 3, "B", (-21, 9)), (3, 1 / 3, "D", (-21, -24)))
    for px, py, label, offset in points:
        ax.plot(px, py, "o", color="#17212b", ms=7, zorder=5)
        ax.annotate(label, (px, py), xytext=offset, textcoords="offset points", fontsize=14, fontweight="bold")
    ax.text(2.00, 1.30, r"Upper route: $T=2T_0$", color=blue)
    ax.text(1.46, 0.24, r"Lower route: $T=T_0$", color=orange)
    ax.text(1.77, 0.84, r"$\Delta W_{\rm by}=E_0\ln 3$", fontsize=13)
    ax.text(0.86, 1.50, "heat at fixed volume", rotation=90, va="center", ha="center", fontsize=10, color=blue)
    ax.text(3.18, 0.53, "heat at fixed volume", rotation=90, va="center", ha="center", fontsize=10, color=orange)
    ax.set(xlim=(0.77, 3.30), ylim=(0, 2.17), xlabel=r"Volume $V/V_0$", ylabel=r"Pressure $p/p_0$")
    ax.set_xticks([1, 2, 3])
    ax.set_yticks([0, 1 / 3, 2 / 3, 1, 1.5, 2], ["0", r"$1/3$", r"$2/3$", "1", "1.5", "2"])
    ax.spines[["top", "right"]].set_visible(False)
    ax.grid(alpha=0.16, zorder=-1)
    fig.suptitle("Same equilibrium endpoints, different paths", y=0.96, fontsize=17, fontweight="bold")
    fig.text(0.5, 0.155, r"$A=(V_0,T_0,p_0)$   to   $B=(3V_0,2T_0,2p_0/3)$", ha="center", fontsize=13)
    fig.text(0.5, 0.098, r"$W_{\rm upper}=2E_0\ln 3$       $W_{\rm lower}=E_0\ln 3$       $E_0=nRT_0$", ha="center", fontsize=13)
    fig.text(0.5, 0.045, "Idealized reversible paths; shaded area is the work difference.", ha="center", fontsize=11, color="#45515c")
    fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), dpi=120, facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()