codex/heat-capacity-constraints.py

heat-capacity-constraints.py
"""Compare stipulated heat capacities under two constraints and by mass.

Tested with Python 3.14.4, NumPy 2.3.5, Matplotlib 3.10.7.
Run through Make; writes heat-capacity-constraints.png to the current directory.
All values are stipulated examples; no measured material properties are used.
"""

from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np


def main():
    amount, gas_constant = 1.0, 8.314  # mol, J/(mol K)
    cv, cp = 1.5 * amount * gas_constant, 2.5 * amount * gas_constant
    delta_t = np.linspace(0.0, 10.0, 201)
    qv, qp = cv * delta_t, cp * delta_t
    work = amount * gas_constant * delta_t
    mass, specific_capacity = 2.0, 400.0  # kg, J/(kg K), fixed volume
    body_capacity = mass * specific_capacity
    body_heat = body_capacity * delta_t
    assert np.allclose(qp - work, qv)  # First-law balance for every point.
    assert np.isclose(cp - cv, amount * gas_constant)
    assert np.allclose([cv, cp], [12.471, 20.785])
    assert np.allclose([qv[-1], qp[-1], work[-1]], [124.71, 207.85, 83.14])
    assert np.isclose(body_heat[-1], 8000.0)
    assert np.isclose(body_heat[100], 4000.0)

    plt.rcParams.update({"font.size": 11, "figure.facecolor": "white",
                         "axes.facecolor": "white", "savefig.facecolor": "white",
                         "axes.spines.top": False, "axes.spines.right": False})
    fig, (gas_ax, mass_ax) = plt.subplots(1, 2, figsize=(10.4, 4.8), dpi=120)
    fig.subplots_adjust(left=0.075, right=0.97, bottom=0.23, top=0.79, wspace=0.32)
    fig.suptitle("Heating requirements: specify the constraint and sample size", fontsize=15)
    blue, orange, green = "#216a99", "#b8531c", "#24765c"
    gas_ax.plot(delta_t, qp, color=orange, lw=2.7, label="Fixed pressure")
    gas_ax.plot(delta_t, qv, color=blue, lw=2.7, label="Fixed volume")
    gas_ax.fill_between(delta_t, qv, qp, color=orange, alpha=0.13)
    gas_ax.set_title("1 mol classical monatomic ideal gas", fontsize=12)
    gas_ax.set_ylabel("Accumulated supplied heat Q (J)")
    gas_ax.set_ylim(0, 260)
    gas_ax.set_yticks([0, 50, 100, 150, 200, 250])
    gas_ax.legend(frameon=False, loc="upper left", fontsize=10)
    gas_ax.plot([10, 10], [qv[-1], qp[-1]], "o", color="#343b43", ms=4)
    gas_ax.annotate("207.85 J", (10, qp[-1]), xytext=(-3, 12),
                    textcoords="offset points", ha="right", color=orange)
    gas_ax.annotate("124.71 J", (10, qv[-1]), xytext=(-3, -18),
                    textcoords="offset points", ha="right", color=blue)
    gas_ax.annotate("Extra heat becomes work:\n83.14 J at ΔT = 10 K",
                    xy=(7.0, 7.0 * (cv + cp) / 2), xytext=(1.0, 65),
                    fontsize=10, arrowprops={"arrowstyle": "->", "color": "#555555"},
                    bbox={"facecolor": "white", "edgecolor": "none", "pad": 3})
    gas_ax.text(0.3, 17, "Both paths: ΔU = 124.71 J at 10 K", fontsize=9.5)

    mass_ax.plot(delta_t, body_heat / 1000, lw=2.7, color=green)
    mass_ax.plot([5, 10], [4, 8], "o", color=green, ms=5)
    mass_ax.set_title("Illustrative body at fixed volume", fontsize=12)
    mass_ax.set_ylabel("Accumulated supplied heat Q (kJ)")
    mass_ax.set_ylim(0, 10)
    mass_ax.set_yticks([0, 2, 4, 6, 8, 10])
    mass_ax.text(0.45, 8.75, "m = 2 kg; cᵥ = 400 J/(kg K)\nCᵥ = mcᵥ = 800 J/K", fontsize=11)
    mass_ax.annotate("ΔT = 10 K\nQ = 8 kJ", (10, 8), xytext=(-7, 12),
                     textcoords="offset points", ha="right", color=green)
    mass_ax.annotate("5 K → 4 kJ", (5, 4), xytext=(5, -22),
                     textcoords="offset points", color=green, fontsize=10)
    for ax in (gas_ax, mass_ax):
        ax.set_xlim(0, 10.5)
        ax.set_xticks([0, 2, 4, 6, 8, 10])
        ax.set_xlabel("Temperature increase ΔT (K)", labelpad=8)
        ax.grid(alpha=0.18)
    fig.text(0.5, 0.09, "Stipulated values; fixed composition; only pressure–volume work; no phase change",
             ha="center", fontsize=10)
    fig.text(0.5, 0.035, "Q = 0 at the initial state; R = 8.314 J/(mol K)",
             ha="center", fontsize=9, color="#555555")
    fig.savefig(Path(__file__).with_suffix(".png").name, transparent=False)
    plt.close(fig)
    print(f"Cv={cv:.3f} J/K; Cp={cp:.3f} J/K; Qv={qv[-1]:.2f} J; "
          f"Qp={qp[-1]:.2f} J; Wp={work[-1]:.2f} J; body Q={body_heat[-1]:.0f} J")


if __name__ == "__main__":
    main()