codex/isothermal-piston-thermal-bath-temperature-lag.py

isothermal-piston-thermal-bath-temperature-lag.py
"""Illustrative uniform gas model, not experimental data.

Python 3.14, matplotlib 3.10.7, numpy 2.3.5. Generate the matching PNG in CWD.
"""

from pathlib import Path

import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
import numpy as np


def temperature_ratio(s, epsilon, capacity_ratio=2.5):
    """Exponential expansion to twice the volume, then a fixed-volume hold."""
    finish = np.log(2.0) / (capacity_ratio * epsilon)
    steady = 1.0 / (1.0 + epsilon)
    during = steady + (1.0 - steady) * np.exp(-(1.0 + epsilon) * s)
    at_finish = steady + (1.0 - steady) * np.exp(-(1.0 + epsilon) * finish)
    after = 1.0 + (at_finish - 1.0) * np.exp(-(s - finish))
    return np.where(s <= finish, during, after), finish


def main():
    plt.rcParams.update({"font.size": 10.5, "axes.spines.top": False,
                         "axes.spines.right": False, "figure.facecolor": "white"})
    fig, (apparatus, ax) = plt.subplots(1, 2, figsize=(11.4, 5.1), dpi=110,
                                      gridspec_kw={"width_ratios": [1.1, 1]})
    apparatus.set(xlim=(0, 6.8), ylim=(0, 5.1), aspect="equal")
    apparatus.axis("off")
    apparatus.set_title("Regulated bath and conducting cylinder", pad=15)
    apparatus.add_patch(Rectangle((0.25, 0.55), 6.05, 3.15,
                                  facecolor="#e5f3fb", edgecolor="#397999", lw=1.8))
    apparatus.text(0.55, 3.32, r"Stirred heat bath at $T_b$", color="#195e7e")
    apparatus.add_patch(Rectangle((1.0, 1.5), 2.4, 1.25,
                                  facecolor="#fff0d4", edgecolor="#515151", lw=2))
    apparatus.plot([3.4, 4.2], [1.5, 1.5], color="#515151", lw=2)
    apparatus.plot([3.4, 4.2], [2.75, 2.75], color="#515151", lw=2)
    apparatus.add_patch(Rectangle((3.3, 1.5), 0.2, 1.25,
                                  facecolor="#8c939a", edgecolor="#515151"))
    apparatus.plot([3.5, 5.8], [2.125, 2.125], color="#515151", lw=4)
    apparatus.plot([5.8, 5.8], [1.85, 2.4], color="#515151", lw=4)
    apparatus.text(2.13, 2.12, "Ideal gas\nT, p, V", ha="center", va="center")
    apparatus.annotate("", xy=(5.25, 3.03), xytext=(3.95, 3.03),
                       arrowprops={"arrowstyle": "-|>", "lw": 1.8})
    apparatus.text(4.75, 3.22, "Slow expansion", ha="center", fontsize=10)
    apparatus.annotate("", xy=(2.3, 1.5), xytext=(2.3, 1.0),
                       arrowprops={"arrowstyle": "-|>", "color": "#b44b1b", "lw": 2})
    apparatus.text(2.3, 0.76, r"Heat in: $\dot Q=G(T_b-T)$", ha="center", color="#a23d16")
    apparatus.plot([1.45, 1.45, 2.4], [2.75, 4.08, 4.08], color="#515151", lw=1.5)
    apparatus.add_patch(Rectangle((2.4, 3.88), 2.25, 0.62,
                                  facecolor="white", edgecolor="#515151"))
    apparatus.text(3.525, 4.19, "T and p readouts", ha="center", va="center", fontsize=10)
    apparatus.text(3.3, 0.15, "Schematic; small mechanical imbalance drives motion", ha="center",
                   fontsize=9)
    s = np.linspace(0, 65, 2000)
    for epsilon, color, label in [(0.05, "#b75224", "Faster"), (0.005, "#176e9e", "Slower")]:
        theta, finish = temperature_ratio(s, epsilon)
        ax.plot(s, theta - 1.0, color=color, lw=2.2,
                label=rf"{label}: $\epsilon={epsilon:g}$")
        endpoint, _ = temperature_ratio(np.array([finish]), epsilon)
        ax.scatter([finish], endpoint - 1, color=color, zorder=4)
        ax.annotate("Hold V fixed", xy=(finish, endpoint[0] - 1),
                    xytext=(finish + (5 if epsilon == 0.05 else -18),
                            -0.038 if epsilon == 0.05 else -0.016),
                    arrowprops={"arrowstyle": "->", "color": color},
                    color=color, fontsize=10)
    ax.axhline(0, color="0.4", ls="--", lw=1)
    ax.set(xlim=(0, 65), ylim=(-0.055, 0.003), xlabel=r"Time $s=t/\tau$,  $\tau=C_V/G$",
           ylabel=r"Temperature difference $(T-T_b)/T_b$",
           title="Calculated lag, then recovery")
    ax.legend(loc="lower right", frameon=True, facecolor="white", fontsize=10)
    ax.grid(alpha=0.2)
    fig.suptitle("Slower piston motion approaches an isotherm", fontsize=14)
    fig.text(0.5, 0.035, r"Uniform-temperature model: $C_V=\frac{5}{2}nR$, "
             r"$V=V_i e^{\lambda t}$ to $2V_i$, $\epsilon=nR\lambda/G$" +
             "   •   illustrative, not measured", ha="center", fontsize=10)
    fig.subplots_adjust(left=0.025, right=0.97, bottom=0.19, top=0.81, wspace=0.32)
    fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), facecolor="white",
                transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()