codex/van-der-waals-throttling-inversion-model.py

van-der-waals-throttling-inversion-model.py
"""Hypothetical van der Waals inversion and finite-throttle temperature model.

Python 3.14 / matplotlib 3.10.7 / numpy 2.3.5. Run from _media.
No fitted material parameters or experimental points are used.
r=v_m/b_m, theta=R*b_m*T/a_m, pi=b_m**2*p/a_m, eta=b_m*h_m/a_m.
The caloric assumption is C_V,m/R=3/2, so u_m=C_V,m*T-a_m/v_m.
Only supercritical theta>8/27 is displayed; all endpoints have pi>0.
"""
from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np

C = 1.5
CASES = ((.8, .12, "#176ca4", "Cooling"),
         (2.2, .12, "#9b4327", "Warming"),
         (1.45, .30, "#7950a2", "Warms, then cools"))
OUTLET = .025


def initial_volume(theta, pressure):
    roots = np.roots([pressure, -pressure-theta, 1, -1])
    valid = [z.real for z in roots if abs(z.imag) < 1e-9 and z.real > 1]
    if len(valid) != 1:
        raise ValueError("The plotted supercritical state must have one physical root")
    return valid[0]


def state_on_isenthalp(r, eta):
    theta = (eta + 2/r) / (C + r/(r-1))
    pressure = theta/(r-1) - 1/r**2
    return theta, pressure


def throttle(theta, pressure):
    r1 = initial_volume(theta, pressure)
    eta = C*theta + pressure*r1 - 1/r1
    lo, hi = r1, 250.
    for _ in range(100):
        mid = (lo+hi)/2
        if state_on_isenthalp(mid, eta)[1] > OUTLET:
            lo = mid
        else:
            hi = mid
    r = np.geomspace(r1, (lo+hi)/2, 600)
    t, p = state_on_isenthalp(r, eta)
    return r, t, p, eta


def main():
    plt.rcParams.update({"font.size": 11, "figure.facecolor": "white"})
    fig, (ax, bx) = plt.subplots(1, 2, figsize=(10, 5.6), dpi=120)
    r = np.linspace(1.5, 450, 20000)
    inv_t, inv_p = 2*(1-1/r)**2, (2*r-3)/r**2
    # At fixed theta, the inversion pressure is found directly from r.
    ts = np.linspace(.42, 2, 500)
    inverse_r = 1-np.sqrt(ts/2)
    ps = 2*inverse_r-3*inverse_r**2
    ax.fill_betweenx(ts, 0, ps, color="#e6f2fa")
    ax.fill_betweenx(ts, ps, .38, color="#f9ece7")
    ax.axhspan(2, 2.36, color="#f9ece7")
    ax.plot(inv_p, inv_t, "k", linewidth=1.8, label="$\\mu_{JT}=0$")
    ax.text(.055, 1.06, "Cooling\n$\\mu_{JT}>0$", color="#176ca4", ha="center")
    ax.text(.30, 1.97, "Warming\n$\\mu_{JT}<0$", color="#9b4327", ha="center")
    ax.set(xlim=(0, .38), ylim=(.42, 2.36), xlabel="Reduced pressure $\\pi$",
           ylabel="Reduced temperature $\\theta$", title="Inversion curve and endpoint states")
    summary = []
    for t1, p1, color, label in CASES:
        r, t, p, eta = throttle(t1, p1)
        ax.plot(p, t, color=color, linewidth=2)
        ax.plot([p[0], p[-1]], [t[0], t[-1]], "o", color=color, markersize=4)
        j = len(p)//2
        ax.annotate("", xy=(p[j+25], t[j+25]), xytext=(p[j-25], t[j-25]),
                    arrowprops={"arrowstyle": "->", "color": color, "lw": 1.8})
        bx.plot(p1-p, t-t1, color=color, linewidth=2, label=label)
        bx.plot(p1-p[-1], t[-1]-t1, "o", color=color, markersize=4)
        summary.append((t1, p1, float(t[-1]), float(p[-1]), eta))
    ax.legend(loc="lower left", framealpha=1)
    bx.axhline(0, color="#777777", linewidth=1)
    bx.set(xlabel="Pressure drop $\\pi_1-\\pi_2$",
           ylabel="Temperature change $\\theta_2-\\theta_1$",
           title="Finite drops along constant enthalpy")
    bx.legend(loc="lower right", fontsize=10, framealpha=1)
    for a in (ax, bx):
        a.grid(alpha=.22)
    fig.suptitle("Hypothetical van der Waals throttling: cooling is not universal",
                 fontsize=15, weight="bold", y=.98)
    fig.text(.5, .10, "$r=v_m/b_m$,  $\\theta=Rb_mT/a_m$,  $\\pi=b_m^2p/a_m$;  $C_{V,m}=3R/2$",
             ha="center", fontsize=11)
    fig.text(.5, .045, "Model curves only; supercritical states; no experimental measurements or fitted gas.",
             ha="center", fontsize=10, color="#555555")
    fig.subplots_adjust(left=.08, right=.98, bottom=.25, top=.86, wspace=.31)
    fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), facecolor="white")
    plt.close(fig)
    for row in summary:
        print("theta1=%.8f pi1=%.8f theta2=%.8f pi2=%.8f eta=%.8f" % row)


if __name__ == "__main__":
    main()