codexsecond-virial-pvt-and-slope.py
second-virial-pvt-and-slope.py
"""Burnett principle and chosen virial polynomials; no measured data.
Tested with Python 3.14.4, NumPy 2.3.5, Matplotlib 3.10.7.
Run from _media: MPLCONFIGDIR=/tmp/codex-wiki-matplotlib python3 ../second-virial-pvt-and-slope.py
The PNG basename is written to the current working directory.
"""
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.patches import Circle, FancyArrowPatch, Rectangle
import numpy as np
def main():
plt.rcParams.update({"font.size": 11, "axes.titlesize": 12, "axes.labelsize": 11})
fig, (apparatus, slopes) = plt.subplots(1, 2, figsize=(9.8, 5.4))
fig.patch.set_facecolor("white")
fig.subplots_adjust(left=0.04, right=0.97, bottom=0.27, top=0.80, wspace=0.30)
fig.suptitle("Second virial coefficient: a small departure from ideal-gas pressure", fontsize=14, y=0.96)
fig.text(0.5, 0.89, "Apparatus schematic + deliberately chosen toy curves (no measured data)", ha="center", fontsize=11)
apparatus.set(xlim=(0, 10), ylim=(0, 9))
apparatus.axis("off")
apparatus.set_title("A. Repeat expansions at the same temperature", pad=18)
apparatus.add_patch(Rectangle((0.45, 2.3), 8.95, 4.9, facecolor="#f3f8fc", edgecolor="#6390ae", lw=1.5))
apparatus.text(4.9, 6.75, "Thermostatic bath at T", ha="center", color="#245670")
for x, label, desc in [(1.3, "$V_A$", "sample"), (6.45, "$V_B$", "expansion")]:
apparatus.add_patch(Rectangle((x, 3.45), 2.1, 2.5, facecolor="white", edgecolor="#334452", lw=1.7))
apparatus.text(x + 1.05, 4.9, label, ha="center", fontsize=15)
apparatus.text(x + 1.05, 4.05, desc, ha="center", fontsize=10)
apparatus.plot([3.4, 4.8, 6.45], [4.7, 4.7, 4.7], color="#334452", lw=1.7)
apparatus.plot([4.55, 5.05], [4.4, 5.0], color="#334452", lw=1.7)
apparatus.text(4.9, 3.55, "valve", ha="center")
apparatus.plot([2.35, 2.35], [5.95, 7.7], color="#334452", lw=1.7)
apparatus.add_patch(Circle((2.35, 8.0), 0.42, facecolor="white", edgecolor="#334452", lw=1.5))
apparatus.text(2.35, 8.0, "p", ha="center", va="center")
apparatus.text(3.05, 8.0, "pressure readout", va="center", fontsize=10)
apparatus.add_patch(FancyArrowPatch((7.5, 3.4), (7.5, 2.65), arrowstyle="->", mutation_scale=12, lw=1.3))
apparatus.text(7.5, 2.0, "evacuate while isolated", ha="center", fontsize=10)
apparatus.text(4.9, 0.80, "Open → equilibrate → record p\nClose → evacuate expansion chamber → repeat", ha="center", va="center", fontsize=10.5)
x = np.linspace(0, 0.14, 400)
slopes.set_title("B. The initial slope is the coefficient", pad=18)
slopes.axvspan(0, 0.035, color="#e9f3e5")
for b, color, label in [(2, "#c36a20", "$b=+2$"), (-2, "#21669c", "$b=-2$")]:
slopes.plot(x, 1 + b*x + 6*x*x, color=color, lw=2.3, label=label + ": $Z=1+bx+6x^2$")
tangent_x = np.linspace(0, 0.09, 60)
slopes.plot(tangent_x, 1 + b*tangent_x, "--", color=color, lw=1.4)
slopes.axhline(1, color="#6b6b6b", lw=1)
slopes.set(xlim=(0, 0.14), ylim=(0.77, 1.45), xlabel=r"$x=n\ell^3$ (dimensionless density)", ylabel="$Z=p/(nk_BT)$")
slopes.set_xticks([0, 0.035, 0.07, 0.105, 0.14], ["0", ".035", ".070", ".105", ".140"])
slopes.grid(alpha=0.18)
slopes.text(0.005, 1.38, "narrow\nfit window", fontsize=10, color="#3f6634")
slopes.legend(loc="lower right", fontsize=9, frameon=True, facecolor="white", framealpha=1)
slopes.text(0.064, 1.22, "dashed: tangents at x = 0", fontsize=9, rotation=0)
fig.text(0.5, 0.10, r"$B_2=b\ell^3$; chosen curvature $B_3=6\ell^6$. Finite secant slope: $(Z(h)-1)/h=b+6h$.", ha="center", fontsize=11)
fig.text(0.5, 0.045, "A low-density fit needs temperature control, calibration and a check for curvature.", ha="center", fontsize=10.5)
fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), dpi=110, facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()