codexheat-capacity-ratio-gas-spring.py
heat-capacity-ratio-gas-spring.py
"""Original Ruchardt schematic and analytical gas-spring model, not data.
Tested with Python 3.14.4, NumPy 2.3.5 and Matplotlib 3.10.7.
Run from _media; the output basename follows this script's basename.
"""
from pathlib import Path
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
import numpy as np
def main():
plt.rcParams.update({"font.size": 11, "axes.spines.top": False,
"axes.spines.right": False, "figure.facecolor": "white",
"axes.facecolor": "white", "savefig.facecolor": "white"})
fig, (setup, model) = plt.subplots(1, 2, figsize=(10.8, 4.8), dpi=100,
gridspec_kw={"width_ratios": [1, 1.2]})
fig.subplots_adjust(left=0.04, right=0.98, top=0.80, bottom=0.19, wspace=0.32)
fig.suptitle("Heat-capacity ratio: a gas becomes a spring", fontsize=17, y=0.97)
fig.text(0.5, 0.89, "Original apparatus schematic and analytical curves — no measured data",
ha="center", fontsize=11, color="#555555")
setup.set(xlim=(0, 6), ylim=(0, 5.5))
setup.axis("off")
setup.set_title("Rüchardt piston apparatus", fontsize=13)
setup.add_patch(Rectangle((1.2, 0.65), 2.2, 2.65, facecolor="#e4f1fb", edgecolor="none"))
setup.plot([1.2, 1.2, 3.4, 3.4], [4.8, 0.65, 0.65, 4.8], color="#333333", lw=2.5)
setup.add_patch(Rectangle((1.22, 3.30), 2.16, 0.35, facecolor="#a1a9b0", edgecolor="#333333"))
setup.text(2.3, 2.0, "Sealed gas\n" + r"$p_0,\ V_0,\ T_0$", ha="center", va="center", fontsize=13)
setup.text(2.3, 4.65, r"Ambient $p_{\rm atm}$", ha="center")
setup.annotate("", xy=(2.3, 3.80), xytext=(2.3, 4.40),
arrowprops={"arrowstyle": "->", "lw": 1.8, "color": "#b45c23"})
setup.text(2.55, 4.04, r"$Mg$", color="#b45c23")
setup.annotate("", xy=(2.3, 3.23), xytext=(2.3, 2.65),
arrowprops={"arrowstyle": "->", "lw": 1.8, "color": "#2266a0"})
setup.annotate("Piston\nmass M, area A", xy=(3.0, 3.5), xytext=(4.0, 2.85),
va="center", fontsize=10, arrowprops={"arrowstyle": "-", "color": "#555555"})
setup.annotate("", xy=(3.75, 4.4), xytext=(3.75, 3.5),
arrowprops={"arrowstyle": "->", "lw": 1.8})
setup.text(4.0, 4.0, "x > 0\nexpansion", fontsize=10, va="center")
setup.text(2.5, 0.1, r"$p_0=p_{\rm atm}+Mg/A$", ha="center", fontsize=13)
gamma = 1.4
q = np.linspace(-0.10, 0.10, 601)
adiabatic = np.expm1(-gamma * np.log1p(q))
isothermal = np.expm1(-np.log1p(q))
model.axvspan(-0.025, 0.025, color="#ecf4e8", label="Small displacement region")
model.axhline(0, color="#999999", lw=0.8)
model.axvline(0, color="#999999", lw=0.8)
model.plot(q, adiabatic, color="#2266a0", lw=2.6, label=r"Adiabatic, $\gamma=1.4$")
model.plot(q, -gamma * q, "--", color="#2266a0", lw=1.6, label=r"Tangent: $-\gamma q$")
model.plot(q, isothermal, color="#b45c23", lw=2.3, label="Isothermal limit")
model.set(xlabel=r"Fractional volume change $q=Ax/V_0$",
ylabel=r"Net restoring force $F/(p_0 A)$", xlim=(-0.10, 0.10), ylim=(-0.16, 0.19))
model.set_title(r"$F/(p_0 A)=(1+q)^{-\gamma}-1$", fontsize=13)
model.legend(loc="upper right", fontsize=9, framealpha=1)
model.grid(alpha=0.18)
fig.text(0.50, 0.055, r"Small-amplitude adiabatic stiffness: $k=\gamma p_0 A^2/V_0$"
" • Heat exchange changes the restoring response",
ha="center", fontsize=11)
fig.savefig(Path(__file__).with_suffix(".png").name, facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()