Appendix A

Appendix A. Computations

Three Lemmas

All computations were performed with a short program in JavaScript (Node.js 22) on a laptop, and are reproducible in any language; the program is listed in §A.6. Running times are given where they matter. The purpose of the computations is not to confirm the theorems, which are proved, but to measure the distance between each polynomial bound and the truth in the smallest cases.

A.1 Sumsets in Zp\mathbb Z_p

For each prime p17p\le17 and each kk, the minimum of A+A|A+A| and of A+^A|A\mathbin{\hat+}A| over all kk-subsets AZpA\subseteq\mathbb Z_p was computed by enumerating all 2p2^p subsets. For p11p\le11 the minimum of A+^B|A\mathbin{\hat+}B| over all pairs of subsets with AB|A|\ne|B| was computed by enumerating all pairs. In every case the minimum equals the polynomial bound:

Table A.1. Minimum of (A+A, A+^A)\big(|A+A|,\ |A\mathbin{\hat+}A|\big) over kk-subsets of Zp\mathbb Z_p.

ppk=2k=2334455667788991010
5(3,1)(5,3)(5,5)(5,5)
7(3,1)(5,3)(7,5)(7,7)(7,7)(7,7)
11(3,1)(5,3)(7,5)(9,7)(11,9)(11,11)(11,11)(11,11)(11,11)
13(3,1)(5,3)(7,5)(9,7)(11,9)(13,11)(13,13)(13,13)(13,13)
17(3,1)(5,3)(7,5)(9,7)(11,9)(13,11)(15,13)(17,15)(17,17)

These agree with min(p,2k1)\min(p,2k-1) and min(p,2k3)\min(p,2k-3) for every entry (and for the omitted k>10k>10 at p=17p=17). For AB|A|\ne|B|, all 4242 size pairs at p=7p=7 and all 110110 at p=11p=11 have minimum min(p,k+l2)\min(p,k+l-2). Theorem B is therefore sharp for every kk, not only asymptotically.

A.2 Erdős–Ginzburg–Ziv

For each n9n\le9, every multiset of 2n12n-1 residues modulo nn (there are (3n2n1)\binom{3n-2}{n-1} of them) was tested, by dynamic programming over (number of elements used, sum modulo nn), for an nn-element sub-multiset with sum 00. All multisets pass:

nn23456789
multisets4211207154368271321705441081575
passing4211207154368271321705441081575

A.3 Cap sets

The maximum size of a cap set in F3n\mathbb F_3^n was computed by depth-first search over points in a fixed order, maintaining for each point the number of pairs already chosen whose third collinear point it is, and pruning when the current size plus the number of unforbidden remaining points cannot exceed the best found. No symmetry reduction was used.

Table A.3.

nnresultstatustime
12exhaustive<1<1 ms
24exhaustive<1<1 ms
39exhaustive48 ms
420search stopped at 240 s; a cap of size 20 was found240 s

For n=4n=4 the search found the known maximum 2020 but did not complete; completing it requires symmetry reduction (the affine group of F34\mathbb F_3^4 has order 81GL4(F3)81\cdot|GL_4(\mathbb F_3)|), which was not implemented. The value r(4)=20r(4)=20 is established in the literature (Bierbrauer and Edel 2002).

A.4 Ellenberg–Gijswijt numbers

The numbers md=#{α{0,1,2}n:αd}m_d=\#\{\boldsymbol\alpha\in\{0,1,2\}^n:|\boldsymbol\alpha|\le d\} were computed by dynamic programming. The constant of Theorem 6.9 was evaluated as t0=(331)/8=0.593070331t_0=(\sqrt{33}-1)/8=0.593070331, c=2.755105c=2.755105.

Table A.4. Cap-set bounds in F3n\mathbb F_3^n.

nn2n2^nm2n/3m_{\lfloor2n/3\rfloor}3m2n/33m_{\lfloor2n/3\rfloor} (Cor. 6.6)best dd in Thm 6.53cn3c^n3n3^nr(n)r(n)
121338.332
2439722.894
3810301862.7279
416154545172.98120
53251153123476.224345
6641685043241312.0729112
71282748228223614.82187
8256927278122779959.26561
951230619183607527438.719683
1010245193155791557975596.659049
112048174695240743365208276.5177147
12409657720173160116532573823.6531441

The column "best dd" is mind(m2nd1+md/2+md/21)\min_d\big(m_{2n-d-1}+m_{\lfloor d/2\rfloor}+m_{\lceil d/2\rceil-1}\big); the minimising dd is 0,2,3,5,6,7,9,10,11,13,14,150,2,3,5,6,7,9,10,11,13,14,15 for n=1,,12n=1,\dots,12. Note that 3cn3c^n exceeds 3n3^n for n12n\le12: the exponential bound only overtakes the trivial one at n13n\ge13, though 3m2n/33m_{\lfloor2n/3\rfloor} is below 3n3^n from n=4n=4.

A.5 Kakeya sets in the plane

For q{3,5,7}q\in\{3,5,7\} the minimum size of a union of q+1q+1 lines, one in each direction of Fq2\mathbb F_q^2, was computed by branch and bound over the qq+1q^{q+1} choices. Since every Kakeya set contains such a union, this is the minimum size of a Kakeya set. Results, with the bounds of Chapter 4 and the exact formula of Blokhuis and Mazzocca (2008):

qqminimum(q2+2q1)/2(q^2+2q-1)/2DvirDKSS
37763.24
51717157.72
731312814.21

A minimal set for q=5q=5 found by the search is written out in §4.5. For reference, the two lower bounds in higher dimension: for (q,n)=(5,3)(q,n)=(5,3) Dvir gives 3535 and DKSS 21.421.4; for (5,5)(5,5), 126126 and 165.4165.4; for (11,5)(11,5), 30033003 and 6350.86350.8; for (101,5)(101,5), 9.66×1079.66\times10^7 and 3.37×1083.37\times10^8 against qn1.05×1010q^n\approx1.05\times10^{10}.

A.6 Punctured Kakeya sets (Chapter 7)

For each q{3,4,5,7,8,9}q\in\{3,4,5,7,8,9\}, every choice of one line per direction in Fq2\mathbb F_q^2 was enumerated, the lines in the directions (1,0)(1,0) and (0,1)(0,1) being fixed by translation, so that qq1q^{q-1} configurations remain (43,046,72143{,}046{,}721 for q=9q=9, about one minute). Fields of order 44, 88, 99 were represented as F2[x]/(x2+x+1)\mathbb F_2[x]/(x^2+x+1), F2[x]/(x3+x+1)\mathbb F_2[x]/(x^3+x+1), F3[x]/(x2+1)\mathbb F_3[x]/(x^2+1). For each configuration the program records the multiplicity mPm_P of every point, the excess EE, the number ss of lines carrying a simple point, and the matching number ν\nu, computed exactly by dynamic programming over the 2q+12^{q+1} subsets of lines: ν(M)=max(ν(M{v}), 1+maxev, eMν(Me))\nu(M)=\max\big(\nu(M\setminus\{v\}),\ 1+\max_{e\ni v,\ e\subseteq M}\nu(M\setminus e)\big) where vv is the lowest line in MM and ee ranges over the points whose line set contains vv. The three tables of §7.6 are the output. The core of the program:

// one line per direction; lines[d][b] = point indices of the b-th line in direction d
function matchingNumber(edgeMasks) {          // edges = points, as bitmasks of the lines through them
  const full = (1 << D) - 1, byLow = Array.from({ length: D }, () => []);
  for (const e of edgeMasks) { let v = 0; while (!((e >> v) & 1)) v++; byLow[v].push(e); }
  const dp = new Int8Array(1 << D);
  for (let mask = 1; mask <= full; mask++) {
    let v = 0; while (!((mask >> v) & 1)) v++;
    let best = dp[mask & ~(1 << v)];
    for (const e of byLow[v]) if ((e & mask) === e) best = Math.max(best, 1 + dp[mask & ~e]);
    dp[mask] = best;
  }
  return dp[full];
}
function evaluate() {                          // called once per configuration
  let E = 0, edges = [], lineHasSimple = Array(D).fill(false);
  for (let P = 0; P < N; P++) {
    const m = cnt[P]; if (m === 0) continue;
    if (m >= 3) E += ((m - 1) * (m - 2)) / 2;
    if (m === 1) lineHasSimple[lowestBit(pmask[P])] = true;
    edges.push(pmask[P]);
  }
  const s = lineHasSimple.filter(Boolean).length, nu = matchingNumber(edges);
  record(size, E, s, nu);                       // |U| = size; |K'| = size - nu
}
function rec(d) {                              // depth-first over directions, maintaining cnt[] and pmask[]
  if (d === D) { evaluate(); return; }
  for (const b of (d === 0 || d === D - 1) ? [0] : [...Array(q).keys()]) {
    for (const P of lines[d][b]) { if (cnt[P]++ === 0) size++; pmask[P] |= 1 << d; }
    rec(d + 1);
    for (const P of lines[d][b]) { if (--cnt[P] === 0) size--; pmask[P] &= ~(1 << d); }
  }
}

A.7 Program for Appendices A.1 to A.5

// compute.js — computational checks (Node.js). Prints the tables of Appendix A.
const fs = require("fs");

// A.1 Sumsets in Z_p
function sumsetStats(p) {
  const N = 1 << p, minPlain = Array(p + 1).fill(Infinity), minRestr = Array(p + 1).fill(Infinity);
  const pc = (x) => { let c = 0; while (x) { c += x & 1; x >>= 1; } return c; };
  for (let m = 1; m < N; m++) {
    const A = []; for (let i = 0; i < p; i++) if (m >> i & 1) A.push(i);
    let plain = 0, restr = 0;
    for (const a of A) for (const b of A) { plain |= 1 << ((a + b) % p); if (a !== b) restr |= 1 << ((a + b) % p); }
    minPlain[A.length] = Math.min(minPlain[A.length], pc(plain));
    minRestr[A.length] = Math.min(minRestr[A.length], pc(restr));
  }
  return { minPlain, minRestr };
}

// A.2 Erdős–Ginzburg–Ziv: all multisets of 2n-1 residues mod n
function egz(n) {
  let total = 0, good = 0; const c = Array(n).fill(0);
  function check() {
    total++;
    let dp = Array.from({ length: n + 1 }, () => Array(n).fill(false)); dp[0][0] = true;
    for (let r = 0; r < n; r++) for (let t = 0; t < c[r]; t++)
      for (let j = n - 1; j >= 0; j--) for (let s = 0; s < n; s++) if (dp[j][s]) dp[j + 1][(s + r) % n] = true;
    if (dp[n][0]) good++;
  }
  (function rec(i, left) {
    if (i === n - 1) { c[i] = left; check(); return; }
    for (let v = 0; v <= left; v++) { c[i] = v; rec(i + 1, left - v); }
  })(0, 2 * n - 1);
  return { total, good };
}

// A.3 Cap sets in F_3^n: branch and bound
function capMax(n, timeLimitMs) {
  const N = 3 ** n, pts = [];
  for (let i = 0; i < N; i++) { const v = []; let x = i; for (let j = 0; j < n; j++) { v.push(x % 3); x = Math.floor(x / 3); } pts.push(v); }
  const idx = (v) => v.reduce((a, d, j) => a + d * 3 ** j, 0);
  const third = (i, j) => idx(pts[i].map((d, t) => (6 - d - pts[j][t]) % 3));
  let bestSize = 0, timedOut = false; const t0 = Date.now(), forb = new Int32Array(N), cur = [];
  function dfs(start) {
    if (Date.now() - t0 > timeLimitMs) { timedOut = true; return; }
    let cand = 0; for (let i = start; i < N; i++) if (forb[i] === 0) cand++;
    if (cur.length + cand <= bestSize) return;
    for (let i = start; i < N; i++) {
      if (forb[i] !== 0) continue;
      const added = []; for (const c of cur) { const t = third(i, c); forb[t]++; added.push(t); }
      cur.push(i); if (cur.length > bestSize) bestSize = cur.length;
      dfs(i + 1); cur.pop(); for (const t of added) forb[t]--;
      if (timedOut) return;
    }
  }
  dfs(0); return { bestSize, timedOut, ms: Date.now() - t0 };
}

// A.4 m_d and the constant c
function m3(n, d) {
  if (d < 0) return 0;
  let dp = Array(2 * n + 1).fill(0); dp[0] = 1;
  for (let i = 0; i < n; i++) { const nd = Array(2 * n + 1).fill(0);
    for (let s = 0; s <= 2 * n; s++) if (dp[s]) for (let a = 0; a <= 2; a++) if (s + a <= 2 * n) nd[s + a] += dp[s]; dp = nd; }
  let tot = 0; for (let s = 0; s <= Math.min(d, 2 * n); s++) tot += dp[s]; return tot;
}
const t0 = (Math.sqrt(33) - 1) / 8, c0 = (1 + t0 + t0 * t0) * Math.pow(t0, -2 / 3);

// A.5 Kakeya sets in F_q^2: one line per direction
function kakeya2(q) {
  const lines = [];
  for (let s = 0; s < q; s++) lines.push([...Array(q)].map((_, b) => [...Array(q)].map((_, t) => t * q + ((b + s * t) % q))));
  lines.push([...Array(q)].map((_, b) => [...Array(q)].map((_, t) => b * q + t)));
  let best = Infinity; const cnt = new Int32Array(q * q); let size = 0;
  (function rec(d) {
    if (d === lines.length) { best = Math.min(best, size); return; }
    if (size >= best) return;
    for (const pts of lines[d]) { for (const p of pts) if (cnt[p]++ === 0) size++; rec(d + 1); for (const p of pts) if (--cnt[p] === 0) size--; }
  })(0);
  return best;
}

for (const p of [5, 7, 11, 13, 17]) console.log(p, sumsetStats(p));
for (let n = 2; n <= 9; n++) console.log(n, egz(n));
for (let n = 1; n <= 4; n++) console.log(n, capMax(n, n === 4 ? 240000 : 60000));
for (let n = 1; n <= 12; n++) console.log(n, m3(n, Math.floor(2 * n / 3)), 3 * c0 ** n);
for (const q of [3, 5, 7]) console.log(q, kakeya2(q), (q * q + 2 * q - 1) / 2);