Skip to content
The Lumenary

Math laboratory

Truth values in presheaf toposes

Computes the sieves Omega(p) for presheaves on small finite posets, checks exhaustively for all presheaves on {0 < 1} with at most 4 and 3 sections that the characteristic map is a bijection from subpresheaves to natural maps into Omega, and computes Heyting negation on truth values, exhibiting failures of excluded middle. Finite cases only: the general theorems are proved in the lesson.

Back to the labExact computationExhaustive finite check
Truth values in presheaf toposesPythonExact computationExhaustive finite checkComputationally tested
"""The subobject classifier of a presheaf topos, computed exactly.

For presheaves on a finite poset P, the classifier Omega has Omega(p) = the
sieves on p (down-closed subsets of the principal down-set of p), and the
global truth values are the down-sets of P. This experiment

1. counts sieves on a chain (|Omega(k)| = k + 2) and on other small posets;
2. checks, for every presheaf A on {0 < 1} with |A1| <= 4 and |A0| <= 3,
   that A has exactly as many subpresheaves as natural maps A -> Omega, and
   that the characteristic map chi is a bijection between them;
3. computes the Heyting algebra of truth values and finds where excluded
   middle fails.

Evidence: exact computation and exhaustive checks of finitely many cases.
"""

import itertools
import textwrap

def down_sets(elements, leq):
    """All down-closed subsets of the finite poset (elements, leq)."""
    found = []
    for r in range(len(elements) + 1):
        for combo in itertools.combinations(elements, r):
            s = frozenset(combo)
            if all(q in s for p in s for q in elements if leq(q, p)):
                found.append(s)
    return found

def sieves_on(p, elements, leq):
    """Sieves on p: down-closed subsets of {q | q <= p}."""
    below = [q for q in elements if leq(q, p)]
    return down_sets(below, leq)

def chain(n):
    """The chain 0 < 1 < ... < n-1 as (elements, leq)."""
    return list(range(n)), lambda a, b: a <= b

def heyting_implies(u, v, opens):
    """Largest member w of `opens` with w & u <= v (the Heyting implication)."""
    best = frozenset()
    for w in opens:
        if w & u <= v:
            best = best | w
    assert best in opens
    return best

def excluded_middle_failures(elements, leq):
    """Down-sets u with u | (not u) != everything, where not u = u => empty."""
    opens = down_sets(elements, leq)
    top = frozenset(elements)
    return [u for u in opens
            if u | heyting_implies(u, frozenset(), opens) != top]

OMEGA1 = [(a, b) for a in (False, True) for b in (False, True) if a <= b]

def subpresheaves(a1, a0, res):
    """Pairs (S1, S0) of subsets with res(S1) inside S0."""
    subs = []
    for r0 in range(a0 + 1):
        for s0 in itertools.combinations(range(a0), r0):
            for r1 in range(a1 + 1):
                for s1 in itertools.combinations(range(a1), r1):
                    if all(res[x] in s0 for x in s1):
                        subs.append((frozenset(s1), frozenset(s0)))
    return subs

def natural_maps(a1, a0, res):
    """Natural maps A -> Omega: (f1, f0) with f1(x)[1] == f0(res x)."""
    maps = []
    for f0 in itertools.product((False, True), repeat=a0):
        for f1 in itertools.product(OMEGA1, repeat=a1):
            if all(f1[x][1] == f0[res[x]] for x in range(a1)):
                maps.append((f1, f0))
    return maps

def chi(sub, a1, a0, res):
    """Characteristic map of a subpresheaf (S1, S0)."""
    s1, s0 = sub
    f1 = tuple((x in s1, res[x] in s0) for x in range(a1))
    return f1, tuple(y in s0 for y in range(a0))

def check_classifier(max1=4, max0=3):
    """Return (presheaves checked, failures) for the bijection Sub = Hom."""
    checked, failures = 0, []
    for a1 in range(max1 + 1):
        for a0 in range(max0 + 1):
            for res in itertools.product(range(a0), repeat=a1):
                subs = subpresheaves(a1, a0, res)
                maps = set(natural_maps(a1, a0, res))
                images = {chi(s, a1, a0, res) for s in subs}
                if len(images) != len(subs) or images != maps:
                    failures.append((a1, a0, res))
                checked += 1
    return checked, failures

POSETS = {
    "{0<1}": chain(2),
    "chain of 3": chain(3),
    "0 < 2 > 1": ([0, 1, 2], lambda a, b: a == b or b == 2),
    "discrete 3": ([0, 1, 2], lambda a, b: a == b),
}

def main():
    elems, leq = chain(5)
    sizes = [len(sieves_on(k, elems, leq)) for k in elems]
    print(f"Chain 0<1<2<3<4: |Omega(k)| = {sizes} (k + 2 each)")
    print()
    checked, failures = check_classifier()
    print("Presheaves A on {0<1} with |A1| <= 4, |A0| <= 3 (all restrictions):")
    print(f"  {checked} presheaves; chi : Sub(A) -> Hom(A, Omega) failed to be a")
    print(f"  bijection in {len(failures)} of them.")
    a1, a0, res = 3, 2, (0, 0, 1)
    n_sub = len(subpresheaves(a1, a0, res))
    print(f"  Example res = {res}: {n_sub} subpresheaves = (1 + 2^2)(1 + 2^1).")
    print()
    print("Global truth values = down-sets of P:")
    for name, (el, le) in POSETS.items():
        opens = down_sets(el, le)
        bad = excluded_middle_failures(el, le)
        status = "Boolean" if not bad else f"not Boolean ({len(bad)} fail LEM)"
        print(f"  {name:<12} {len(opens):>2} truth values, {status}")
    el, le = chain(2)
    opens = down_sets(el, le)
    half = frozenset({0})
    neg = heyting_implies(half, frozenset(), opens)
    negneg = heyting_implies(neg, frozenset(), opens)
    print("In {0<1}: u = {0} ('true only after restriction'):")
    print(f"  not u = {sorted(neg)}, not not u = {sorted(negneg)},"
          f" u or not u = {sorted(half | neg)}")
    print()
    print(textwrap.fill(
        "Epistemic status: exact computations and an exhaustive check of "
        f"{checked} small presheaves. They illustrate, but do not prove, the "
        "theorems that Omega classifies subobjects in every presheaf topos "
        "and that its truth values form a Heyting algebra; those are proved "
        "in the lesson, and the {0<1} case is also checked in Lean.",
        width=78,
    ))

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

Used in