codexrenormalization-flow-and-correlation-length.py
renormalization-flow-and-correlation-length.py
"""An analytic infrared-flow and correlation-length scaling example.
Tested on Python 3.14, NumPy 2.3.5, Matplotlib 3.10.7; no SciPy.
Run from _media to emit this script's basename as PNG.
"""
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
EPSILON = 0.2
THERMAL_EIGENVALUE = 1.5
def flow(ell, g0):
return EPSILON / (1 + (EPSILON/g0 - 1)*np.exp(-EPSILON*np.asarray(ell)))
def correlation_length(t, amplitude=1):
return amplitude*np.abs(np.asarray(t))**(-1/THERMAL_EIGENVALUE)
def checks():
ell = np.linspace(0, 30, 301)
h = 1e-4
residuals = []
for g0 in (.025, .05, .12, .3, .6):
g = flow(ell, g0)
derivative = (flow(ell+h, g0)-flow(ell-h, g0))/(2*h)
residuals.append(float(np.max(np.abs(derivative-(EPSILON*g-g*g)))))
assert np.all(g >= min(g0, EPSILON)-1e-14)
assert np.all(g <= max(g0, EPSILON)+1e-14)
assert max(residuals) < 2e-9
assert np.isclose(flow(np.log(1024), .05), 4/35)
assert np.isclose(flow(np.log(1024), .6), 6/25)
t = np.geomspace(1e-6, 1e-2, 81)
scaling_residual = 0.0
for b in (2., 4., 10.):
for amplitude in (1., 2.):
lhs = correlation_length(t, amplitude)
rhs = b*correlation_length(b**THERMAL_EIGENVALUE*t, amplitude)
scaling_residual = max(scaling_residual, float(np.max(np.abs(rhs/lhs-1))))
assert scaling_residual < 2e-14
slope = float(np.polyfit(np.log(t), np.log(correlation_length(t)), 1)[0])
assert abs(slope + 2/3) < 1e-13
return {"flow_derivative_max_residual": max(residuals),
"correlation_scaling_max_relative_residual": scaling_residual,
"log_slope": slope, "eigenvalues": [-EPSILON, THERMAL_EIGENVALUE]}
def main():
print(checks())
plt.rcParams.update({"font.size": 11, "font.family": "DejaVu Sans"})
fig, axes = plt.subplots(1, 2, figsize=(10.8, 4.8), facecolor="white")
fig.subplots_adjust(left=.075, right=.98, bottom=.23, top=.80, wspace=.34)
ell = np.linspace(0, 30, 400)
colors = ["#376b9f", "#4f8a68", "#9c7b36", "#9860a4", "#bd584c"]
for g0, color in zip((.025, .05, .12, .3, .6), colors):
axes[0].plot(ell, flow(ell, g0), color=color, lw=2, label=f"{g0:g}")
axes[0].axhline(EPSILON, color="#242b37", ls="--", lw=1.3)
axes[0].text(29, .215, r"$g_*=1/5$", ha="right", fontsize=11)
axes[0].set(xlabel=r"Increasing log length $\ell=\log(L/a)$ →",
ylabel="Dimensionless coupling g", xlim=(0, 30), ylim=(0, .63))
axes[0].legend(title=r"Initial $g_0$", fontsize=9, title_fontsize=9,
loc="upper right", frameon=False, ncol=2)
axes[0].set_title(r"$dg/d\ell=g/5-g^2$", pad=13, fontsize=13)
t = np.geomspace(1e-6, 1e-2, 120)
ax = axes[1]
ax.loglog(t, correlation_length(t), lw=2, color="#376b9f", label=r"$\xi/a,\ C_+=1$")
ax.loglog(t, correlation_length(t, 2), lw=2, color="#bd584c", label=r"$\xi/a,\ C_+=2$")
selected = np.geomspace(1e-6, 1e-2, 9)
ax.loglog(selected, correlation_length(selected, 2)/2, "o", ms=5,
mfc="white", mec="#bd584c", mew=1.4, label=r"$\xi/(aC_+),\ C_+=2$")
ax.set(xlabel=r"Positive thermal coordinate $t$", ylabel="Correlation length in lattice units",
xlim=(7e-7, 1.4e-2), ylim=(12, 30000))
ax.set_title(r"Chosen $y_t=3/2$: $\nu=2/3$", pad=13, fontsize=13)
ax.legend(fontsize=9, loc="upper right", frameon=False)
ax.text(.04, .10, "Normalize the amplitude:\nred circles collapse onto blue.",
transform=ax.transAxes, fontsize=10, color="#434d5d")
for axis in axes:
axis.spines[["top", "right"]].set_visible(False)
axis.grid(alpha=.14)
fig.suptitle("A solvable model of infrared attraction and thermal escape", fontsize=14, y=.96, weight="bold")
fig.text(.075, .075, "Analytic example with chosen coefficients; not measured data or a precision 3D Ising calculation.",
fontsize=10, color="#434d5d")
fig.savefig(Path(__file__).stem + ".png", dpi=120, facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()