Skip to content
The Lumenary

Math laboratory

Ehrenfeucht-Fraisse games on finite linear orders

Solves the k-round Ehrenfeucht-Fraisse game on finite linear orders of sizes m and n by exhaustive game-tree search (k <= 3, sizes up to 10) and checks the rule 'Duplicator wins iff m = n or m, n >= 2^k - 1'. By the Ehrenfeucht-Fraisse theorem this certifies which pairs agree on all sentences of quantifier rank <= k, but only for the sizes searched.

Back to the labExact computationExhaustive finite check
Ehrenfeucht-Fraisse games on finite linear ordersPythonExact computationExhaustive finite checkComputationally tested
"""Ehrenfeucht-Fraisse games on finite linear orders, solved exhaustively.

Two players play k rounds on the linear orders {0 < 1 < ... < m-1} and
{0 < 1 < ... < n-1}. In each round Spoiler picks an element of either order and
Duplicator answers with an element of the other; Duplicator wins if the chosen
pairs always form a partial isomorphism (same order and equality pattern). By
the Ehrenfeucht-Fraisse theorem, Duplicator has a winning strategy for k rounds
exactly when the two orders satisfy the same sentences of quantifier rank <= k.
The program solves every game in a finite range by brute-force search and
compares the result with the classical rule: Duplicator wins iff m = n or
both m, n >= 2^k - 1. Evidence: exact and exhaustive over the range searched.
"""

from functools import lru_cache

def sign(x):
    """Return -1, 0 or 1 according to the sign of x."""
    return (x > 0) - (x < 0)

def consistent(pairs, new):
    """True if adding the pair ``new`` keeps ``pairs`` a partial isomorphism."""
    a, b = new
    return all(sign(a - c) == sign(b - d) for c, d in pairs)

def duplicator_wins(m, n, k):
    """Solve the k-round EF game on linear orders of sizes m and n exactly."""
    if min(m, n, k) < 0:
        raise ValueError("sizes and rounds must be non-negative")

    @lru_cache(maxsize=None)
    def win(rounds, pairs):
        if rounds == 0:
            return True
        for side, size, other in ((0, m, n), (1, n, m)):
            for x in range(size):
                answers = ((x, y) if side == 0 else (y, x) for y in range(other))
                if not any(consistent(pairs, p)
                           and win(rounds - 1, tuple(sorted(pairs + (p,))))
                           for p in answers):
                    return False
        return True

    return win(k, ())

def predicted(m, n, k):
    """The classical rule for linear orders: m = n, or both are >= 2^k - 1."""
    return m == n or (m >= 2 ** k - 1 and n >= 2 ** k - 1)

def check_range(k, sizes):
    """Compare the search with the rule on all pairs m <= n from ``sizes``."""
    pairs = [(m, n) for m in sizes for n in sizes if m <= n]
    wrong = [(m, n) for m, n in pairs
             if duplicator_wins(m, n, k) != predicted(m, n, k)]
    return len(pairs), wrong

def smallest_equivalent_pair(k, limit):
    """Least m with sizes m and m + 1 indistinguishable in k rounds."""
    for m in range(1, limit):
        if duplicator_wins(m, m + 1, k):
            return m
    return None

def main():
    print("k-round EF games on linear orders of sizes m <= n (D = Duplicator)")
    for k, sizes in [(1, range(1, 9)), (2, range(1, 9)), (3, range(1, 11))]:
        total, wrong = check_range(k, list(sizes))
        least = smallest_equivalent_pair(k, 10)
        print(f"k = {k}: {total} games solved, sizes {sizes.start}..{sizes.stop - 1}; "
              f"disagreements with the 2^k - 1 rule: {len(wrong)}")
        print(f"       least m such that m, m + 1 are k-equivalent: {least} "
              f"(rule predicts {2 ** k - 1})")
    print("Row k = 2 (sizes 1..6), entry for (m, n):")
    for m in range(1, 7):
        row = " ".join("D" if duplicator_wins(m, n, 2) else "." for n in range(1, 7))
        print(f"   m = {m}: {row}")
    print("So 'the order has an even number of elements' is not expressible by")
    print("one first-order sentence: a sentence of rank k cannot tell 2^k - 1")
    print("elements from 2^k (one even, one odd size).")
    print()
    print("Epistemic status: exact, exhaustive game-tree search for the listed")
    print("sizes and k <= 3. It confirms the 2^k - 1 rule there and nowhere else;")
    print("the rule for all m, n, k and the link between games and sentences")
    print("(the Ehrenfeucht-Fraisse theorem) are theorems, cited in the lesson.")

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

Used in