codextriangular-numbers-pairs-and-square.py
triangular-numbers-pairs-and-square.py
"""Original pair-counting and square-partition diagram with exact counts.
Tested with Python 3.14.4, matplotlib 3.10.7 and numpy 2.3.5.
Run from _media; writes its matching PNG into the current directory.
"""
from itertools import combinations
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
import numpy as np
def main():
n = 5
blue, orange = "#235d9f", "#d4782a"
plt.rcParams.update({"font.size": 12, "figure.facecolor": "white"})
fig, axes = plt.subplots(1, 2, figsize=(11, 5.4))
fig.subplots_adjust(left=0.05, right=0.96, bottom=0.18, top=0.77, wspace=0.18)
fig.suptitle("The same triangular counts organize pairs and squares", fontsize=18, y=0.97)
fig.text(0.5, 0.885, "Exact counting examples; the drawings represent mathematical objects, not measurements", ha="center", fontsize=11)
angles = np.pi / 2 + np.arange(n) * 2 * np.pi / n
points = np.column_stack([np.cos(angles), np.sin(angles)])
ax = axes[0]
for i, j in combinations(range(n), 2):
ax.plot(points[[i, j], 0], points[[i, j], 1], color="#8eaac4", lw=1.8, zorder=1)
ax.scatter(points[:, 0], points[:, 1], color=blue, s=180, zorder=3)
for label, point in zip("ABCDE", points, strict=True):
ax.text(*(point * 1.24), label, ha="center", va="center", fontsize=14, color=blue)
ax.set_title("Five objects: ten unordered pairs", fontsize=14, pad=14)
ax.set(xlim=(-1.4, 1.4), ylim=(-1.2, 1.4))
ax.text(0, -1.35, r"$\binom{5}{2}=4+3+2+1=T_4=10$", ha="center", fontsize=13)
ax = axes[1]
count = {"blue": 0, "orange": 0}
for row in range(n):
for col in range(n):
lower = col <= row
count["blue" if lower else "orange"] += 1
ax.add_patch(Rectangle((col, n - row - 1), 1, 1, facecolor=blue if lower else orange, edgecolor="white", lw=2))
assert count == {"blue": 15, "orange": 10}
ax.set_title("Consecutive triangles fill a square", fontsize=14, pad=14)
ax.set(xlim=(-0.2, n + 0.2), ylim=(-0.2, n + 0.2))
ax.text(n / 2, -0.55, r"$T_5+T_4=15+10=25=5^2$", ha="center", fontsize=13)
for ax in axes:
ax.set_aspect("equal")
ax.axis("off")
fig.text(0.5, 0.05, "Left: line crossings are not extra vertices. Right: the blue triangle includes the diagonal cells.", ha="center", fontsize=11)
fig.savefig(Path(__file__).with_suffix(".png").name, dpi=110, facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()