Math laboratory
Spec, localisation and Nakayama in the rings Z/n
For every n <= 60 lists the ideals of Z/n, finds the prime and maximal ideals, the nilradical and the Jacobson radical, decides whether Z/n is local, computes the localisation at each prime from the definition, and checks Nakayama's lemma for every ideal of every local Z/p^e in range. Exhaustive in that range; the lesson proves the general statements.
"""Spec, localisation and Nakayama in the finite rings Z/n, by brute force.
The ideals of Z/n are dZ/n for d | n, so everything about Z/n can be checked
by listing elements. For every n <= 60 we compute: the prime and maximal
ideals (they coincide: Z/n has Krull dimension 0), the nilradical (the
nilpotent elements) and the Jacobson radical (intersection of the maximal
ideals), whether Z/n is local, and the localisation at each prime p,
computed from the definition S^-1 R = R / {a : sa = 0 for some s in S},
S = R minus (p), which turns out to be Z/p^e with p^e || n. Finally it checks
Nakayama's lemma for every ideal of every local ring Z/p^e: mM = M forces
M = 0. Exhaustive over a finite range, exact arithmetic.
"""
def ideal(n, d):
"""The ideal generated by d in Z/n, as a frozenset of residues."""
return frozenset(d * k % n for k in range(n))
def ideals(n):
"""All ideals of Z/n (one for each divisor d of n)."""
return {d: ideal(n, d) for d in range(1, n + 1) if n % d == 0}
def is_prime_ideal(n, i):
"""Proper, and ab in I implies a in I or b in I."""
if len(i) == n:
return False
return all(a in i or b in i for a in range(n) for b in range(n)
if a * b % n in i)
def is_maximal_ideal(n, i):
"""Proper, and no ideal strictly between I and Z/n."""
others = ideals(n).values()
return len(i) < n and not any(i < j < frozenset(range(n)) for j in others)
def nilradical(n):
"""Nilpotent elements of Z/n."""
return frozenset(a for a in range(n) if any(pow(a, k, n) == 0
for k in range(1, n + 1)))
def jacobson(n):
"""Intersection of the maximal ideals of Z/n (n >= 2)."""
maxes = [i for i in ideals(n).values() if is_maximal_ideal(n, i)]
out = frozenset(range(n))
for m in maxes:
out &= m
return out
def localisation_size(n, p):
"""|S^-1 (Z/n)| for S = Z/n minus (p), from the definition."""
prime = ideal(n, p)
s = [a for a in range(n) if a not in prime]
killed = {a for a in range(n) if any(si * a % n == 0 for si in s)}
return n // len(killed)
def p_part(n, p):
"""p^e with p^e exactly dividing n."""
q = 1
while n % p == 0:
n, q = n // p, q * p
return q
def prime_divisors(n):
"""The set of primes dividing n."""
return {p for p in range(2, n + 1)
if n % p == 0 and all(p % q for q in range(2, p))}
def nakayama_holds(n):
"""For local Z/n with maximal ideal m: mM = M implies M = 0, all ideals M."""
(m,) = [i for i in ideals(n).values() if is_maximal_ideal(n, i)]
for big_m in ideals(n).values():
m_times = frozenset((a * b) % n for a in m for b in big_m)
if m_times == big_m and big_m != frozenset({0}):
return False
return True
def main():
ns = range(2, 61)
agree = loc_ok = rad_ok = 0
local, nak = [], True
for n in ns:
ids = ideals(n)
primes = {d for d, i in ids.items() if is_prime_ideal(n, i)}
maxes = {d for d, i in ids.items() if is_maximal_ideal(n, i)}
agree += primes == maxes
rad_ok += nilradical(n) == jacobson(n)
loc_ok += all(localisation_size(n, p) == p_part(n, p) for p in primes)
if len(maxes) == 1:
local.append(n)
nak = nak and nakayama_holds(n)
print(f"Rings Z/n for 2 <= n <= 60 ({len(ns)} rings):")
print(f" prime ideals = maximal ideals (dimension 0): {agree} rings")
print(f" nilradical = Jacobson radical: {rad_ok} rings")
print(f" localisation at (p) has p^e elements, p^e||n: {loc_ok} rings")
print(f" local rings: n = {local[:12]} ...")
print(" all local n are prime powers:",
all(len(prime_divisors(n)) == 1 for n in local))
print(f" Nakayama (mM = M implies M = 0) in every local Z/n: {nak}")
n = 12
primes = sorted(d for d, i in ideals(n).items() if is_prime_ideal(n, i))
print(f"Example n = {n}: prime ideals generated by {primes}, nilradical "
f"{sorted(nilradical(n))},")
print(" sizes of the localisations at these primes: "
f"{[localisation_size(n, p) for p in primes]}")
print()
print("Epistemic status: exhaustive and exact for n <= 60, so every "
"statement printed\nis verified in that range. It illustrates, "
"but does not prove, the general\ntheorems (Z/n is Artinian; "
"Z/n localised at (p) is Z/p^e) proved in the lesson.")
if __name__ == "__main__":
main()Runs entirely in your browser, in an isolated worker. Nothing is sent to a server.