Skip to content

gfq: minimum-distance checker for linear codes over GF(q), q in 3..9 - #3

Open
orda-aska wants to merge 1 commit into
mainfrom
agent/wave1-gfq-checker
Open

orda-aska wants to merge 1 commit into
mainfrom
agent/wave1-gfq-checker

Conversation

@orda-aska

Copy link
Copy Markdown
Contributor

What was done

  • gfq/ package (stdlib only, Python >=3.10): GF(q) arithmetic for q in {2,3,4,5,7,8,9} (GF(4)/GF(8)/GF(9) via explicit polynomial tables; q=6 rejected), generator-matrix parsing (space/comma/bracketed/compact-digit rows, optional q k n header), exact minimum distance by brute force and by a Brouwer-Zimmermann-style disjoint-information-set enumeration with an explicit budget, and classical constructions (Hamming, Reed-Solomon, ternary Golay) for validation.
  • CLI (python -m gfq --q Q [--method auto|brute|bz] [--json] FILE|-, console script gfq-check via pyproject.toml) printing [n,k,d], a minimum-weight codeword certificate (checked to lie in the row space with the claimed weight), and honest exit codes (0 exact, 2 budget exhausted with only bounds claimed, 1 input error).
  • Test suite (tests/, 24 tests, stdlib unittest) and examples/golay11.txt; README section with use, limits, and next steps for the smallest open codetables entries.

Exact reproduction commands

python3 -m unittest discover -s tests -v
python3 -m gfq --q 3 examples/golay11.txt
python3 -m gfq --q 3 --method bz --json examples/golay11.txt

Observed on this branch (Python 3.14.6): Ran 24 tests in 1.369s OK; CLI prints [11,6,5], a weight-5 certificate, certificate in row space with claimed weight: True; BZ stress: GF(3) Hamming r=4 -> [40,36,3] (1296 enumerations), GF(4) Hamming r=3 -> [21,18,3] where brute force (4^18) is infeasible.

Claim labels

  • KNOWN: expected parameters used in tests are classical theorems (Hamming d=3; Reed-Solomon MDS d=n-k+1; ternary Golay [11,6,5]/[12,6,6]).
  • VERIFIED (finite enumeration, in this run): field axioms for every supported q; all 24 tests passing, including brute-vs-BZ agreement on Golay, small Hamming/RS codes and small random codes; the Golay generator matrix's d=5 was additionally confirmed by enumerating all 3^6 codewords with a throwaway script before being committed as a fixture.
  • PROVEN: nothing is claimed as a human-style proof; exactness rests on the stated finite enumerations, whose lower-bound argument is documented in gfq/distance.py.
  • CONJECTURE: none.
  • No new code records are claimed.

What was NOT done

  • No search over codetables entries and no submissions to codetables.de; the README only describes the next steps.
  • No Galois fields beyond q<=9, no upper-bound (LP/duality) machinery, no optimised (bitset/GPU) BZ; large-k, large-d codes will exhaust the budget and return bounds (exit 2) by design.
  • GF(6) does not exist and is rejected rather than approximated.

@ahrazzle ahrazzle left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: gfq checker

Verdict: the code is correct. Fix three small issues and settle where it lives before merging.

What I checked

  • Tests and CLI. Ran the suite on this head (Python 3.13): Ran 24 tests … OK. python3 -m gfq --q 3 examples/golay11.txt prints [11,6,5] with a checked certificate.
  • Independent cross-check. 384 random full-rank codes over q ∈ {2,3,4,5,7,8,9} (k ≤ 5, n ≤ 14). I compared brute_force_min_distance and bz_min_distance against a separate naive enumerator over all q^k messages: 0 mismatches, and BZ returned complete=True every time.
  • BZ lower bound. Re-derived the argument in distance.py. With g disjoint information sets, a codeword of weight < g(s+1) has at most s nonzeros on one of them, so it is enumerated at message weight ≤ s in that systematic form. lb = min(ub, g·(s+1)) and the s > k exit are both sound. The budget exit reports the previous level's bounds, so it never claims more than it proved.

Inline (minor)

  1. GF(9) uses x²+1, not the Conway polynomial x²+2x+2 that Magma/GAP/Sage use. Integer-encoded matrices from those tools would be read as a different code without any warning.
  2. The q k n / k n header heuristic can drop or reject a real first row (n ≤ 3 only).
  3. A one-line compact matrix (20121) fails to parse, which contradicts the README.

Before merging: where should this live?

  • Foundation-0/gfq-codes exists, was pushed as recently as 25 Oct, and #6's README lists it as the home for exactly this question. #6 also says that once work has code and certificates, it gets its own repository. This PR adds a Python package (pyproject.toml at the math0 root) to the hub repo. Please confirm with the maintainers whether gfq/ belongs here or in gfq-codes. If gfq-codes already has it, close this PR and #4.
  • Merge conflicts with other open PRs. README.md conflicts with #1 and #6, and .gitignore is add/add with #1.
  • This branch has no CI (no check runs), so the 24 tests only run by hand.

Generated by Claude Code

Comment thread gfq/field.py
_IRREDUCIBLE = {
4: (2, [1, 1, 1]), # x^2 + x + 1 over GF(2)
8: (2, [1, 1, 0, 1]), # x^3 + x + 1 over GF(2)
9: (3, [1, 0, 1]), # x^2 + 1 over GF(3) (no root mod 3)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GF(9) encoding differs from Conway / Magma / GAP / Sage. x²+1 is irreducible over GF(3), so this is a valid GF(9). But x isn't primitive here (x⁴ = 1; primitive_element() returns 4, i.e. x+1). The Conway polynomial for GF(9) is x²+2x+2, and Magma, GAP and Sage use it by default. A matrix integer-encoded by one of those tools (e.g. Sage's integer_representation(), base-3 digits of the coefficients) has the same additive encoding but a different multiplication table. gfq would read it as a different code and report that code's d without any error.

GF(4) and GF(8) already match the Conway choices (x²+x+1, x³+x+1). Suggest switching GF(9) to x²+2x+2 now, while no GF(9) data is committed anywhere (#4 has none), and adding a test that pins the encoding. With x²+2x+2 you get x² = x+1, so GF(9).mul(3, 3) == 4; today it returns 2.


Generated by Claude Code

Comment thread gfq/matrix.py
Comment on lines +71 to +78
consistent = (
(len(hdr) == 3 and hdr[1] == len(lines) - 1 and hdr[2] == width2)
or (len(hdr) == 2 and hdr[0] == len(lines) - 1 and hdr[1] == width2))
if consistent:
if len(hdr) == 3 and hdr[0] != F.q:
raise ValueError(
f"header says q={hdr[0]} but --q is {F.q}")
continue

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The header heuristic can drop or reject a real first row. Repro on this head:

parse_matrix("1 2\n0 1\n", GF(3))              # -> [[0, 1]]   first row silently read as "k n"
parse_matrix("1 2 3\n0 1 0\n0 0 1\n", GF(4))   # -> ValueError: header says q=1 but --q is 4

The first gives a [2,1] code instead of [2,2] with no warning. The second rejects a valid full-rank matrix. Only n ≤ 3 can trigger this, so codetables-size inputs are safe. But silently misreading input is exactly what this tool is meant to rule out. Suggest never treating a line as a header when all its values are valid field elements, which fixes both repros and keeps the 3 2 3 test passing (3 ∉ GF(3)). Or require an explicit marker such as # q k n.


Generated by Claude Code

Comment thread gfq/matrix.py
rows: Matrix = []
for idx, line in enumerate(lines):
s = line.strip().strip("[]")
if re.fullmatch(r"[0-9]+", s) and len(s) > 1 and (rows or len(lines) > 1):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A one-line compact matrix doesn't parse. For one-line input, (rows or len(lines) > 1) is false, so "20121" falls through to the token branch and becomes the integer 20121:

parse_matrix("20121\n", GF(3))   # ValueError: 20121 is not an element of GF(3) (0..2)

The README says compact strings such as 20121 work, and the comment on lines 52-53 says a single compact line is kept as a row. This only affects k = 1 inputs. Dropping the (rows or len(lines) > 1) clause fixes it: the header branch only handles lines with 2 or 3 space-separated tokens, so it never sees a no-space line anyway.


Generated by Claude Code

@ahrazzle ahrazzle left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The matrix parser silently removes valid input. In GF(3), parse_matrix('[1,2]\n[0,1]\n', GF(3)) returns [[0, 1]], so the checker reports [2,1,1] for a rank-two generator. Brackets do not disambiguate a matrix row from the optional header. A single compact row, 102, also raises an out-of-field error despite the documented format. The 24 existing tests pass without covering these inputs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants