codexset-partitions-stirling-three-elements.py
set-partitions-stirling-three-elements.py
"""Draw an exact enumeration, not measured data. Python 3.14 / Matplotlib 3.10.
Run from _media; outlines and colours separate blocks but do not label them.
"""
from itertools import product
from pathlib import Path
import matplotlib.pyplot as plt
from matplotlib.patches import Circle, FancyBboxPatch
def enumerate_partitions(n):
"""Enumerate canonical restricted-growth words independently of S(n,k)."""
if n == 0:
return [()]
partitions = []
for word in product(range(n), repeat=n):
if word[0] != 0:
continue
if any(word[i] > 1 + max(word[:i]) for i in range(1, n)):
continue
partitions.append(tuple(
tuple(i + 1 for i, label in enumerate(word) if label == j)
for j in range(max(word) + 1)
))
return partitions
def draw_partition(ax, partition, x, y):
colours = ["#1b6d97", "#975725", "#6f519b"]
widths = [0.43 * len(block) + 0.17 for block in partition]
total = sum(widths) + 0.13 * (len(widths) - 1)
cursor = x - total / 2
for block, width, colour in zip(partition, widths, colours):
ax.add_patch(FancyBboxPatch(
(cursor, y - 0.29), width, 0.58,
boxstyle="round,pad=0.025,rounding_size=0.12",
linewidth=2, edgecolor=colour, facecolor="white",
))
for j, element in enumerate(block):
px = cursor + 0.30 + 0.43 * j
ax.add_patch(Circle((px, y), 0.17, facecolor="#f2f5f7", edgecolor="none"))
ax.text(px, y, str(element), ha="center", va="center", fontsize=14)
cursor += width + 0.13
notation = "{" + ", ".join("{" + ",".join(map(str, b)) + "}" for b in partition) + "}"
ax.text(x, y - 0.53, notation, ha="center", va="center", fontsize=13)
def main():
partitions = enumerate_partitions(3)
assert len(partitions) == 5
assert [sum(len(p) == k for p in partitions) for k in (1, 2, 3)] == [1, 3, 1]
fig, ax = plt.subplots(figsize=(10.4, 6.4), dpi=100, facecolor="white")
fig.subplots_adjust(left=0.025, right=0.975, bottom=0.03, top=0.98)
ax.set(xlim=(0, 10), ylim=(0, 6.1))
ax.axis("off")
ax.text(5, 5.78, "All five set partitions of {1, 2, 3}", ha="center", fontsize=20, weight="bold")
ax.text(5, 5.37, "Exact enumeration • elements are distinct; blocks are unnamed", ha="center", fontsize=13)
for k, y in [(1, 4.4), (2, 2.8), (3, 1.2)]:
row = [p for p in partitions if len(p) == k]
label = (f"{k} block" + ("s" if k > 1 else "") + f" → {len(row)} "
+ ("possibility" if len(row) == 1 else "possibilities"))
ax.text(0.08, y + 0.53, label, fontsize=12, color="#333333")
positions = [5] if len(row) == 1 else [1.7, 5, 8.3]
for p, x in zip(row, positions):
draw_partition(ax, p, x, y)
ax.text(5, 0.12, "Reordering blocks or elements within a block gives the same partition.", ha="center", fontsize=12)
fig.savefig(Path(__file__).with_suffix('.png').name, facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()