codex/boltzmann-entropy-binomial-macrostates.py

boltzmann-entropy-binomial-macrostates.py
"""Count binary configurations of 20 labeled particles by left occupancy.

Run from _media: MPLCONFIGDIR=/tmp/codex-wiki-matplotlib python3
../boltzmann-entropy-binomial-macrostates.py
Writes its matching PNG basename to the current working directory.
Tested with Python 3.14.4, Matplotlib 3.10.7 (Debian +dfsg1).
"""

from math import comb, log
from pathlib import Path

import matplotlib

matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.ticker import FuncFormatter, MaxNLocator


def main():
    n = 20
    ks = list(range(n + 1))
    counts = [comb(n, k) for k in ks]
    entropies = [log(count) for count in counts]
    plt.rcParams.update({"font.size": 12, "figure.facecolor": "white",
                         "axes.facecolor": "white", "savefig.facecolor": "white"})
    fig, axes = plt.subplots(1, 2, figsize=(10, 5.3), dpi=120)
    fig.subplots_adjust(left=0.085, right=0.97, bottom=0.23, top=0.76, wspace=0.32)
    fig.suptitle("Twenty labels, two equal boxes: count each occupancy", fontsize=16, y=0.96)
    fig.text(0.5, 0.87, "One string per left/right assignment; all 1,048,576 strings equally likely",
             ha="center", fontsize=12)
    blue, orange = "#216b9d", "#ac4b14"
    axes[0].bar(ks, counts, color=blue, width=0.72)
    axes[0].bar([10], [counts[10]], color=orange, width=0.72)
    axes[0].set_ylabel(r"Count $\Omega(k)=\binom{20}{k}$")
    axes[0].set_ylim(0, 245000)
    axes[0].yaxis.set_major_formatter(FuncFormatter(lambda v, _: f"{v:,.0f}"))
    axes[0].annotate("184,756 at k = 10", xy=(10, counts[10]), xytext=(10, 222000),
                     ha="center", fontsize=11, color=orange,
                     arrowprops={"arrowstyle": "->", "color": orange})
    axes[1].plot(ks, entropies, "o-", color=blue, markersize=4, linewidth=1.8)
    axes[1].scatter([10], [entropies[10]], color=orange, zorder=3, s=45)
    axes[1].set_ylabel(r"Entropy $S(k)/k_B=\ln\Omega(k)$")
    axes[1].set_ylim(-0.6, 16.0)
    axes[1].annotate("12.126791 at k = 10", xy=(10, entropies[10]), xytext=(10, 14.4),
                     ha="center", fontsize=11, color=orange,
                     arrowprops={"arrowstyle": "->", "color": orange})
    for ax in axes:
        ax.set(xlim=(-0.7, 20.7), xlabel="Number k in the left box")
        ax.set_xticks([0, 5, 10, 15, 20])
        ax.yaxis.set_major_locator(MaxNLocator(nbins=5))
        ax.grid(axis="y", alpha=0.22)
        ax.set_axisbelow(True)
        ax.spines[["top", "right"]].set_visible(False)
    fig.text(0.5, 0.12, r"All-left: $k=20$, $\Omega=1$, $S/k_B=0$; all-right is symmetric.",
             ha="center", fontsize=12)
    fig.text(0.5, 0.055, r"One balanced macrostate: $\ln 184756$; all allowed strings together: $20\ln 2=13.862944$.",
             ha="center", fontsize=11)
    fig.savefig(Path.cwd() / "boltzmann-entropy-binomial-macrostates.png",
                facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()