codexfrustrated-ising-triangle-states.py
frustrated-ising-triangle-states.py
"""Draw an exactly enumerable Ising triangle; write the PNG to the cwd."""
from pathlib import Path
import itertools
import matplotlib.pyplot as plt
import numpy as np
def energy(spins):
return sum(spins[i] * spins[(i + 1) % 3] for i in range(3))
def main():
fig, axes = plt.subplots(1, 3, figsize=(12, 4.4), layout="constrained")
points = np.array([[0, 1], [-0.866, -0.5], [0.866, -0.5]])
for ax, spins, title in zip(axes[:2], [(1, -1, 1), (1, 1, 1)],
["Ground state: one unsatisfied bond", "Aligned state: three unsatisfied bonds"]):
for i in range(3):
j = (i + 1) % 3
bad = spins[i] == spins[j]
ax.plot(*points[[i, j]].T, color="#d55e00" if bad else "#0072b2",
lw=4, ls="--" if bad else "-")
for (x, y), spin in zip(points, spins):
ax.scatter(x, y, s=850, c="white", edgecolors="#333333", zorder=3)
ax.annotate("↑" if spin == 1 else "↓", (x, y), ha="center", va="center",
fontsize=29, zorder=4)
ax.set_title(title, fontsize=12)
ax.text(0, -0.91, f"Energy / J = {energy(spins):+d}", ha="center", fontsize=13)
ax.set(xlim=(-1.3, 1.3), ylim=(-1.1, 1.5), aspect="equal")
ax.axis("off")
states = list(itertools.product((-1, 1), repeat=3))
levels, counts = np.unique([energy(s) for s in states], return_counts=True)
axes[2].bar(levels, counts, width=1.0, color=["#0072b2", "#d55e00"])
axes[2].set(xticks=levels, yticks=range(7), ylim=(0, 7), xlabel="Energy / J",
ylabel="Number of spin configurations", title="All eight configurations")
for level, count in zip(levels, counts):
axes[2].text(level, count + .12, str(count), ha="center", fontsize=13)
fig.suptitle("Three antiferromagnetic Ising bonds cannot all be satisfied", fontsize=15)
fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), dpi=140, facecolor="white")
plt.close(fig)
if __name__ == "__main__":
main()