Skip to content
The Lumenary

Math laboratory

A relation checker: counterexamples and classes

Tests six relations on small finite sets - same remainder, differ by at most 1, divides, less than, same number of letters and share a letter - for reflexivity, symmetry and transitivity by checking every case. Prints a counterexample for each failure and the equivalence classes of each equivalence relation, checking that they form a partition. Valid for these finite sets only.

Back to the labExhaustive finite checkCounterexample
A relation checker: counterexamples and classesPythonExhaustive finite checkCounterexampleComputationally tested
"""A relation checker: properties, counterexamples and classes.

Give it a finite set and a yes/no rule for pairs. It tests reflexivity,
symmetry and transitivity by running through every case, prints a
counterexample for each property that fails, and, when the rule is an
equivalence relation, prints its classes - which always come out as a
partition. On a finite set this exhaustive check is a proof for that set;
it says nothing about larger sets.
"""

def reflexive_counterexample(elements, rel):
    """An a with not rel(a, a), or None if rel is reflexive."""
    for a in elements:
        if not rel(a, a):
            return (a,)
    return None

def symmetric_counterexample(elements, rel):
    """A pair (a, b) with rel(a, b) but not rel(b, a), or None."""
    for a in elements:
        for b in elements:
            if rel(a, b) and not rel(b, a):
                return (a, b)
    return None

def transitive_counterexample(elements, rel):
    """A triple (a, b, c) with rel(a, b), rel(b, c), not rel(a, c), or None."""
    for a in elements:
        for b in elements:
            if not rel(a, b):
                continue
            for c in elements:
                if rel(b, c) and not rel(a, c):
                    return (a, b, c)
    return None

def equivalence_classes(elements, rel):
    """The classes [a] = [x for x in elements if rel(x, a)], without repeats,
    in order of first appearance."""
    found = []
    for a in elements:
        cls = [x for x in elements if rel(x, a)]
        if cls not in found:
            found.append(cls)
    return found

def is_partition(elements, blocks):
    """Blocks are non-empty, pairwise disjoint and together cover elements."""
    members = [x for block in blocks for x in block]
    return (all(blocks) and len(members) == len(set(members))
            and set(members) == set(elements))

def same_letters_count(u, v):
    return len(u) == len(v)

def share_a_letter(u, v):
    return bool(set(u.lower()) & set(v.lower()))

COUNTRIES = ["Chad", "Peru", "Iran", "Fiji", "Ghana", "Nepal", "Kenya",
             "Malta", "Bhutan", "Mexico"]

RELATIONS = [
    ("same remainder mod 3", list(range(12)), lambda a, b: a % 3 == b % 3),
    ("differ by at most 1", list(range(6)), lambda a, b: abs(a - b) <= 1),
    ("a divides b", list(range(1, 7)), lambda a, b: b % a == 0),
    ("a is less than b", list(range(6)), lambda a, b: a < b),
    ("same number of letters", COUNTRIES, same_letters_count),
    ("share a letter", COUNTRIES, share_a_letter),
]

def report(name, elements, rel):
    """Lines of text describing one relation."""
    checks = [("reflexive", reflexive_counterexample(elements, rel)),
              ("symmetric", symmetric_counterexample(elements, rel)),
              ("transitive", transitive_counterexample(elements, rel))]
    verdict = ", ".join(("" if cx is None else "not ") + p for p, cx in checks)
    lines = [f"{name}: {verdict}"]
    for prop, cx in checks:
        if cx is not None:
            shown = ", ".join(str(x) for x in cx)
            lines.append(f"   fails {prop}: ({shown})")
    if all(cx is None for _, cx in checks):
        blocks = equivalence_classes(elements, rel)
        shown = " ".join("{" + ",".join(str(x) for x in b) + "}" for b in blocks)
        lines.append(f"   {len(blocks)} classes: {shown}")
        lines.append(f"   the classes form a partition: {is_partition(elements, blocks)}")
    return lines

def main():
    for name, elements, rel in RELATIONS:
        for line in report(name, elements, rel):
            print(line)
    print()
    print("Epistemic status: every case on these finite sets was checked, so")
    print("each verdict is proved for that set - and one counterexample")
    print("settles a failure for good. A rule that passes on a small set may")
    print("still fail on a larger one; for 'same remainder' on all of Z the")
    print("lesson gives a proof.")

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

Used in