Skip to content
The Lumenary

Math laboratory

Divisibility orders, lattices and a least fixed point

Computes the Hasse diagram of divisibility on {1, ..., 12} and its maximal elements, checks exhaustively that the divisors of 60 form a lattice (meet gcd, join lcm) while {1, ..., 12} does not, and finds the least fixed point of a monotone map on subsets both by iteration from the empty set and as the meet of all pre-fixed points (Knaster-Tarski).

Back to the labExact computationExhaustive finite check
Divisibility orders, lattices and a least fixed pointPythonExact computationExhaustive finite checkComputationally tested
"""Divisibility as an order: Hasse diagrams, lattices and a least fixed point.

The experiment lists the covering pairs of ({1, ..., 12}, |) - the edges of
its Hasse diagram - and its minimal and maximal elements; checks that the
divisors of 60 form a lattice with meet gcd and join lcm, while {1, ..., 12}
is not a lattice; and computes a least fixed point two ways, as the
Knaster-Tarski theorem predicts: by iterating a monotone map on the subsets of
a finite set starting from the empty set, and as the intersection of all
sets S with f(S) contained in S. Exact and exhaustive for these examples.
"""

import itertools
import math

def covers(elements, leq):
    """Pairs (a, b) with a < b and nothing strictly between: Hasse edges."""
    return [(a, b) for a in elements for b in elements
            if a != b and leq(a, b)
            and not any(c not in (a, b) and leq(a, c) and leq(c, b)
                        for c in elements)]

def divides(a, b):
    """a | b."""
    return b % a == 0

def maximal(elements, leq):
    """Elements with no strictly larger element."""
    return [m for m in elements
            if not any(x != m and leq(m, x) for x in elements)]

def least_upper_bound(elements, leq, a, b):
    """The join of a and b inside `elements`, or None."""
    ups = [u for u in elements if leq(a, u) and leq(b, u)]
    least = [u for u in ups if all(leq(u, v) for v in ups)]
    return least[0] if least else None

def lattice_check(elements, leq):
    """Return the first pair without a join, or None if all joins exist."""
    for a, b in itertools.combinations(elements, 2):
        if least_upper_bound(elements, leq, a, b) is None:
            return a, b
    return None

EDGES = {0: {1, 2}, 1: {3}, 2: {3}, 3: {4}, 4: set(), 5: {6}, 6: {5}, 7: {0}}

def step(s, start=0, edges=EDGES):
    """f(S) = {start} together with every successor of S: monotone in S."""
    return frozenset({start}) | frozenset(t for v in s for t in edges[v])

def lfp_by_iteration(f):
    """Kleene iteration: empty set, f(empty), f(f(empty)), ... until stable."""
    s, rounds = frozenset(), 0
    while f(s) != s:
        s, rounds = f(s), rounds + 1
    return s, rounds

def lfp_by_prefixed_points(f, universe):
    """Intersection of all S with f(S) a subset of S (Knaster-Tarski)."""
    result = frozenset(universe)
    for r in range(len(universe) + 1):
        for s in itertools.combinations(universe, r):
            s = frozenset(s)
            if f(s) <= s:
                result &= s
    return result

def main():
    nums = list(range(1, 13))
    edges = covers(nums, divides)
    print(f"Hasse diagram of ({{1..12}}, |): {len(edges)} covering pairs")
    print("  " + " ".join(f"{a}-{b}" for a, b in edges[:12]))
    print("  " + " ".join(f"{a}-{b}" for a, b in edges[12:]))
    print("  minimal:", maximal(nums, lambda a, b: divides(b, a)),
          " maximal:", maximal(nums, divides))
    print("  pair with no join in {1..12}:", lattice_check(nums, divides))
    d60 = [d for d in range(1, 61) if 60 % d == 0]
    ok = all(least_upper_bound(d60, divides, a, b) == math.lcm(a, b)
             for a in d60 for b in d60)
    print(f"divisors of 60 ({len(d60)}): every join exists and is the lcm:", ok)
    print("  pair with no join among divisors of 60:",
          lattice_check(d60, divides))
    print()
    fix, rounds = lfp_by_iteration(step)
    meet = lfp_by_prefixed_points(step, range(8))
    print("reachable from 0 in a graph = least fixed point of S -> {0} u succ(S)")
    print(f"  iteration from the empty set: {sorted(fix)} after {rounds} rounds")
    print(f"  meet of all pre-fixed points: {sorted(meet)}")
    print("  the two agree:", fix == meet)
    print()
    print("Epistemic status: exact, exhaustive computations for these finite")
    print("examples. They illustrate the definitions and the Knaster-Tarski")
    print("theorem, which the lesson proves for every complete lattice.")

if __name__ == "__main__":
    main()
Runs entirely in your browser, in an isolated worker. Nothing is sent to a server.

Used in