codexboyles-law-pressure-volume-linearization.py
boyles-law-pressure-volume-linearization.py
"""Plot a calculated Boyle-law isotherm and its reciprocal-volume form.
Tested with Python 3.14, matplotlib 3.10.7 and numpy 2.3.5.
Run from _media; the PNG is written to the current directory.
"""
from pathlib import Path
import matplotlib.pyplot as plt
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"})
constant = 10_000.0 # kPa cm³; calculated example, not measured data
ambient = 100.0 # kPa; stipulated reference for the gauge curve
volume = np.linspace(30.0, 160.0, 500)
reciprocal = np.linspace(0.0, 0.035, 500)
fig, (left, right) = plt.subplots(1, 2, figsize=(11, 4.8), dpi=100)
fig.subplots_adjust(left=0.075, right=0.975, bottom=0.18, top=0.78, wspace=0.29)
fig.suptitle("Boyle's law: the same states in two coordinates", y=0.97, fontsize=16)
fig.text(0.5, 0.89, "Fixed amount and absolute temperature • pV = 10 000 kPa cm³ = 10 J",
ha="center", fontsize=12)
blue, orange = "#17628c", "#b45b16"
left.plot(volume, constant / volume, color=blue, lw=2.7)
left.set(xlim=(0, 170), ylim=(0, 360), xlabel="Total gas volume V (cm³)",
ylabel="Absolute pressure p (kPa)", title="Inverse relation: p = C / V")
left.set_xticks([0, 50, 100, 150])
left.set_yticks([0, 100, 200, 300])
for v, label, xytext in [(100, "A: 100 cm³, 100 kPa", (76, 43)),
(50, "B: 50 cm³, 200 kPa", (66, 260))]:
p = constant / v
left.scatter([v], [p], color=blue, s=45, zorder=4)
left.plot([0, v, v], [p, p, 0], color=blue, alpha=0.35, ls=":")
left.annotate(label, (v, p), xytext=xytext, textcoords="data",
arrowprops={"arrowstyle": "->", "color": blue}, fontsize=10)
right.plot(reciprocal, constant * reciprocal, color=blue, lw=2.7,
label="Absolute p = C (1/V)")
right.plot(reciprocal, constant * reciprocal - ambient, color=orange,
lw=2, ls="--", label="Gauge p = C (1/V) − 100 kPa")
right.scatter([0.01, 0.02], [100, 200], color=blue, s=45, zorder=4)
right.annotate("A", (0.01, 100), xytext=(0.011, 125), color=blue)
right.annotate("B", (0.02, 200), xytext=(0.021, 215), color=blue)
right.scatter([0], [-100], color=orange, s=35, zorder=4, clip_on=False)
right.set(xlim=(0, 0.035), ylim=(-120, 360), xlabel="Reciprocal total volume 1/V (cm⁻³)",
ylabel="Pressure (kPa)", title="Straight lines: reference changes intercept")
right.set_xticks([0, 0.01, 0.02, 0.03], ["0", "0.010", "0.020", "0.030"])
right.set_yticks([-100, 0, 100, 200, 300])
right.axhline(0, color="#777777", lw=0.8)
right.legend(loc="upper left", fontsize=9, framealpha=1)
for ax in (left, right):
ax.grid(alpha=0.18)
fig.text(0.5, 0.04, "Calculated curves, not experimental data; the origin on the right is an extrapolation.",
ha="center", fontsize=10, color="#444444")
fig.savefig(Path.cwd() / f"{Path(__file__).stem}.png", facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()