Math laboratory
Units, zero divisors and ideals of Z/nZ
Classifies every residue of Z/nZ as unit, zero divisor or zero, finds nilpotents, idempotents and all ideals by brute force, and checks for n up to 60 that units and zero divisors split the non-zero residues, that ideals match divisors of n, and that domain = field = prime. Exhaustive for these n; the general proofs are in the lesson.
"""Units, zero divisors, nilpotents and ideals in the rings Z/nZ.
For each n the program sorts the residues 0, ..., n - 1 into units (elements
with a multiplicative inverse) and zero divisors (non-zero a with ab = 0 for
some non-zero b), finds the nilpotent elements (a^k = 0) and the idempotents
(e^2 = e), and lists all ideals by brute force. It confirms, for every n up to
60, that each non-zero residue is exactly one of unit or zero divisor, that the
units number phi(n) = #{a : gcd(a, n) = 1}, that the ideals correspond to the
divisors of n, and that Z/nZ is a domain exactly when it is a field, exactly
when n is prime. Exact and exhaustive for these n only.
"""
from math import gcd
def units(n):
"""Residues a in Z/nZ with some b such that a * b = 1 (mod n)."""
return [a for a in range(n) if any(a * b % n == 1 % n for b in range(n))]
def zero_divisors(n):
"""Non-zero residues a with a * b = 0 (mod n) for some non-zero b."""
return [a for a in range(1, n) if any(a * b % n == 0 for b in range(1, n))]
def nilpotents(n):
"""Residues a with a^k = 0 (mod n) for some k >= 1 (k = n suffices)."""
return [a for a in range(n) if pow(a, n, n) == 0]
def idempotents(n):
"""Residues e with e * e = e (mod n)."""
return [e for e in range(n) if e * e % n == e]
def ideals(n):
"""All ideals of Z/nZ, found by brute force as sorted tuples.
An ideal is an additive subgroup closed under multiplication by every
residue. Every subset generated by one element is checked, and the ideal
generated by a set is the one generated by the gcd, so this is complete;
the test file also checks completeness against all subsets for small n.
"""
found = set()
for a in range(n):
ideal = tuple(sorted({a * r % n for r in range(n)}))
found.add(ideal)
return sorted(found, key=len)
def is_ideal(subset, n):
"""True when subset is non-empty, closed under +, and absorbs products."""
s = set(subset)
return bool(s) and all((x + y) % n in s and x * r % n in s
for x in s for y in s for r in range(n))
def is_prime(n):
"""Trial division."""
return n >= 2 and all(n % p for p in range(2, int(n ** 0.5) + 1))
def main():
print(" n units zero divisors ideals idempotents non-zero nilpotents")
for n in (2, 4, 6, 7, 8, 9, 10, 12, 15, 30):
u, z = units(n), zero_divisors(n)
nil = [a for a in nilpotents(n) if a]
print(f"{n:>2} {len(u):>5} {len(z):>13} {len(ideals(n)):>6} "
f"{len(idempotents(n)):>11} {nil}")
split = phi = divisors = domain = True
for n in range(2, 61):
u, z = set(units(n)), set(zero_divisors(n))
split &= u.isdisjoint(z) and u | z == set(range(1, n))
phi &= len(u) == sum(gcd(a, n) == 1 for a in range(n))
divisors &= len(ideals(n)) == sum(n % d == 0 for d in range(1, n + 1))
domain &= (not z) == (len(u) == n - 1) == is_prime(n)
print("For every n from 2 to 60:")
print(f" each non-zero residue is a unit or a zero divisor, not both: {split}")
print(f" number of units = phi(n) = #{{a : gcd(a, n) = 1}}: {phi}")
print(f" number of ideals = number of divisors of n: {divisors}")
print(f" no zero divisors <=> every non-zero a a unit <=> n prime: {domain}")
print()
print("Epistemic status: exact, exhaustive checks for n <= 60. In a finite")
print("commutative ring every non-zero element is a unit or a zero divisor,")
print("and Z/nZ is a field exactly when n is prime: those are theorems, proved")
print("in the lesson; the table is evidence and illustration, not proof.")
if __name__ == "__main__":
main()Runs entirely in your browser, in an isolated worker. Nothing is sent to a server.