Skip to content
The Lumenary

Math laboratory

Monos and epis in finite sets, by brute force

Enumerates every function between sets of size 0 to 3, decides whether it is a monomorphism or an epimorphism straight from the cancellation definitions (testing against every map from or to a set of size at most 2), and checks that monos are exactly the injections and epis exactly the surjections. Exhaustive for these sizes only; the proof in the lesson covers all sets.

Back to the labExact computationExhaustive finite check
Monos and epis in finite sets, by brute forcePythonExact computationExhaustive finite checkComputationally tested
"""Monomorphisms and epimorphisms in finite sets, decided by brute force.

A function f : A -> B is a monomorphism if f . g = f . h forces g = h for
all maps g, h : T -> A, and an epimorphism if g . f = h . f forces g = h
for all maps g, h : B -> T. Both definitions quantify over *every* test
set T. This experiment enumerates every function between sets with at most
three elements, tests the definitions against all test sets with at most
two elements, and compares the answers with injectivity and surjectivity.
It also shows which test sets are needed: a one-element T already detects
monos, but epis need a two-element T. The evidence is exhaustive for these
small sets; the lesson's proof covers all sets.
"""

from itertools import product

def functions(m, n):
    """All functions {0..m-1} -> {0..n-1}, each as a tuple of values."""
    return list(product(range(n), repeat=m))

def compose(g, f):
    """The composite g . f of tuples: first f, then g."""
    return tuple(g[x] for x in f)

def is_mono(f, n, test_sizes):
    """Is f : m -> n left-cancellable against maps from sets of these sizes?"""
    m = len(f)
    for t in test_sizes:
        maps = functions(t, m)
        for g in maps:
            for h in maps:
                if g != h and compose(f, g) == compose(f, h):
                    return False
    return True

def is_epi(f, n, test_sizes):
    """Is f : m -> n right-cancellable against maps to sets of these sizes?"""
    for t in test_sizes:
        maps = functions(n, t)
        for g in maps:
            for h in maps:
                if g != h and compose(g, f) == compose(h, f):
                    return False
    return True

def is_injective(f):
    """No two inputs share an output."""
    return len(set(f)) == len(f)

def is_surjective(f, n):
    """Every element of {0..n-1} is a value."""
    return set(f) == set(range(n))

def tally(m, n, test_sizes=(0, 1, 2)):
    """Counts (functions, monos, injections, epis, surjections) for m -> n."""
    fs = functions(m, n)
    return (
        len(fs),
        sum(is_mono(f, n, test_sizes) for f in fs),
        sum(is_injective(f) for f in fs),
        sum(is_epi(f, n, test_sizes) for f in fs),
        sum(is_surjective(f, n) for f in fs),
    )

def main():
    print("|A| |B|  maps  monos  injective  epis  surjective")
    agree = True
    for m in range(4):
        for n in range(4):
            total, mono, inj, epi, surj = tally(m, n)
            agree = agree and mono == inj and epi == surj
            print(f"{m:>3} {n:>3} {total:>5} {mono:>6} {inj:>10}"
                  f" {epi:>5} {surj:>11}")
    print(f"monos = injections and epis = surjections: {agree}")
    print()
    print("Which test sets T are needed?")
    fake = sum(is_epi(f, 3, (1,)) for f in functions(2, 3))
    print(f"  with |T| = 1 only, {fake} of the 9 maps 2 -> 3 pass the epi test,")
    print("  although none of them is surjective")
    ok = all(is_mono(f, 3, (1,)) == is_injective(f) for f in functions(3, 3))
    print(f"  |T| = 1 already decides mono for all 27 maps 3 -> 3: {ok}")
    print()
    print("Epistemic status: exhaustive and exact for sets with at most three")
    print("elements, testing against all sets with at most two elements. It is")
    print("evidence, not proof, that the pattern continues; the lesson proves")
    print("mono = injective and epi = surjective for all sets, using a")
    print("one-element and a two-element test set exactly as found here.")

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

Used in