Skip to content
The Lumenary

Math laboratory

Roth numbers, prime progressions and the sum-product phenomenon

Computes the Roth numbers r3(N) for N <= 40 exactly by exhaustive backtracking, compares them with base-3 digit sets, finds the k-term progressions of primes with the smallest last term for k <= 7, and measures |A + A| and |A . A| for arithmetic and geometric progressions. Exact finite computations: they illustrate, but cannot test, asymptotic theorems such as Roth's and Green-Tao's.

Back to the labExact computationExhaustive finite check
Roth numbers, prime progressions and the sum-product phenomenonPythonExact computationExhaustive finite checkComputationally tested
"""Progressions in dense sets and in the primes; sums versus products.

1. Roth numbers: r3(N), the size of the largest subset of {0, ..., N-1} with no
   three-term arithmetic progression, computed exactly by backtracking for
   N <= 40. Roth's theorem says r3(N)/N -> 0, but the decay is extremely slow.
   We compare with the "base-3 digits 0 and 1" sets, which are 3AP-free.
2. Green-Tao: the primes contain arbitrarily long (finite) progressions. We
   find, for k = 3, ..., 7, the k-term progression of primes with the smallest
   last term.
3. Sum-product: for A = {1, ..., n}, |A + A| is small and |A . A| is large;
   for a geometric progression it is the other way round. Erdos and
   Szemeredi conjectured that one of the two is always nearly |A|^2.
"""

import math

def roth_number(N, smaller=()):
    """r3(N): the largest size of a subset of {0, ..., N-1} with no 3-term AP.

    `smaller` may list the known values r3(0), r3(1), ..., r3(N - 1). They speed
    up the exhaustive search: the elements still to be chosen lie in an interval
    of length L and form a 3AP-free set, so there are at most r3(L) of them."""
    table = list(smaller)

    def room(length):
        if length < len(table):
            return table[length]
        return length if not table else table[-1] + length - len(table) + 1

    best = table[-1] if table else 0  # a set for N - 1 also works for N
    chosen, member = [], [False] * N

    def ends_progression(x):
        # x is larger than every chosen element: it ends an AP a, b, x iff
        # a = 2b - x was chosen for some chosen b
        for b in chosen:
            a = 2 * b - x
            if a >= 0 and member[a]:
                return True
        return False

    def extend(x, size):
        nonlocal best
        if size + room(N - x) <= best:
            return  # cannot beat the best set known so far
        if x == N:
            best = size
            return
        if not ends_progression(x):
            chosen.append(x)
            member[x] = True
            extend(x + 1, size + 1)
            chosen.pop()
            member[x] = False
        extend(x + 1, size)

    extend(0, 0)
    return best

def roth_numbers(n_max):
    """[r3(1), ..., r3(n_max)], each search using all the smaller values."""
    table = [0]
    for N in range(1, n_max + 1):
        table.append(roth_number(N, table))
    return table[1:]

def has_three_ap(A):
    """True if the set A contains a, b, c with a < b < c and a + c = 2b."""
    s = sorted(set(A))
    members = set(s)
    return any((a + c) % 2 == 0 and (a + c) // 2 in members
               for i, a in enumerate(s) for c in s[i + 2:])

def base3_set(N):
    """Numbers below N whose base-3 digits are all 0 or 1 (a 3AP-free set)."""
    def ok(n):
        while n:
            if n % 3 == 2:
                return False
            n //= 3
        return True
    return [n for n in range(N) if ok(n)]

def prime_sieve(n):
    """sieve[i] is 1 exactly when i < n is prime."""
    sieve = bytearray([1]) * max(n, 2)
    sieve[0] = sieve[1] = 0
    for i in range(2, int(n ** 0.5) + 1):
        if sieve[i]:
            sieve[i * i::i] = bytearray(len(range(i * i, n, i)))
    return sieve

def smallest_prime_progression(k, limit=2000):
    """The k-term AP of primes with the smallest last term below limit, or None."""
    sieve = prime_sieve(limit)
    for last in range(2, limit):
        if not sieve[last]:
            continue
        for d in range(1, (last - 2) // (k - 1) + 1):
            if all(sieve[last - i * d] for i in range(1, k)):
                return [last - i * d for i in range(k - 1, -1, -1)]
    return None

def sum_and_product_sizes(A):
    """(|A + A|, |A . A|) for a finite set of integers A."""
    return (len({a + b for a in A for b in A}),
            len({a * b for a in A for b in A}))

def main():
    values = roth_numbers(40)
    print("1. Roth numbers r3(N), N = 1..40 (exact, exhaustive):")
    print("   " + " ".join(str(v) for v in values[:20]))
    print("   " + " ".join(str(v) for v in values[20:]))
    for N in (10, 20, 40):
        b = base3_set(N)
        print(f"   N = {N}: r3/N = {values[N - 1] / N:.2f}; base-3 set has "
              f"{len(b)} elements, 3AP-free: {not has_three_ap(b)}")
    print("   base-3 sets have about N^0.63 elements; Behrend (1946) does")
    print("   better: N exp(-c sqrt(log N)), still o(N) as Roth requires.")
    print()
    print("2. Progressions of k primes with the smallest last term:")
    for k in range(3, 8):
        ap = smallest_prime_progression(k)
        print(f"   k = {k}: {', '.join(map(str, ap))} (difference {ap[1] - ap[0]})")
    print()
    print("3. Sums versus products, |A| = 20:")
    n = 20
    for name, A in [("{1, ..., 20}", list(range(1, n + 1))),
                    ("{1, 2, 4, ..., 2^19}", [2 ** i for i in range(n)])]:
        s, p = sum_and_product_sizes(A)
        print(f"   A = {name:20s}  |A + A| = {s:3d}   |A . A| = {p:3d}")
    print(f"   (|A|^2 = {n * n}; max(|A+A|, |A.A|) is large in both cases)")
    print()
    print("Epistemic status: exact, exhaustive computations for the sizes shown.")
    print("Roth's theorem (r3(N)/N -> 0) and Green-Tao are theorems about")
    print("N -> infinity; finite tables cannot confirm or refute them, and the")
    print("sum-product conjecture remains open despite such examples.")

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

Used in