codexpast-exam-of-the-mathematics-course-of-the-university-of-cambridge2011iipaper-4-van-der-waals.py
past-exam-of-the-mathematics-course-of-the-university-of-cambridge/2011/ii/paper-4-van-der-waals.py
"""Van der Waals isotherms; Python 3.14, root NumPy/Matplotlib dependencies.
Preserve caller MPLCONFIGDIR; emit an opaque basename PNG only in cwd.
"""
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
def pressure(v, temperature):
return 8 * temperature / (3 * v - 1) - 3 / v**2
def coexistence(temperature):
spins = sorted(r.real for r in np.roots([4 * temperature, -9, 6, -1])
if abs(r.imag) < 1e-10 and r.real > 1 / 3)
left, right = spins
low, high = pressure(left, temperature), pressure(right, temperature)
def intersections(p):
return sorted(r.real for r in np.roots([3 * p, -(p + 8 * temperature), 9, -3])
if abs(r.imag) < 1e-7 and r.real > 1 / 3)
for _ in range(70):
p = (low + high) / 2
roots = intersections(p)
vl, vg = roots[0], roots[-1]
area = (8 * temperature / 3) * np.log((3 * vg - 1) / (3 * vl - 1))
area += 3 / vg - 3 / vl - p * (vg - vl)
if area > 0:
low = p
else:
high = p
p = (low + high) / 2
roots = intersections(p)
return p, roots[0], roots[-1], left, right
def main():
fig, axes = plt.subplots(1, 2, figsize=(11, 5.6), dpi=100, facecolor="white")
v = np.linspace(0.36, 4.2, 1200)
for t, color in [(1.15, "#176a9a"), (1, "#444444"), (0.85, "#a44211")]:
axes[0].plot(v, pressure(v, t), color=color, linewidth=2,
label=rf"$T/T_c={t:g}$")
axes[0].plot([1], [1], "o", color="#444444")
axes[0].annotate("Critical point", xy=(1, 1), xytext=(1.55, 1.65),
arrowprops={"arrowstyle": "->"}, fontsize=11)
axes[0].set_title("Critical inflection and subcritical loop")
axes[0].legend(fontsize=10)
t = 0.85
p, vl, vg, sl, sg = coexistence(t)
intervals = [(0.36, vl, "#176a9a", "Stable homogeneous phase"),
(vl, sl, "#d08000", "Metastable homogeneous phase"),
(sl, sg, "#b52c38", "Unstable homogeneous phase"),
(sg, vg, "#d08000", None), (vg, 4.2, "#176a9a", None)]
for start, stop, color, label in intervals:
xx = np.linspace(start, stop, 200)
axes[1].plot(xx, pressure(xx, t), color=color, linewidth=2.7, label=label)
xx = np.linspace(vl, vg, 700)
axes[1].fill_between(xx, pressure(xx, t), p, alpha=0.15, color="#555555")
axes[1].plot([vl, vg], [p, p], "--", color="#20803d", linewidth=2,
label="Equilibrium two-phase mixture")
axes[1].plot([vl, vg], [p, p], "o", color="#20803d")
for location, label in [(vl, r"$v_l$"), (sl, r"$v_{s,l}$"),
(sg, r"$v_{s,g}$"), (vg, r"$v_g$")]:
axes[1].axvline(location, color="#777777", linestyle=":", linewidth=0.8)
axes[1].text(location, -0.075, label, ha="center", fontsize=11)
axes[1].set_title(r"Maxwell construction at $T/T_c=0.85$")
axes[1].legend(loc="upper right", fontsize=9)
for ax in axes:
ax.set_xlim(0.35, 4.2)
ax.set_ylim(-0.13, 2.6)
ax.set_xlabel(r"Reduced volume $v=V/V_c$")
ax.set_ylabel(r"Reduced pressure $P=p/p_c$")
ax.grid(alpha=0.15)
fig.tight_layout()
fig.savefig(Path.cwd() / Path(__file__).with_suffix(".png").name,
dpi=100, facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()