codex/regular-solution-liquid-coexistence.py

regular-solution-liquid-coexistence.py
"""Original symmetric regular-solution plots; writes a PNG in the current directory."""

from pathlib import Path
import math

import matplotlib.pyplot as plt
import numpy as np


def mixing_free_energy(x, chi):
    x = np.asarray(x, dtype=float)
    log_x = np.zeros_like(x)
    log_other = np.zeros_like(x)
    np.log(x, out=log_x, where=x > 0)
    np.log(1 - x, out=log_other, where=x < 1)
    return x * log_x + (1 - x) * log_other + chi * x * (1 - x)


def low_binodal(chi):
    assert chi >= 2
    if chi == 2:
        return 0.5
    low = 1e-12
    high = (1 - math.sqrt(1 - 2 / chi)) / 2
    for _ in range(100):
        middle = (low + high) / 2
        derivative = math.log(middle / (1 - middle)) + chi * (1 - 2 * middle)
        if derivative > 0:
            high = middle
        else:
            low = middle
    return (low + high) / 2


def main():
    alpha = low_binodal(3)
    beta = 1 - alpha
    z = 0.30
    phase_fraction = (z - alpha) / (beta - alpha)
    np.testing.assert_allclose((1 - phase_fraction) * alpha + phase_fraction * beta, z)
    assert 0 < phase_fraction < 1
    for chi in (2.1, 3, 4):
        endpoint = low_binodal(chi)
        np.testing.assert_allclose(
            math.log(endpoint / (1 - endpoint)) + chi * (1 - 2 * endpoint),
            0, atol=1e-12,
        )
        np.testing.assert_allclose(
            mixing_free_energy(endpoint, chi), mixing_free_energy(1 - endpoint, chi),
            atol=1e-14,
        )
    x = np.linspace(0, 1, 1201)
    minimum = float(mixing_free_energy(alpha, 3))
    assert np.all(mixing_free_energy(x, 3) >= minimum - 1e-12)
    assert minimum < float(mixing_free_energy(z, 3))

    plt.rcParams.update({"font.size": 11, "axes.titlesize": 11})
    fig, (energy, diagram) = plt.subplots(1, 2, figsize=(9.6, 4.8), dpi=100,
                                         facecolor="white")
    fig.subplots_adjust(left=0.105, right=0.985, bottom=0.16, top=0.80, wspace=0.28)
    fig.suptitle("Symmetric regular solution: coexistence and local stability",
                 fontsize=15, y=0.97)
    fig.text(0.5, 0.885,
             r"$h(x)=x\ln x+(1-x)\ln(1-x)+\chi x(1-x)$; original model calculation",
             ha="center", fontsize=12)
    energy.plot(x, mixing_free_energy(x, 3), color="#0072b2", lw=2.2,
                label="homogeneous liquid")
    energy.plot([alpha, beta], [minimum, minimum], color="#d55e00", lw=2.5,
                label="two-phase minimum")
    energy.plot([alpha, beta], [minimum, minimum], "o", color="#d55e00")
    energy.plot(z, mixing_free_energy(z, 3), "o", color="#333333", ms=5)
    energy.annotate("overall z = 0.30", xy=(z, mixing_free_energy(z, 3)),
                    xytext=(0.04, 0.067), fontsize=10,
                    arrowprops={"arrowstyle": "->", "color": "#666666"})
    energy.text(alpha, minimum - 0.008, r"$x_\alpha$", ha="center", va="top")
    energy.text(beta, minimum - 0.008, r"$x_\beta$", ha="center", va="top")
    energy.set(xlabel="Mole fraction x", ylabel=r"Mixing free energy $h=g_{mix}/(RT)$",
               title=r"Supporting common tangent at $\chi=3$",
               xlim=(0, 1), ylim=(-0.08, 0.09))
    energy.legend(loc="upper right", fontsize=9)

    chi = np.linspace(2, 4, 301)
    binodal = np.array([low_binodal(c) for c in chi])
    spinodal = (1 - np.sqrt(1 - 2 / chi)) / 2
    diagram.plot(binodal, chi, color="#0072b2", lw=2.2, label="binodal: coexistence")
    diagram.plot(1 - binodal, chi, color="#0072b2", lw=2.2)
    diagram.plot(spinodal, chi, color="#cc79a7", lw=2, ls="--",
                 label="spinodal: local stability")
    diagram.plot(1 - spinodal, chi, color="#cc79a7", lw=2, ls="--")
    diagram.plot([alpha, beta], [3, 3], color="#777777", lw=1, ls=":")
    diagram.plot([alpha, beta], [3, 3], "o", color="#0072b2", ms=4)
    diagram.set(xlabel="Mole fraction x", ylabel=r"Interaction parameter $\chi$",
                title="Binodal and spinodal in the model",
                xlim=(0, 1), ylim=(1.95, 4.05))
    diagram.legend(loc="upper center", fontsize=9)
    for ax in (energy, diagram):
        ax.grid(alpha=0.20)
        ax.set_facecolor("white")
        ax.spines[["top", "right"]].set_visible(False)
    fig.savefig(Path.cwd() / "regular-solution-liquid-coexistence.png", dpi=100,
                transparent=False, facecolor="white")
    plt.close(fig)
    print(f"chi=3: x_alpha={alpha:.10f}, x_beta={beta:.10f}, "
          f"beta mole fraction at z=0.30: {phase_fraction:.10f}")


if __name__ == "__main__":
    main()