Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
__pycache__/
*.pyc
68 changes: 68 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,70 @@
# math0
Math explorations.

## gfq: minimum-distance checker for linear codes over GF(q)

`gfq/` is an open checker for the first step in [AREAS.md](AREAS.md) §1:
given a generator matrix over GF(q), compute the exact minimum distance
and print a minimum-weight codeword as a certificate that anyone can
re-check. It makes no claims about new code records.

**Fields.** q ∈ {2, 3, 4, 5, 7, 8, 9}. Prime fields are residues mod p.
GF(4), GF(8) and GF(9) use explicit polynomial tables
(GF(2)[x]/(x²+x+1), GF(2)[x]/(x³+x+1), GF(3)[x]/(x²+1)); elements are the
integers 0..q−1 encoding polynomial coefficients in base p. There is no
field GF(6), and q=6 is rejected. Zero dependencies (Python ≥ 3.10).

**Use.**

```
python3 -m gfq --q 3 examples/golay11.txt # prints [11,6,5] + certificate
python3 -m gfq --q 4 --method bz --json matrix.txt # machine-readable
cat matrix.txt | python3 -m gfq --q 3 - # stdin
pip install . # optional: provides `gfq-check`
```

Matrix files: one row per line, entries separated by spaces or commas
(brackets as pasted from Magma/Sage also work), compact digit strings
such as `20121` work for q ≤ 9, `#` comments are ignored, and an optional
leading `q k n` header is accepted. Exit codes: 0 exact, 2 budget
exhausted (only bounds claimed), 1 input error. The printed certificate
is checked to lie in the row space with the claimed weight before it is
reported.

**Methods and limits.** `--method brute` enumerates all
(q^k−1)/(q−1) normalised codewords: exact, feasible only for small q^k
(`auto` uses it up to 400,000 by default). `--method bz` is a
Brouwer–Zimmermann-style enumeration: in g disjoint information sets it
enumerates message weights s = 1, 2, …, which proves
d ≥ min(upper bound, g·(s+1)) at each level, and stops when the bounds
meet (exact) or the `--max-enum` budget runs out (then it reports an
honest interval and exit code 2, never a guessed d). Cost per level is
about g·C(k,s)·(q−1)^(s−1) codewords, so it is strong when d is small
relative to k or several disjoint information sets exist, and weak for
large-k codes with large d — those need the budget raised or a stronger
implementation (bitsets/GPU, better information sets). Finite
enumerations here are labelled VERIFIED, not "proved"; expected values
in the tests are KNOWN theorems.

**Tests.** `python3 -m unittest discover -s tests` validates the field
axioms exhaustively for every supported q, and the distance against
known codes: ternary Golay [11,6,5] and [12,6,6], Hamming codes over
GF(3)/GF(4)/GF(8)/GF(9) (including [21,18,3] and [40,36,3] cases where
brute force is infeasible), Reed–Solomon examples over
GF(5)/GF(7)/GF(8)/GF(9), and brute-vs-BZ agreement on small random codes.

**Attacking the smallest open codetables entries next.** The census in
[data/codetables/](data/codetables/) counts 80,652 open non-binary
entries. A sane order: (1) re-run `python3 data/codetables/census.py
--csv entries.csv` and sort open entries by gap, then by n — single-gap,
small-n entries first, remembering AREAS.md's caution that large GF(7)
gaps mostly reflect weak upper bounds; (2) for a target [n,k] over GF(q),
fetch the record construction's generator matrix from codetables.de and
re-verify its d with this checker before searching (a construction that
cannot be re-checked is not a baseline); (3) search where Grassl's
quasi-cyclic index-2 sweep has not: quasi-twisted codes of index 3–4,
constacyclic shifts, and one-column extensions of record codes, using
this checker (BZ mode, budget set explicitly) to certify every candidate
before believing it; (4) only then consider submitting a verified
improvement, generator matrix included, to codes@codetables.de. No
search or submission has been done in this repository yet.
7 changes: 7 additions & 0 deletions examples/golay11.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Ternary Golay code [11,6,5] over GF(3): cyclic shifts of g(x)=x^5+x^4+2x^3+x^2+2
2 0 1 2 1 1 0 0 0 0 0
0 2 0 1 2 1 1 0 0 0 0
0 0 2 0 1 2 1 1 0 0 0
0 0 0 2 0 1 2 1 1 0 0
0 0 0 0 2 0 1 2 1 1 0
0 0 0 0 0 2 0 1 2 1 1
9 changes: 9 additions & 0 deletions gfq/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""Open minimum-distance checker for linear codes over GF(q), q in 3..9
(plus GF(2)). See gfq.distance for methods, gfq.cli for the CLI."""
from .field import GF, SUPPORTED_Q
from .distance import MinDistanceResult, min_distance, brute_force_min_distance, bz_min_distance
from .matrix import parse_matrix

__all__ = ["GF", "SUPPORTED_Q", "MinDistanceResult", "min_distance",
"brute_force_min_distance", "bz_min_distance", "parse_matrix"]
__version__ = "0.1.0"
4 changes: 4 additions & 0 deletions gfq/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .cli import main

if __name__ == "__main__":
raise SystemExit(main())
85 changes: 85 additions & 0 deletions gfq/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
"""Command-line minimum-distance checker.

Usage:
python -m gfq --q 3 examples/golay11.txt
python -m gfq --q 4 --method bz --json matrix.txt
cat matrix.txt | python -m gfq --q 3 -

Exit codes: 0 = exact distance (with certificate codeword printed);
2 = enumeration budget exhausted, only bounds are claimed;
1 = input/usage error.
"""
from __future__ import annotations

import argparse
import json
import sys

from .field import GF
from .matrix import parse_matrix, row_space_contains, weight
from .distance import min_distance


def main(argv=None) -> int:
ap = argparse.ArgumentParser(
prog="gfq-check",
description="Exact minimum distance of a linear code over GF(q) "
"from a generator matrix.")
ap.add_argument("matrix", help="generator-matrix file, or - for stdin")
ap.add_argument("--q", type=int, required=True,
help="field order: one of 2,3,4,5,7,8,9")
ap.add_argument("--method", choices=["auto", "brute", "bz"], default="auto")
ap.add_argument("--max-enum", type=int, default=2_000_000,
help="Brouwer-Zimmermann enumeration budget (default 2000000)")
ap.add_argument("--brute-limit", type=int, default=400_000,
help="auto: use brute force at or below this many codewords")
ap.add_argument("--json", action="store_true", help="machine-readable output")
args = ap.parse_args(argv)

try:
F = GF(args.q)
text = sys.stdin.read() if args.matrix == "-" else open(args.matrix).read()
G = parse_matrix(text, F)
res = min_distance(G, F, method=args.method,
brute_limit=args.brute_limit, max_enum=args.max_enum)
except (ValueError, OSError) as e:
print(f"error: {e}", file=sys.stderr)
return 1

# Certificate sanity: the printed codeword really is in the row space
# and really has the claimed weight. This is a cheap independent check
# of the *upper* bound (the lower bound rests on the enumeration).
cert_ok = (row_space_contains(G, F, res.codeword)
and weight(res.codeword) == res.upper_bound)

if args.json:
print(json.dumps({
"q": args.q, "n": res.n, "k": res.k,
"d": res.d, "lower_bound": res.lower_bound,
"upper_bound": res.upper_bound, "complete": res.complete,
"codeword_certificate": res.codeword,
"certificate_checked": cert_ok,
"method": res.method, "enumerated": res.enumerated,
"num_info_sets": res.num_info_sets,
}, indent=2))
else:
print(res.params())
print(f"field: GF({args.q}) method: {res.method}"
+ (f" disjoint info sets: {res.num_info_sets}" if res.method == "bz" else "")
+ f" combinations enumerated: {res.enumerated}")
label = ("minimum-weight codeword" if res.complete
else "lightest codeword found (upper-bound certificate only)")
print(f"{label} (certificate, weight {res.upper_bound}): "
+ " ".join(str(a) for a in res.codeword))
print(f"certificate in row space with claimed weight: {cert_ok}")
if not res.complete:
print("status: INCOMPLETE - enumeration budget exhausted; "
"only the stated bounds are claimed, not an exact d.")
if not cert_ok: # pragma: no cover - would indicate an internal bug
print("error: internal certificate check failed", file=sys.stderr)
return 1
return 0 if res.complete else 2


if __name__ == "__main__": # pragma: no cover
raise SystemExit(main())
83 changes: 83 additions & 0 deletions gfq/codes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""Classical code constructions with KNOWN parameters.

These exist so the checker can be validated against parameters that are
theorems in the literature (Hamming: d=3; Reed-Solomon: MDS, d=n-k+1;
ternary Golay: [11,6,5] / [12,6,6]), not merely against itself. The test
suite additionally cross-checks the Brouwer-Zimmermann method against
brute force on the smaller examples, so no expected value rests on a
single code path.
"""
from __future__ import annotations

from itertools import product

from .field import GF
from .matrix import Matrix, nullspace_basis


def hamming_generator(F: GF, r: int) -> Matrix:
"""Generator matrix of the q-ary Hamming code with redundancy r:
[ (q^r-1)/(q-1), (q^r-1)/(q-1) - r, 3 ] (KNOWN: d=3 for r >= 2)."""
if r < 2:
raise ValueError("Hamming codes need r >= 2")
cols = []
for vec in product(F.elements(), repeat=r):
if all(a == 0 for a in vec):
continue
first = next(a for a in vec if a != 0)
if first == 1: # one representative per 1-dim subspace
cols.append(vec)
H = [[cols[j][i] for j in range(len(cols))] for i in range(r)]
return nullspace_basis(H, F)


def reed_solomon_generator(F: GF, n: int, k: int, points=None) -> Matrix:
"""[n,k,n-k+1] Reed-Solomon code (KNOWN: MDS) by evaluating all
polynomials of degree < k at n distinct points of GF(q)."""
if not 1 <= k <= n <= F.q:
raise ValueError("need 1 <= k <= n <= q for this RS construction")
pts = list(points) if points is not None else F.elements()[:n]
if len(set(pts)) != n:
raise ValueError("evaluation points must be distinct")
return [[F.pow(x, i) for x in pts] for i in range(k)]


def repetition_generator(F: GF, n: int) -> Matrix:
"""[n,1,n] repetition code."""
return [[1] * n]


def single_parity_generator(F: GF, n: int) -> Matrix:
"""[n,n-1,2] single-parity-check code (n >= 2)."""
if n < 2:
raise ValueError("need n >= 2")
rows = []
for i in range(n - 1):
row = [0] * n
row[i] = 1
row[n - 1] = F.neg(1)
rows.append(row)
return rows


# Ternary Golay code: cyclic [11,6,5] code over GF(3) generated by
# g(x) = x^5 + x^4 + 2x^3 + x^2 + 2, a degree-5 factor of x^11 - 1 over
# GF(3). (Its parameters are classical; in this repo the value d=5 was
# also confirmed by exhaustively enumerating all 3^6 codewords of this
# exact matrix with an independent throwaway script, and the committed
# tests re-confirm it by brute force.) Rows are the cyclic shifts
# x^i g(x), i = 0..5, coefficients low -> high.
_GOLAY_G = [2, 0, 1, 2, 1, 1]


def ternary_golay_generator(extended: bool = False) -> Matrix:
"""[11,6,5] ternary Golay generator; with ``extended=True`` an overall
parity coordinate is appended giving the [12,6,6] extended code."""
rows = []
for i in range(6):
row = [0] * i + list(_GOLAY_G) + [0] * (11 - i - len(_GOLAY_G))
if extended:
F = GF(3)
row.append(F.neg(sum(row) % 3))
rows.append(row)
return rows
Loading