codex/chemical-potential-and-pressure.py

chemical-potential-and-pressure.py
"""Plot an ideal gas's molar chemical-potential difference and exchange direction.

Writes chemical-potential-and-pressure.png in the current working directory.
Tested with Python 3.14.4, Matplotlib 3.10.7, and NumPy 2.3.5.
"""

from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np


def main():
    gas_constant = 8.314462618  # Stipulated J / (mol K).
    temperature = 300.0  # K.
    ratio = np.linspace(0.18, 4.0, 700)
    blue, orange, green = "#236a9e", "#b6532b", "#267747"
    plt.rcParams.update({"font.size": 12, "figure.facecolor": "white",
                         "axes.facecolor": "white", "savefig.facecolor": "white"})
    fig, ax = plt.subplots(figsize=(8.6, 5.6), dpi=120)
    fig.subplots_adjust(left=0.13, right=0.96, bottom=0.35, top=0.80)
    fig.suptitle("Chemical potential responds to a pressure ratio", fontsize=16, y=0.96)
    fig.text(0.5, 0.875, r"Same ideal gas at a common temperature:  $\Delta\mu_{\rm m}/(RT)=\ln(p_B/p_A)$",
             ha="center", fontsize=12)
    ax.axvspan(0.18, 1.0, color=blue, alpha=0.07)
    ax.axvspan(1.0, 4.0, color=orange, alpha=0.07)
    ax.axhline(0, color="#777777", lw=1)
    ax.axvline(1, color=green, lw=1, ls="--")
    ax.plot(ratio, np.log(ratio), color=blue, lw=2.5)
    ax.scatter([0.5, 1.0], [np.log(0.5), 0], color=[orange, green], s=55, zorder=5)
    delta = -gas_constant * temperature * np.log(2)
    ax.annotate(f"Half the pressure\nAt 300 K: {delta:,.3f} J/mol",
                xy=(0.5, np.log(0.5)), xytext=(1.55, -1.25), fontsize=11,
                arrowprops={"arrowstyle": "->", "color": orange, "lw": 1.4},
                bbox={"boxstyle": "round,pad=0.4", "fc": "white", "ec": orange})
    ax.annotate("Equal chemical potentials", xy=(1, 0), xytext=(1.5, 0.15),
                fontsize=11, color=green,
                arrowprops={"arrowstyle": "->", "color": green})
    ax.set(xlim=(0.18, 4), ylim=(-1.85, 1.55),
           xlabel=r"Pressure ratio $p_B/p_A$",
           ylabel=r"$(\mu_{{\rm m},B}-\mu_{{\rm m},A})/(RT)$")
    ax.set_xticks([0.25, 0.5, 1, 2, 3, 4], ["0.25", "0.5", "1", "2", "3", "4"])
    ax.grid(alpha=0.2)
    ax.spines[["top", "right"]].set_visible(False)
    fig.text(0.5, 0.215, "Initial transfer through an open valve between rigid compartments",
             ha="center", fontsize=12)
    fig.text(0.27, 0.135, r"$p_B<p_A$:  A $\longrightarrow$ B", ha="center", color=blue, fontsize=14)
    fig.text(0.74, 0.135, r"$p_B>p_A$:  B $\longrightarrow$ A", ha="center", color=orange, fontsize=14)
    fig.text(0.5, 0.055, "At fixed T and compartment volumes, transfer lowers total Helmholtz free energy.",
             ha="center", fontsize=11)
    fig.savefig(Path.cwd() / Path(__file__).with_suffix(".png").name,
                facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()