codex/ising-four-spin-ring-correlations.py

ising-four-spin-ring-correlations.py
"""Exact correlations and fluctuations of a zero-field four-spin Ising ring.

Tested with Python 3.14, matplotlib 3.10.7 and numpy 2.3.5.
Run from _media; the opaque 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": 12, "figure.facecolor": "white", "axes.spines.top": False,
                         "axes.spines.right": False})
    k = np.linspace(0, 2.0, 501)
    z = 4 * np.cosh(4 * k) + 12
    correlation = np.sinh(4 * k) / (np.cosh(4 * k) + 3)
    variance_m = (2 * np.exp(4 * k) + 2) / z
    variance_h = 16 * (1 + 3 * np.cosh(4 * k)) / (np.cosh(4 * k) + 3) ** 2
    capacity = k**2 * variance_h
    fig, axes = plt.subplots(1, 2, figsize=(10.5, 5), dpi=100, facecolor="white")
    fig.subplots_adjust(left=0.07, right=0.97, bottom=0.29, top=0.79, wspace=0.29)
    fig.suptitle("Alignment can grow while mean magnetization stays zero", y=0.95, fontsize=16)
    axes[0].plot(k, correlation, color="#246caa", linewidth=2.5,
                 label=r"bond correlation $c_1$")
    axes[0].plot(k, variance_m, color="#ca691b", linewidth=2.5,
                 label=r"magnetization variance $\langle m^2\rangle$")
    axes[0].axhline(0, color="#4d575f", linewidth=1, linestyle=":")
    axes[0].set(ylim=(-0.04, 1.08), title="Neighbors agree; the sign still fluctuates",
                ylabel="Dimensionless correlation or variance")
    exact_k = np.log(2) / 4
    axes[0].scatter([exact_k, exact_k], [3 / 17, 6 / 17],
                    color=["#246caa", "#ca691b"], s=40, zorder=5)
    axes[0].annotate(r"$K=\ln(2)/4$" + "\n" + r"$c_1=3/17$" + "\n" + r"$\langle m^2\rangle=6/17$",
                     xy=(exact_k, 6 / 17), xytext=(0.65, 0.28),
                     arrowprops={"arrowstyle": "->", "color": "#65717b"}, fontsize=11)
    handles, labels = axes[0].get_legend_handles_labels()
    fig.legend(handles, labels, loc="lower center", bbox_to_anchor=(0.28, 0.075),
               frameon=False, fontsize=10)
    axes[1].plot(k, capacity, color="#6b5197", linewidth=2.5)
    axes[1].set(ylim=(0, 1.8), title="A smooth heat-capacity peak",
                ylabel=r"Total heat capacity $C_H/k_B$")
    axes[1].text(0.96, 0.90, r"$C_H/k_B=K^2\,\mathrm{Var}(H)/J^2$",
                 transform=axes[1].transAxes, ha="right", fontsize=11)
    fig.text(0.75, 0.105, "No finite-temperature singularity",
             ha="center", fontsize=10, color="#605471")
    for ax in axes:
        ax.set(xlim=(0, 2), xlabel=r"$K=\beta J$  (larger means colder)")
        ax.grid(alpha=0.2)
    fig.text(0.51, 0.035, r"Exact sixteen-state model: $N=4$, four edges, $h=0$, $J>0$.  $\langle m\rangle=0$ throughout.",
             ha="center", fontsize=11)
    fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()