Skip to content
The Lumenary

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.

Back to the labExact computationExhaustive finite check
Spec, localisation and Nakayama in the rings Z/nPythonExact computationExhaustive finite checkComputationally tested
"""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.

Used in