codex/entropy-two-box-configuration-count.py

entropy-two-box-configuration-count.py
"""Enumerate the 16 binary configurations of four distinguishable labels.

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

from itertools import product
from pathlib import Path

import matplotlib

matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.patches import FancyBboxPatch


def main():
    strings = ["".join(bits) for bits in product("01", repeat=4)]
    groups = [[s for s in strings if s.count("0") == k] for k in range(5)]
    blue, orange, gray = "#216b9d", "#ac4b14", "#52616b"
    plt.rcParams.update({"font.size": 12, "figure.facecolor": "white",
                         "axes.facecolor": "white", "savefig.facecolor": "white"})
    fig, ax = plt.subplots(figsize=(9.5, 6.4), dpi=120)
    fig.subplots_adjust(left=0.03, right=0.97, bottom=0.03, top=0.80)
    fig.suptitle("Four labeled particles: sixteen configurations, five occupancies", fontsize=15, y=0.96)
    fig.text(0.5, 0.89, "Digits refer to labels A, B, C, D in that order.  0 = left; 1 = right.", ha="center")
    fig.text(0.5, 0.845, "Each configuration has probability 1/16 in the unrestricted uniform model.",
             ha="center", fontsize=11)
    ax.set(xlim=(-0.55, 4.55), ylim=(-2.65, 7.5))
    ax.axis("off")
    for k, group in enumerate(groups):
        ax.text(k, 7.20, f"k = {k} left", ha="center", weight="bold")
        ax.text(k, 6.70, f"Count {len(group)}; P = {len(group)}/16", ha="center", fontsize=10.5)
        for row, s in enumerate(group):
            y = 5.95 - row * 0.81
            edge = orange if s == "0000" else gray
            ax.add_patch(FancyBboxPatch((k - 0.37, y - 0.29), 0.74, 0.58,
                                       boxstyle="round,pad=0.06", facecolor="white",
                                       edgecolor=edge, linewidth=2 if s == "0000" else 1.0))
            for i, bit in enumerate(s):
                ax.text(k + (i - 1.5) * 0.15, y, bit, ha="center", va="center",
                        color=blue if bit == "0" else orange, fontfamily="monospace", fontsize=15)
        ax.text(k, 0.92, rf"$S/k_B=\ln {len(group)}$", ha="center", fontsize=11)
    ax.plot([-0.43, -0.43, 4.43, 4.43], [6.40, 0.48, 0.48, 6.40],
            color=blue, linewidth=1.6)
    ax.text(2, 0.03, r"Union of all columns: $\Omega=16$, $S/k_B=\ln 16=4\ln 2$", ha="center", color=blue)
    ax.add_patch(FancyBboxPatch((-0.30, -1.79), 1.85, 1.05,
                               boxstyle="round,pad=0.08", facecolor="white", edgecolor=orange, linewidth=1.8))
    ax.text(0.62, -1.03, "Initial restriction: all-left", ha="center", fontsize=11)
    ax.text(0.62, -1.46, r"0000 only: $\Omega=1$, $S=0$", ha="center", fontsize=11)
    ax.annotate("", xy=(2.25, -1.26), xytext=(1.7, -1.26),
                arrowprops={"arrowstyle": "->", "color": blue, "lw": 2})
    ax.text(3.27, -1.00, "Restriction removed: all 16", ha="center", fontsize=11, color=blue)
    ax.text(3.27, -1.48, r"$\Delta S=4k_B\ln2$", ha="center", fontsize=12, color=blue)
    ax.text(2, -2.32, "Balanced occupancy (k = 2) is just six configurations, not the whole allowed set.",
            ha="center", fontsize=11)
    fig.savefig(Path.cwd() / "entropy-two-box-configuration-count.png",
                facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()