codexentropy-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()