codexresidue-ring-multiplication.py
residue-ring-multiplication.py
"""Compare computed residue-ring products; write the matching PNG to cwd.
Tested with Python 3.14.4, NumPy 2.3.5, and Matplotlib 3.10.7.
"""
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
import numpy as np
def multiplication_table(n):
residues = np.arange(n, dtype=int)
return np.outer(residues, residues) % n
def main():
six, five = multiplication_table(6), multiplication_table(5)
assert six[2].tolist() == [0, 2, 4, 0, 2, 4]
assert np.flatnonzero(six[2] == 2).tolist() == [1, 4]
assert np.flatnonzero(six[2] == 1).tolist() == []
assert six[2, 3] == six[4, 3] == 0
assert [a for a in range(6) if 1 in six[a]] == [1, 5]
for row in five[1:]:
assert sorted(row.tolist()) == list(range(5))
assert [int(np.flatnonzero(five[a] == 1)[0]) for a in range(1, 5)] == [1, 3, 2, 4]
fig, axes = plt.subplots(1, 2, figsize=(9.2, 5.3), dpi=120)
for ax, n, table, color in zip(axes, (6, 5), (six, five), ("#b95f22", "#176997")):
for a in range(n):
for b in range(n):
nontrivial_zero = a != 0 and b != 0 and table[a, b] == 0
fill = "#ffe0d8" if nontrivial_zero else (
"#fff0df" if n == 6 and a == 2 else (
"#e7f0f7" if n == 5 and a != 0 else "#f6f7f8"))
ax.add_patch(Rectangle((b - 0.5, a - 0.5), 1, 1,
facecolor=fill, edgecolor="white", linewidth=2))
ax.text(b, a, str(table[a, b]), ha="center", va="center", fontsize=15,
color="#a62b25" if nontrivial_zero else "#243441",
fontweight="bold" if nontrivial_zero else "normal")
if n == 6:
ax.add_patch(Rectangle((-0.5, 1.5), n, 1, fill=False,
edgecolor=color, linewidth=2))
ax.set_xticks(range(n))
ax.set_yticks(range(n))
ax.tick_params(length=0, labelsize=12, pad=7)
ax.xaxis.tick_top()
ax.xaxis.set_label_position("top")
ax.set_xlabel("second factor", fontsize=11, labelpad=10)
ax.set_ylabel("first factor", fontsize=11, labelpad=9)
ax.set_xlim(-0.55, n - 0.45)
ax.set_ylim(n - 0.45, -0.55)
ax.set_aspect("equal")
for spine in ax.spines.values():
spine.set_visible(False)
ax.set_title(f"Multiplication modulo {n}", color=color, fontsize=14, pad=47)
axes[0].text(0.5, -0.13, "Row 2 repeats: 0, 2, 4, 0, 2, 4\nRed: nonzero factors give zero",
transform=axes[0].transAxes, ha="center", va="top", fontsize=11)
axes[1].text(0.5, -0.13, "Every nonzero row contains each output once\nEach has one entry equal to 1: an inverse",
transform=axes[1].transAxes, ha="center", va="top", fontsize=11)
fig.suptitle("A nonzero multiplier can lose information", fontsize=16, y=0.985)
fig.subplots_adjust(left=0.085, right=0.98, bottom=0.20, top=0.75, wspace=0.35)
fig.savefig(Path.cwd() / Path(__file__).with_suffix(".png").name, facecolor="white")
plt.close(fig)
if __name__ == "__main__":
main()