diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 49de07ab..87e239c2 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -69,7 +69,7 @@ body: attributes: label: ogdoad version description: "`cargo pkgid` (Rust) or `pip show ogdoad` (Python), or the commit SHA." - placeholder: 1.0.2 + placeholder: version or commit SHA validations: required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 8059b934..46c2d7e6 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -2,7 +2,7 @@ blank_issues_enabled: false contact_links: - name: Open research problems url: https://github.com/a9lim/ogdoad/blob/main/docs/OPEN.md - about: Arbitrary-graph FIFO linking and the universal transfinite nim-excess rule are known open problems, not bugs. See docs/OPEN.md before filing. + about: FIFO linking, transfinite nim-excess, and natural finite-misere realization are research problems, not bugs. See docs/OPEN.md before filing. - name: Security vulnerability url: https://github.com/a9lim/ogdoad/security/policy about: Please report security issues privately (see SECURITY.md). diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index afda076b..a1f9ab0d 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -12,9 +12,11 @@ body: Clifford algebra needs a commutative scalar *ring*, and the full game group is only an abelian group. New scalar worlds must be commutative rings. - - The genuine open problems (arbitrary-graph FIFO linking and the - universal transfinite nim-excess rule) live in `docs/OPEN.md`. Those are research - questions, not feature requests — open a discussion instead. + - The open mathematical fronts (arbitrary-graph isolated-dummy FIFO + linking, the universal transfinite nim-excess `0/1/4` rule, and + natural-ruleset realization of finite misere quotients) live in + `docs/OPEN.md`. Those are research questions, not feature requests — + open a discussion instead. New backends, invariants, cross-pillar bridges, and binding/docs ergonomics are all in scope and welcome. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index b78900a1..5a65645d 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -8,22 +8,21 @@ ## Test plan -- [ ] `cargo test` passes -- [ ] `cargo clippy --all-targets` is warning-clean -- [ ] `cargo fmt --check` is clean -- [ ] If this touched `src/py/` or any core API the bindings call: `cargo check --features python` **and** `cargo clippy --features python --all-targets` -- [ ] If this touched `clifford/` or `scalar/big/surreal/`: rebuilt (`maturin develop`) and ran `demo.py` — Display changes don't surface in `cargo test` -- [ ] If this touched any doc comment (`//!` / `///`): ran `cargo doc --no-deps` **cold** (`RUSTDOCFLAGS="-D warnings"`) and it's link-clean -- [ ] If this added a new operation: there's a test pinning it to an independent oracle (the `associativity_*` / `general_product_reproduces_*` style) +- [ ] `cargo fmt --all --check` +- [ ] `cargo test --workspace` +- [ ] `cargo clippy --workspace --all-targets -- -D warnings` +- [ ] `RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --workspace` +- [ ] If this touched `src/py/` or a core API used by Python: `cargo check -p ogdoad --features python`, the matching Clippy gate, and `python scripts/generate_stubs.py --check` +- [ ] If this touched Lean or a load-bearing proof claim: `(cd formal && lake build --wfail)` +- [ ] If this touched a paper or bibliography: `npm ci` and `python scripts/check_writeups.py` +- [ ] If this added a generic operation: a focused test pins it to an independent oracle ## Claim level + standard/cited · implemented and tested · proved here · open. --> ## Notes - + diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8c7a1a61..3dc9b49a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,12 +14,9 @@ permissions: contents: read jobs: - # The gate. Reads the version from Cargo.toml (the source of truth — pyproject - # and the maturin build inherit it) and decides whether there is anything to - # ship. The pipeline is DORMANT while the version is the 0.0.0 placeholder; - # bumping the version is what arms it. Each publish target is also probed - # independently so a partial-failure run resumes cleanly: a step is skipped - # only when its own artifact already exists, never gated on a sibling. + # The gate. Cargo.toml is the version source of truth; pyproject and maturin + # inherit it. Each publish target is probed independently so a partial-failure + # run can resume without republishing a completed sibling artifact. guard: runs-on: ubuntu-latest outputs: @@ -43,12 +40,8 @@ jobs: VERSION: ${{ steps.v.outputs.version }} GH_TOKEN: ${{ github.token }} run: | - # Dormant until the version is bumped off the 0.0.0 placeholder; after - # that, the git tag is the completion marker — an existing tag means a - # version was fully shipped and there is nothing to do. - if [ "$VERSION" = "0.0.0" ]; then - echo "should_release=false" >> "$GITHUB_OUTPUT" - elif git rev-parse -q --verify "refs/tags/v$VERSION" >/dev/null; then + # The git tag is the completion marker for the current version. + if git rev-parse -q --verify "refs/tags/v$VERSION" >/dev/null; then echo "should_release=false" >> "$GITHUB_OUTPUT" else echo "should_release=true" >> "$GITHUB_OUTPUT" diff --git a/AGENTS.md b/AGENTS.md index ea2481ae..dbbbbcb7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -41,8 +41,9 @@ crate-private. Their public modules re-export children shallowly. build. - `grundy/` is an unpublished workspace crate depending only on Ogdoad's public API. -- `formal/` is a separately pinned Lean project. It proves named ingredients; - paper-level syntheses must state what Lean does and does not check. +- `formal/` is a separately pinned Lean project. It proves named components + and selected end-to-end finite constructions; paper-level syntheses must + state what Lean does and does not check. ## Non-negotiable mathematical invariants @@ -85,10 +86,6 @@ The current open fronts are exactly: - arbitrary-graph isolated-dummy FIFO linking; - the universal `0/1/4` transfinite nim-excess rule, including its selected nim-reciprocity program; -- quasi-linear multiplication in canonical finite-nimber coordinates; -- game-valued Witt realization over `F_2(t)`; -- stable quadratic-form invariants over the supertropical and Hessenberg - semirings; - natural-ruleset realization of finite misère quotients, including the exact quotient of misère Grundy's game. @@ -97,6 +94,9 @@ matching-plus-isolates theorem and does not depend on general FIFO linking. The full `On_2` classification is a mathematical theorem after scalar extension; it does not make the finite `Nimber(u128)` backend algebraically closed or make the partial `Ordinal` backend construct every root. +Quasi-linear multiplication in canonical finite-nimber coordinates has an +explicit proved transform; its arbitrary-width implementation and crossover +measurement are engineering work in `docs/ROADMAP.md`. ## Code conventions diff --git a/Cargo.lock b/Cargo.lock index aa1830db..fdc5f2ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -193,7 +193,7 @@ dependencies = [ [[package]] name = "ogdoad" -version = "1.0.5" +version = "1.0.6" dependencies = [ "proptest", "pyo3", diff --git a/Cargo.toml b/Cargo.toml index 6ee20acd..2fe2842c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = ["grundy"] [package] name = "ogdoad" -version = "1.0.5" +version = "1.0.6" edition = "2021" description = "Clifford algebras and quadratic forms over exact, finite, local, transfinite, and game-adjacent scalar backends." license = "AGPL-3.0-or-later" diff --git a/README.md b/README.md index f058dbb3..52302106 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Representation limits are part of the API: | --- | --- | | `src/scalar/` | coefficient traits and exact, finite, valued, global, surreal, and ordinal backends | | `src/clifford/` | metrics, blades, multivectors, products, versors, spinors, and geometric-algebra constructions | -| `src/forms/` | quadratic-form classification, Witt/Brauer theory, Springer and local--global arithmetic | +| `src/forms/` | quadratic-form classification, Clifford centers, Witt/Brauer and low Milnor-symbol theory, Springer and local--global arithmetic | | `src/forms/integral/` | lattices, discriminant forms, codes, theta series, genera, neighbors, and Weyl bridges | | `src/games/` | impartial, partizan, misere, loopy, thermographic, Witt--FIFO/Brown, octal-certificate, Hackenbush, and game-exterior constructions | | `src/py/` | optional per-backend PyO3 bindings; scalar worlds never mix at runtime | @@ -91,6 +91,12 @@ assert e0 * e1 + e1 * e0 == A.scalar(og.Nimber(1)) S = og.SurrealAlgebra(q=[og.omega(), og.epsilon()]) assert (S.gen(0) * S.gen(1)) ** 2 == S.scalar(og.Surreal.from_int(-1)) +# A Hermitian form restricts to the ordinary quadratic form q(v)=h(v,v) +# over the involution-fixed field; dimension doubles. +H = og.HermitianForm.diagonal([1, -1]) +Q = H.restrict_scalars() +assert Q.dim == 4 and og.surreal_signature(Q) == (2, 2, 0) + # Checked game constructors preserve their proof and validation boundaries. arena = og.WittFifoArena(diagonal=[True], polar=[0], input=1) assert arena.quadratic_value and arena.grundy(state_budget=100_000) != 0 @@ -108,9 +114,12 @@ The Python layer monomorphizes a documented slice of the Rust backends. It does not provide a runtime-tagged any-scalar algebra. Its typed report surface includes finite quadratic modules and Nikulin criteria, extraspecial and Heisenberg--Weil objects, function-field Brauer--Wall classes, Niemeier data, -finite-field Witt decompositions, lexicode turning games, conformal-algebra -accessors, represented ordinal finite-subfield degrees, checked Witt--FIFO and -Brown constructors, and sealed Guy--Smith periodicity certificates. Python +finite-field Witt decompositions and numeric-invariant reports, +characteristic-two additive spinor norms and symmetry certificates, lexicode +turning games, conformal-algebra accessors, represented ordinal finite-subfield +degrees, checked Witt--FIFO and Brown constructors, Hermitian restriction to +typed ordinary quadratic backends, and sealed Guy--Smith periodicity +certificates. Python `repr` delegates to canonical Rust rendering where the core provides it. ## Mathematical status @@ -121,14 +130,19 @@ The papers under `writeups/` form one current research suite: | --- | --- | | `transfinite_arf.tex` | classification over perfect Artin--Schreier-surjective characteristic-two fields and its full-nimber specialization | | `goldarf.tex` | quadratic-refinement realization in normal play, Gold specialization, Brown selector, and game-exterior obstruction | +| `witt_realization.tex` | quadratic Witt coordinates over `F_2(t)`, finite impartial realization, explicit ramified naturality, and finite-static and singular no-go theorems | | `thermo_newton.tex` | thermic regrading under Norton multiplication and its separation from Newton tropicalization | +| `semiring_stability.tex` | stable quadratic-pair classification over Hessenberg and supertropical semirings, the universal scalar-extension quotient, and the thermograph wall obstruction | | `linking_affine.tex` | proved reductions and exact remaining obstruction for isolated-dummy FIFO linking | -| `excess.tex` | four-arm reduction and exact remaining selected-order problem for transfinite nim excess | - -The last two universal claims remain open. Their concise statements and sharp -proof boundaries are in [`docs/OPEN.md`](docs/OPEN.md). Lean checks named -ingredients and several end-to-end finite constructions, not the open -propositions; see [`formal/README.md`](formal/README.md). +| `excess.tex` | exact four-arm selected-order reduction, proved arithmetic boundaries, and authoritative open status of the transfinite nim-excess `0/1/4` rule | +| `nim_fast_multiplication.tex` | quasi-linear canonical-word multiplication via explicit affine transforms to a primitive Artin--Schreier tower | +| `misere_natural_realization.tex` | exact octal trace calculus, finite-exception heap normal form, realization of every tame finite quotient, and exact misere Grundy quotients through heap 18 | + +The unresolved universal claims and their sharp proof boundaries are in +[`docs/OPEN.md`](docs/OPEN.md). Lean checks named algebraic components and +end-to-end finite constructions including the literal Gold--Arf root; cited +bridges and the open propositions remain outside that boundary. See +[`formal/README.md`](formal/README.md) for the theorem map. ## Verification diff --git a/demo.py b/demo.py index 0bfb1da8..4e0b1916 100644 --- a/demo.py +++ b/demo.py @@ -140,6 +140,10 @@ def cl(qs): print(" WittClass metric constructor:", pl.WittClass.try_from_metric(A) == wA) print(" Dickson(swap) =", pl.dickson_matrix([[0, 1], [1, 0]]), " (a reflection)") print(" Dickson(diag *2,*3 rotation) =", pl.dickson_matrix([[2, 0], [0, 3]]), " (in SO)") +char2_factor = pl.factor_char2_isometry([0, 0], {(0, 1): 1}, [[0, 1], [1, 0]]) +print(" certified F2 symmetry factor:", char2_factor.factors, + "spinor", pl.char2_spinor_norm([0, 0], {(0, 1): 1}, [[0, 1], [1, 0]]), + "Clifford checked", char2_factor.clifford_verified) section("exterior algebra of the GAME group — lives where Clifford can't") # Λ needs only a ℤ-module; the game group is one, even for non-numbers (⋆, ↑). @@ -257,8 +261,8 @@ def cl(qs): print(" ω < ω² :", omega < pl.Ordinal.omega_pow(pl.Ordinal(2))) print(" ω fuzzy ω² (as nimbers):", omega.fuzzy(pl.Ordinal.omega_pow(pl.Ordinal(2)))) print(" 2 ⊗ 2 = *3 :", pl.Ordinal(2).nim_mul(pl.Ordinal(2))) -# nim-multiplication: implemented below ω^ω via the current DiMuro/Conway -# degree-3 tower. The old φ_{ω+1} (<ω³) case is the first layer. +# nim-multiplication: implemented below ω^ω via the DiMuro/Conway +# degree-3 tower. The φ_{ω+1} (<ω³) cell is its first layer. print(" ω ⊗ ω =", omega.nim_mul(omega), " (just polynomial mult)") omega_sq = omega.nim_mul(omega) print(" ω ⊗ ω ⊗ ω =", omega_sq.nim_mul(omega), " (the headline: ω³ = 2)") @@ -405,10 +409,11 @@ def _matmul(a, b): print(f" ⟨1,2,3⟩/F5: e0={stair.e0} (dim) e1={stair.e1} (disc) e2={stair.e2:+} (Hasse), I^{stair.stabilizes_at}=0") # Over ℝ the tower is infinite: eₙ reads the 2-adic expansion of the signature. print(" ⟨1,1,1,1⟩/ℝ (sig 4): eₙ for n=0..3 =", [pl.e_real(4, n) for n in range(4)]) -print(" numeric invariants of F5:", - "level", pl.level(5), - "pythagoras", pl.pythagoras_number(5), - "u", pl.u_invariant(5)) +finite_numeric = pl.finite_field_numeric_invariants(3, 2) +print(" numeric invariants of F9:", finite_numeric, + "level", pl.level(3, 2), + "pythagoras", pl.pythagoras_number(3, 2), + "u", pl.u_invariant(3, 2)) print(" WittClassG constructors:", pl.WittClassG.char0(3, 1), pl.WittClassG.oddchar_one(5, 0) * pl.WittClassG.oddchar_zero(5, 0), pl.WittClassG.char2(1).arf()) @@ -425,10 +430,21 @@ def _matmul(a, b): H = pl.HermitianForm.from_gram([[pl.Surcomplex(2, 0), pl.Surcomplex(0, 1)], [pl.Surcomplex(0, -1), pl.Surcomplex(2, 0)]]) print(" Hermitian [[2,i],[-i,2]]:", H.signature(), "diagonal", H.diagonalize()) +restricted_H = pl.HermitianForm.diagonal([1, -1, 0]).restrict_scalars() +print(" restriction to ordinary No-form:", restricted_H.dim, + pl.surreal_signature(restricted_H)) print(" diagonal Hermitian ⟨1,-1,0⟩:", pl.HermitianForm.diagonal([1, -1, 0]).signature()) -finite_H = pl.FiniteHermitianForm.diagonal(3, 2, [1, 1, 0]).classify() +finite_H_form = pl.FiniteHermitianForm.diagonal(3, 2, [1, 1, 0]) +finite_H = finite_H_form.classify() print(" finite Hermitian F9/F3 :", (finite_H.rank, finite_H.radical_dim, finite_H.base_field_order, finite_H.extension_field_order)) +finite_restriction = finite_H_form.restrict_scalars() +print(" F9/F3 ordinary restriction :", finite_restriction.dim, + pl.classify_finite_algebra(finite_restriction)) +char2_restriction = pl.FiniteHermitianForm.diagonal(2, 4, [1]).restrict_scalars() +char2_restriction_class = pl.classify_finite_algebra(char2_restriction) +print(" F16/F4 norm restriction :", char2_restriction.dim, char2_restriction_class) +assert char2_restriction_class.arf == 1 and char2_restriction_class.rank == 2 print(" form Rust constructors :", pl.SymplecticForm.from_gram([[0, 1], [-1, 0]]).classify().planes(), (lambda sig: (sig.pos, sig.neg, sig.radical))( diff --git a/docs/OPEN.md b/docs/OPEN.md index 711fbed1..3f093950 100644 --- a/docs/OPEN.md +++ b/docs/OPEN.md @@ -1,8 +1,8 @@ # Open mathematical problems -Ogdoad has six live research problems. The first two are universal -conjectures with exact reductions; the remaining four are constructive or -classification programs with explicit completion criteria. This document +Ogdoad has three live research problems. The first two are universal +conjectures with exact reductions; the third is a classification program with +an explicit completion criterion. This document states their current form, known starting point, and missing step. The linked papers carry the detailed arguments. Finite experiments and an implemented API are evidence or infrastructure, never substitutes for the stated theorem or @@ -133,6 +133,14 @@ decomposition, and the principal-ray/circular-unit factorization in the cubic and exceptional arms. It also proves several finite tensor-rank zero cases and shows why generic trace, norm, conductor, factor-shape, reciprocity, and unselected Kummer information cannot recover the marked Conway coordinate. +The selected singleton-even realization lies on +`y^2+y=x^3+x^2`; its function is supported on fixed five-torsion rather than +the Fermat-torsion point, so for positive levels it is not an `F_n`-Weil or +reduced-Tate pairing function attached to that point. The tempting curve +`y^2+y=x^3` is not isomorphic to the selected curve over `F_2`, and its +same-looking function has a trivial cubic Kummer class on positive-level +Fermat torsion. Perfectness of an alternating pairing still does not force a +prescribed pairing to be nonzero. `formal/Ogdoad/Excess.lean` kernel-checks the algebraic reduction layer and finite certificates used by the paper. `DPrimeTarget` and the analogous @@ -147,6 +155,16 @@ along its ancestry. Ambient statements about all points in the field do not distinguish it from the formal countermodels with the same trace, norm, torsion, or conductor data. +Cross-arm multiplicative induction has a sharp primary-vacuity boundary. If a +strict ancestor lies in `F_(2^d)` with `f(ell) ∤ d`, exactness of `f(ell)` gives +`ell ∤ 2^d - 1`, so the `ell`-power map on its multiplicative group is an +automorphism and its Kummer image is trivial. Thus cross-arm words built from +strict lower-level coordinates in such degrees are primary-vacuous. The next +`ell`-component does not repair this, because its radicand is defined as +`kappa_(f(ell)) + m_ell`; using its degree to identify `m_ell` with the +predicted `0`, `1`, or `4` is circular. A surviving cross-arm argument needs +a second, independently defined top-born `ell`-coordinate. + ### Nim reciprocity program This is a route into the same conjecture, not a fifth arm or a separate open @@ -172,6 +190,53 @@ of the following. - Applied to `T_h`, `y_(r,a)`, `gamma_k`, and `M_k`, it specializes to the marked primary coordinates in arms `Z`, `O`, `C`, and `D` respectively. +There is an exact rank-one saturation boundary. After adjoining `mu_ell`, +write a selected phase as `s` and index its absolute-Frobenius conjugates and +cyclotomic lifts by `(j,c)`. Covariance gives + +```text +s_(j,c) = s^(2^j c). +``` + +Consequently every multiplicative reciprocity word in this entire two-axis +family is a single power `s^lambda`. If `lambda = 0 mod ell` it erases the +selected phase; otherwise it is equivalent to the original test `s != 1`. +The inverse-Frobenius/Teichmuller weighted projector recovers `s` exactly, +but does not evaluate it. Thus a successful reciprocity proof must import an +independently evaluable local class or a genuinely nonhomogeneous marked- +ancestry identity; adding further norm, character, or orbit-product axes to +the same Kummer class cannot create a second constraint. + +Standard additive and nonabelian packages do not evade this boundary. A +Jacobi convolution at nonzero total `a` scales by the product-character value +at `a`, so on one Kummer line it is again a fixed scalar times one power of +the marked phase. In the split affine group, every one-step lift `(t,q)` with +fixed `q != 1` is conjugate to `(0,q)`, so class functions on that single +fixed-multiplier coset erase its frame coordinate. This does not apply to the +full-orbit element below: its `E`-th power is a pure translation, and an Artin +character over the cyclotomic base can distinguish zero from nonzero +holonomy, although evaluating it is the original selected problem. +A labeled off-diagonal Fourier coefficient exists after a root frame is +chosen, but that one-step coordinate is gauge-dependent. The intrinsic object +is the full `E`-step Frobenius-orbit holonomy + +```text +T = sum_(j=0)^(E-1) 2^(E-1-j) t_j, +``` + +whose diagonal Fourier eigenvalue is the selected phase. Frame changes +modify the `t_j` by a telescoping coboundary and leave `T` fixed. Affine +composition does not evaluate it: every homomorphic cocycle is a coboundary +with trivial full-period holonomy, while every field value can occur as the +closing holonomy after the proper path has been normalized. Nim reciprocity +must therefore supply an independent Conway-ancestry evaluation of this +closed-cycle coordinate; constructing it from the selected symbol is only an +exact reformulation. An affine ancestry intertwiner does not supply the +missing source: its closed holonomy is only a scalar multiple of the source +holonomy, because every vertex-origin term telescopes. Thus a strict ancestor +with trivial `ell`-coordinate cannot create the required nonzero current +coordinate through an affine recursion. + Success is a uniform nonvanishing/full-primary-order proof for every resulting coordinate, and therefore a proof of the `0/1/4` rule. Merely reconstructing a residue symbol or its orbit product does not close the problem: existing norm, @@ -190,190 +255,7 @@ deciding the selected value. - The authoritative mathematical account is [`../writeups/excess.tex`](../writeups/excess.tex). -## 3. Fast multiplication in canonical nim coordinates - -Put `n = 2^k` and - -```text -K_k = {0, ..., 2^n - 1} = F_(2^n), -``` - -represented by the literal `n`-bit nimber word. If -`c_i = 2^(2^i)`, these coordinates are the multivariate tower basis for - -```text -c_i^2 + c_i = product_(j < i) c_j. -``` - -Thus finite nimbers are an explicit quadratic Artin--Schreier tower, not an -arbitrary polynomial-basis presentation of the same abstract finite field. - -**Problem.** Construct a uniform family of exact multiplication algorithms - -```text -mul_k : K_k x K_k -> K_k -``` - -whose inputs and output are canonical nim words and whose bit complexity is -`M(n) log^O(1)(n)`, where `M(n)` is binary-polynomial multiplication cost. -Auxiliary space and any level-dependent conversion data must also have -`n log^O(1)(n)` size; preprocessing may be reported separately but may not hide -a quadratic multiplication tensor or table. - -### Known starting point - -The standard direct tower recurrence takes `O(k 3^k)`, or -`O(n^(log_2 3) log n)`, bit operations. The current `u128` backend instead -distributes over the set bits and memoizes products of basis powers; it is an -exact fixed-width implementation, not an asymptotic result. - -Quasi-linear arithmetic for arbitrary Artin--Schreier towers is already known: -the substantive target is therefore not an existence conjecture about finite -fields. It is an explicit specialization of the fast tower-isomorphism and -basis-conversion machinery to the literal Conway generators above, with a -proof that the returned bit string is the canonical nim product. - -### Completion criterion - -A completion consists of all four items. - -1. Give forward and inverse transforms between canonical nim coordinates and a - fast multiplication basis, uniformly in `k`. -2. Prove the tower equations, transforms, multiplication, and stated time and - space bounds, including precomputation. -3. Supply an arbitrary-width implementation with exhaustive agreement against - the mex/direct oracle on small fields and differential agreement against the - existing backend on its full supported word widths. -4. Measure the crossover. Retaining the present `u128` path below it is - compatible with solving the problem; a faster fixed-width table alone is - not. - -The mathematical starting point is the fast arbitrary-tower construction of -[De Feo--Schost](https://arxiv.org/abs/1002.2594). The implementation boundary -is `src/scalar/finite_field/nimber/arithmetic.rs`. - -## 4. Witt realization over an imperfect characteristic-two field - -Let `K = F_2(t)`. A nonsingular quadratic form over `K` has alternating polar -form `B`, but `B` does not determine the quadratic refinement. Moreover, -`K != K^2`: singular/quasilinear directions and the restriction of `Q` to the -polar radical carry information not captured by the finite-field Arf class -used by the Gold--Arf construction. - -For nonsingular forms, the characteristic-two Milnor--Scharlau sequence gives -a finite-support description of each individual class in `W_q(K)` by a -constant-field class and second residues at the finite and infinite places, -subject to a transfer relation: - -```text -0 -> W_q(F_2) -> W_q(F_2(t)) - -> direct_sum_v W_1(k(v)) -> W_q(F_2) -> 0. -``` - -The final arrow is the sum of the residue-field transfers. Ogdoad already -computes the nearby local Artin--Schreier symbols, wild Springer coordinates, -finite relevant-place set, and global isotropy verdict. Those results classify -algebraic data; they do not yet realize the Witt class by game outcomes. - -**Problem.** Construct a functorial, finite family of impartial normal-play -arenas `R(Q)` for every finite-dimensional quadratic space over `K` such that: - -1. the joint `P/N` outcome vector encodes the constant class and a chosen - finite presentation of every nonzero local residue coordinate; -2. `R(Q orthogonal_sum Q')` is the coordinatewise sum of `R(Q)` and `R(Q')`, - hyperbolic planes map to zero, and equality of outcome vectors is equivalent - to Witt equivalence; -3. the construction uses public polar data `B` and refinement-sensitive - diagonal queries `Q(e_i)` separately, and is invariant under change of - `K`-basis; -4. its placewise pieces commute with scalar extension and Scharlau transfer, - and their single global relation is the transfer/reciprocity relation in the - exact sequence; and -5. on constant finite-field trace forms it recovers the Gold--Arf realization - after restriction of scalars. - -The nonsingular target comes first. A complete extension to arbitrary -`Metric` must additionally encode the quasilinear radical and -`Q|_(rad B)`; projecting to a nonsingular complement and reporting only an Arf -class is explicitly not a solution. - -### Missing step - -The algebraic local--global sequence is known, and finite tuples of game -outcomes can encode its finite residue groups. What is missing is a canonical -arena construction whose local observations respect the sequence, orthogonal -sum, and basis change simultaneously. A coefficient-by-coefficient Boolean -encoding of one displayed rational function proves only that a finite instance -can be serialized; it does not descend to `W_q(K)` and does not solve the -problem. - -The algebraic scaffold is the characteristic-two Milnor--Scharlau sequence of -[Aravire--Jacob](https://msp.org/pjm/2006/228-1/pjm-v228-n1-p02-s.pdf). The -existing implementation surfaces are -`src/forms/local_global/function_field_char2.rs` and -`src/forms/springer/char2/`. - -## 5. Stable quadratic forms over semirings - -For a commutative semiring `R`, a quadratic pair on a free `R`-module is a map -`q` and a symmetric bilinear companion `b` satisfying - -```text -q(a x) = a^2 q(x), -q(x + y) = q(x) + q(y) + b(x, y). -``` - -Without additive inverses, `b` need not be determined by `q`; hyperbolicity, -orthogonal complements, and group completion also stop being automatic. The -basic companion, rigidity, isometry, scalar-extension, and unique-basis -cancellation theories are already available in the semiring literature, so -reproving their definitions is not the research target. - -Consider two coefficient worlds: - -- a tangible supertropical enhancement of Ogdoad's max/min-plus semirings, - retaining the ghost layer needed by supertropical quadratic-form theory; and -- the Hessenberg semiring of ordinals under natural sum and natural product, - represented first on a closed finite-CNF fragment. - -**Problem.** For each world, construct and compute a stable form invariant with -the following properties, or prove that these requirements force it to -collapse. - -1. Start from isometry classes of finite free quadratic pairs under orthogonal - sum. Specify regular, split, and metabolic objects without using subtraction, - and prove that the proposed stable quotient is a well-defined commutative - monoid or group completion. -2. Classify rank-two pairs and their companion ambiguity, then give generators, - relations, and a decision procedure for the stable invariant on every - supported finite rank. -3. Make supervaluation/scalar extension from ring-valued forms functorial and - determine exactly which classical Witt data survives supertropicalization. -4. Determine whether the Hessenberg invariant is nontrivial beyond the - diagonal/Cantor-normal-form data forced by its unique basis and trivial unit - group. -5. Test whether the max/min-plus pair of thermograph walls defines a stable - class. If it does not, isolate the minimal obstruction implied by freezing, - the Norton degree defect, or temperature-zero torsion. - -### Boundaries and completion - -The current `Tropical` type supplies a bipotent semiring but no tangible/ -ghost supertropical layer, and `Pl` thermograph walls lack a representable -infinite identity. The thermic-regrading paper proves that thermography is not -a Newton-style graded ring. These are constraints on a proposed theory, not -reasons to insert either type into the existing ring-based Witt or Clifford -APIs. - -A successful nontrivial invariant, or a no-go theorem showing that the five -requirements force collapse in one coefficient world, closes that arm. Merely -adding a generic matrix type over `Semiring` does not. The starting references -are [supertropical quadratic forms](https://arxiv.org/abs/1309.5729) and the -[unique-basis cancellation theorem](https://arxiv.org/abs/1509.01039); the -in-house obstruction is -[`../writeups/thermo_newton.tex`](../writeups/thermo_newton.tex). - -## 6. Natural realization of finite misère quotients +## 3. Natural realization of finite misère quotients Let `A` be a set of finite impartial games closed under options and disjunctive sum, and write `o^-(G)` for the misère outcome. Define @@ -424,6 +306,74 @@ give a finite presentation, its `P`-portion, and a proved quotient map for all heap sizes. If it is infinite, exhibit an infinite family of pairwise distinguishable positions together with contexts that separate them. +The current exact reduction is in +[`../writeups/misere_natural_realization.tex`](../writeups/misere_natural_realization.tex). +In a reduced valid transition table, the option-value set determines its +value: a least-rank separating context would otherwise descend to a smaller +separating option. Thus a fixed octal code and table determine one exact heap +trace. The trace criterion is an if-and-only-if statement: all exact heap +records must lie in the table and their values must generate the target +monoid. For codes without split bits, a finite quotient forces this trace to +be ultimately periodic, since its next value is a deterministic function of +the last `d` values. Hence exact realization by a fixed finite no-split code +is decidable. Splitting introduces the unbounded convolution +`{x_i x_j : i + j = n}`; an already periodic trace still has a finite exact +certificate through the explicit bound `2N + p + d`, but automatic +periodicity is not proved in that case. Every value in a nontrivial valid +table has a context carrying it into `P`; combined with the meximal condition, +this proves that no transition value can occur among its own options. For an +octal trace this gives the absolute exclusions + +```text +C_n => x_n != 1 +A_k, k < n => x_n != x_(n-k) +B_k, i+j = n-k => x_n != x_i x_j. +``` + +These are exact obstruction filters, not a characterization. + +The split recurrence also has an exact unary-language formulation. If +`X_q = {n : x_n = q}`, then the language where `q` occurs as an option is a +finite union of whole-removal constants, shifts of `X_q`, and shifted additive +convolutions `X_u + X_v` over `uv = q`; Boolean cells recover each complete +option-value set. The trace is total exactly when the union of cells on which +the partial decoder is undefined is empty. This dictionary is checked in +Lean. It does not collapse to a finite automaton: if the decoder is arbitrary +rather than a valid misère table, totality is `Pi^0_1`-complete already for +the fixed code `0.7`, by encoding unary unambiguous conjunctive-grammar +emptiness in a truncated multiset monoid. Thus any fixed-code decision theorem +must use closedness, parity, ranking, and reducedness essentially; finiteness +and commutativity alone are insufficient. + +There is also a comparison normal-form theorem. Every nontrivial finite +valid quotient has an exact one-species numerical heap realization with a +finite source-local prefix, one inert padding heap, and the uniform tail move +`H_n -> H_(n-1)`. This is not a finite-octal realization: encoding a prefix +edge by an octal digit repeats that edge at every larger source heap. The +theorem isolates this translated cross-talk but does not prove it is the only +possible obstruction to a different octal gadget. More sharply, when the +quotient has more than two elements, no exact heapwise encoding of all chosen +descending prefix records can retain the later inert pad: some record needs a +nonidentity option, hence a persistent one- or two-remainder bit, and that bit +also gives the pad an option. An active bridge or a contextual generator +encoding remains possible. + +There is also a uniform positive family: for every `n >= 2`, the finite code +with `2^(n-1)` consecutive digits equal to `3` has exact quotient `T_n`, the +tame quotient of order `2^n + 2`. This realizes the entire tame arm of the +finite `|P| = 2` classification. The parallel Grundy analysis is exact through +heap 18: the quotient through heap 13 is a reduced monoid of order 12, and the +quotient through heap 18 is a reduced monoid of order 24, with presentations, +all-multiplicity outcome inductions, and separating translation rows in the +note. These prefix theorems neither prove stabilization nor supply an infinite +distinguishable family. There is also a global conditional obstruction: if +the full Grundy quotient is finite, finite Ramsey supplies unequal `i,j` with +`x_i = x_j = x_(i+j) = c`, so a heap record contains `c^2` among its options. +Closure and the no-self theorem force all consecutive positive powers of `c` +to differ. Hence the power sequence has eventual period at least two and the +kernel of the finite quotient is nontrivial. This is compatible with tame +period-two behavior and is not yet an infinity proof. + ### Milestones and completion 1. Implement the valid-transition-table or minimex criterion as an exact @@ -442,6 +392,16 @@ distinguishable positions together with contexts that separate them. induction, then verify surjectivity, reduction, and the `P`-portion. A stable bounded table is not enough. +The algorithmic gap has two independent layers. For fixed finite `(Q, P)`, +fixed valid `T`, and fixed split code, determinism makes the trace a partial +computable recurrence; deciding whether that recurrence is total is still +open. The arbitrary-decoder `Pi^0_1`-completeness theorem identifies validity +as the precise missing restriction, but does not establish hardness for valid +tables. Even if every fixed-code instance became decidable, monoid-level +synthesis ranges over unbounded code lengths. A complete decision procedure +therefore also needs a computable length bound or pumping normalization, or +else an undecidability theorem. + The octal-realization arm closes with the requested if-and-only-if theorem and certified construction/obstruction algorithm. The Grundy arm closes with one of the two exact finite/infinite certificates above. Calling a quotient @@ -451,6 +411,14 @@ infinite. The starting theory is the [Plambeck--Siegel quotient construction](https://arxiv.org/abs/math/0609825) and Siegel's [valid-transition-table classification](https://arxiv.org/abs/math/0703070). +The algebraic determinism, meximal and no-self obstructions, exact quotient +sufficiency, periodic complete-record certificate, typed +prefix/pad/unary-tail normal form, inert-pad obstruction, unary-language +dictionary, and algebraic power-period obstruction are checked in Lean. The +formal-language hardness theorem, numerical rank-order transport for the +normal form, tame-family strategy, kernel deduction, and exact Grundy-prefix +presentations are presently paper proofs. The distinct-summand finite-color +bridge is checked from Mathlib's Hindman theorem. The current `src/games/misere.rs` routines compare only bounded element and test sets, so their signatures and multiplication flags are observational evidence, not exact quotient certificates. The `misere_quotient` and diff --git a/docs/README.md b/docs/README.md index 6e2c760e..f7ab3881 100644 --- a/docs/README.md +++ b/docs/README.md @@ -3,7 +3,7 @@ | document | authority | | --- | --- | | [`../README.md`](../README.md) | public scope, architecture, and supported surface | -| [`OPEN.md`](OPEN.md) | the six live mathematical problems and their exact frontiers | +| [`OPEN.md`](OPEN.md) | the three live mathematical problems and their exact frontiers | | [`ROADMAP.md`](ROADMAP.md) | unfinished engineering and publication work | | [`VERIFY.md`](VERIFY.md) | evidence classes, verification gates, and representation limits | | [`../formal/README.md`](../formal/README.md) | Lean theorem map and proof boundary | diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index d2078885..681fc1c8 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -10,19 +10,30 @@ Highest-value additions are the joins between already public subsystems: | work | completion criterion | | --- | --- | -| Clifford centers | construct centers and even centers as discriminant etale algebras and compare their classes with the Brauer--Wall coordinates | -| Milnor symbols | expose the degree-at-most-two mod-two symbols underlying the existing residue and `e_n` maps | -| Hermitian restriction | compose trace/restriction of scalars with the ordinary quadratic classifiers | | ramified Springer | add a named ramified-extension path with correct value-group parity and independent tests | -| finite-field invariants | lift level, Pythagoras number, and u-invariant reporting from prime fields to supported extensions | -| characteristic-two spinor norm | construct the additive spinor norm from vector-symmetry factorizations | | explicit Brauer representatives | materialize cyclic or quaternion algebras for computed classes | +## Additional mathematics + +New self-contained mathematical areas begin with one auditable construction +and an explicit law or certificate boundary. They remain engineering programs +here unless they produce a sharp unresolved statement for `OPEN.md`. + +| work | completion criterion | +| --- | --- | +| quadratic algebras | implement the rank-two context `S[u]/(u^2 - t*u + n)` with canonical conjugation, trace, norm, partial inverse, and split/etale/nonreduced classification on supported fields; recover `Surcomplex` as the `u^2 = -1` specialization and materialize computed Clifford centers as operational quadratic algebras | +| Hermitian lattices | add exact Gaussian- and Eisenstein-integral Hermitian Gram forms with dual, discriminant, and restriction-of-scalars bridges; implement the corresponding code Construction A, construct the Coxeter--Todd lattice, and independently verify its ordinary integral invariants through the existing lattice, genus, and theta surfaces | +| representable matroids | construct finite-field representable matroids with rank, circuits, flats, duality, deletion/contraction, and a budgeted Tutte polynomial; verify Greene's theorem against independently computed code weight enumerators | +| stabilizer codes | construct finite symplectic Pauli spaces and isotropic stabilizers, derive CSS codes from supported linear codes, and return bounded certificates for parameters and logical operators whose commutation and encoded dimension agree with the independent symplectic and Heisenberg representations | + ## API and maintenance -No unfinished API-maintenance work is currently tracked. New Python bindings -must preserve per-backend type separation and update the generated stub in the -same commit. +| work | completion criterion | +| --- | --- | +| arbitrary-width nim multiplication | implement the proved affine transforms and primitive Artin--Schreier tower from `writeups/nim_fast_multiplication.tex`; exhaustively agree with the mex/direct oracle on small fields, differentially agree with the `u128` backend on every supported width, and measure the crossover while retaining the current path below it | + +New Python bindings must preserve per-backend type separation and update the +generated stub in the same commit. ## Grundy language diff --git a/docs/VERIFY.md b/docs/VERIFY.md index 2d59737d..f30cdfd7 100644 --- a/docs/VERIFY.md +++ b/docs/VERIFY.md @@ -56,6 +56,9 @@ are regenerated artifacts and must also receive page-level visual review. | `Surreal` | exact finite-support expressions only | | `Nimber` | exact `F_(2^128)` arithmetic | | `Ordinal` | exact inside the checked Kummer window; checked multiplication, inversion, and square roots refuse escape | +| Hermitian restriction | `q(v)=h(v,v)` over the represented involution-fixed field, with trace used only for the polar form; dimension doubles and refuses past 128 | +| mod-two Milnor symbols | degrees `0..=2` over `Q`, supported odd finite fields, and `F_q(t)`; strict `e_n` maps require membership in `I^n`; characteristic-two Kato and wild local symbols are outside the surface | +| Clifford centers | for nonsingular forms, materialized full/even central generators with Kummer or characteristic-two Artin--Schreier relation; split class and parity are independently compared with Brauer--Wall coordinates; degenerate centers are refused | | bounded search | budget and exhaustion are reported separately from mathematical truth | The `associativity_*` tests and diff --git a/experiments/common.py b/experiments/common.py index 3df5eb72..33133f60 100644 --- a/experiments/common.py +++ b/experiments/common.py @@ -1,4 +1,4 @@ -"""Shared Gold-form helpers for the experiment scripts.""" +"""Shared finite-field trace helpers for the experiment scripts.""" import ogdoad as pl @@ -15,43 +15,3 @@ def nim_trace(x: int, m: int) -> int: # trace is garbage-in-garbage-out (e.g. nim_trace(2, 1) == 2) assert value in (0, 1), f"trace not in F2: {value}" return value - - -def nim_mul(a: int, b: int) -> int: - """Nim (Conway) multiplication on raw ints, via the bound engine.""" - return (pl.Nimber(a) * pl.Nimber(b)).value - - -def gold_lam(v: int, a: int, m: int, lam: int = 1) -> int: - """Lam-generalized Gold form Q_a(v) = Tr(lam * v^(1+2^a)) over F_{2^m}. - - ``lam`` multiplies the product before the trace. - """ - x = pl.Nimber(v) - return nim_trace((pl.Nimber(lam) * x * frob(x, a)).value, m) - - -def polar_lam(u: int, v: int, a: int, m: int, lam: int = 1) -> int: - """Lam-generalized polar form B(u,v) = Q(u+v) + Q(u) + Q(v).""" - return gold_lam(u ^ v, a, m, lam) ^ gold_lam(u, a, m, lam) ^ gold_lam(v, a, m, lam) - - -def gold(v: int, a: int, m: int) -> int: - """Gold form Q_a(v) = Tr(v^(1+2^a)) over F_{2^m}.""" - return gold_lam(v, a, m) - - -def polar(u: int, v: int, a: int, m: int) -> int: - """Polar form B(u,v) = Q(u+v) + Q(u) + Q(v).""" - return polar_lam(u, v, a, m) - - -def gold_table(a: int, m: int, lam: int = 1) -> list: - """The lam-generalized Gold form over all of F_{2^m}, index = point.""" - return [gold_lam(v, a, m, lam) for v in range(1 << m)] - - -def report(name: str, ok: bool) -> None: - """Print "name: PASS/FAIL" and assert ok.""" - print(f"{name}: {'PASS' if ok else 'FAIL'}") - assert ok diff --git a/experiments/linking_game.py b/experiments/linking_game.py index 826761e5..4c5dcf34 100644 --- a/experiments/linking_game.py +++ b/experiments/linking_game.py @@ -755,8 +755,9 @@ def strategy_holds(k: int, edges, seat: int, menu_version: int = 5) -> bool: """Defender (flips-even) restricted to a prevention/debt menu, attacker unrestricted optimal; STRICT (an empty/illegal menu = defender loss). Menu-existential: True means a winning move always exists IN the menu. - Versions 3 and 4 reproduce the two superseded finite boundaries; version - 5 is the current no-self-flip prevention envelope.""" + Versions 3 and 4 encode narrower finite-boundary menus used as + negative-evidence controls; version 5 is the no-self-flip prevention + envelope.""" if menu_version not in (3, 4, 5): raise ValueError(f"unknown menu version: {menu_version}") n = k + 1 # always with dummy diff --git a/formal/Ogdoad.lean b/formal/Ogdoad.lean index 5538d4d2..fec74bac 100644 --- a/formal/Ogdoad.lean +++ b/formal/Ogdoad.lean @@ -1,99 +1,16 @@ -import Ogdoad.Off -import Ogdoad.SymplecticBasis -import Ogdoad.CharTwoClassification -import Ogdoad.WittFrame -import Ogdoad.Fifo -import Ogdoad.FifoCausal -import Ogdoad.FifoSymmetry -import Ogdoad.FifoNormalization -import Ogdoad.FifoStrategy -import Ogdoad.FifoAffine -import Ogdoad.FifoCrossDescent -import Ogdoad.FifoCrossClose -import Ogdoad.FifoMixedCross -import Ogdoad.FifoSameOpenBraid -import Ogdoad.FifoDummyExitCarrier -import Ogdoad.FifoCrossExitIncidence -import Ogdoad.FifoOuterFan -import Ogdoad.FifoProtectedFan -import Ogdoad.FifoInterlace -import Ogdoad.FifoDummyDeletion -import Ogdoad.FifoRootSelector -import Ogdoad.FifoNeutralPair -import Ogdoad.FifoMinHotCurvature -import Ogdoad.FifoHub -import Ogdoad.FifoEmptyQueue -import Ogdoad.FifoTreeTrace -import Ogdoad.FifoBlockInduction -import Ogdoad.FifoOutcome -import Ogdoad.FifoOutcomeBlock -import Ogdoad.FifoOutcomeSwitch -import Ogdoad.FifoPairState -import Ogdoad.FifoStrategyBadAncestry -import Ogdoad.FifoStrategyBadAncestryClear -import Ogdoad.FifoFirstSeatRoot -import Ogdoad.FifoBadArcCycle -import Ogdoad.FifoFirstSeatStrategy -import Ogdoad.FifoFunctionalDigraphBoundary -import Ogdoad.FifoDummyFront -import Ogdoad.FifoDummyFrontAffine -import Ogdoad.FifoSeparatorFlow -import Ogdoad.FifoThreeSiblingBoundary -import Ogdoad.FifoRootCongruence -import Ogdoad.FifoBlockReversalBoundary -import Ogdoad.FifoCanonicalPositionalOdd -import Ogdoad.FifoCellSwapOutcomeBoundary -import Ogdoad.FifoCommonDummyEventBoundary -import Ogdoad.FifoCongruenceOutcomeBoundary -import Ogdoad.FifoConsumedDummyBoundary -import Ogdoad.FifoConsumedDummyShearBoundary -import Ogdoad.FifoControlledDivergence -import Ogdoad.FifoDistinctOpenForkBoundary -import Ogdoad.FifoGaussianElimination -import Ogdoad.FifoGlobalSpliceBoundary -import Ogdoad.FifoLastChargedCloseBoundary -import Ogdoad.FifoLiveDummyOpenFork -import Ogdoad.FifoOddSpikeDummyReply -import Ogdoad.FifoOddSpikeFactor -import Ogdoad.FifoPairGaussian -import Ogdoad.FifoPairZeroMomentAdjacentBoundary -import Ogdoad.FifoPairZeroMomentNormal -import Ogdoad.FifoPairZeroMomentSafety -import Ogdoad.FifoParityControlledRoot -import Ogdoad.FifoParityCounterNormal -import Ogdoad.FifoParitySeat -import Ogdoad.FifoParitySeatCloseFirst -import Ogdoad.FifoParitySeatDeviationBoundary -import Ogdoad.FifoPositionalSelectedEdgeBoundary -import Ogdoad.FifoPositionalStateDAGBoundary -import Ogdoad.FifoPrivateLeafBoundary -import Ogdoad.FifoProtectedBlockBoundary -import Ogdoad.FifoProtectedFactorBoundary -import Ogdoad.FifoPublicPolicyAffine -import Ogdoad.FifoPublicPolicyDuality -import Ogdoad.FifoPublicPolicyTopologyBoundary -import Ogdoad.FifoPublicPrefixQueueCutBoundary -import Ogdoad.FifoPublicQueueCutCounterexample -import Ogdoad.FifoPublicSeparatorAncestry -import Ogdoad.FifoPublicSeparatorAutomaton -import Ogdoad.FifoPublicSeparatorQueueDebt -import Ogdoad.FifoRootGadgetBoundary -import Ogdoad.FifoSeparatorBadSynchronizationBoundary -import Ogdoad.FifoSeparatorControlledBridgeBoundary -import Ogdoad.FifoSingletonForkBoundary -import Ogdoad.FifoMatching -import Ogdoad.ImpartialRealizer -import Ogdoad.GoldSemantics -import Ogdoad.GoldMatchingAlgebra -import Ogdoad.PhysicalDeferred -import Ogdoad.GoldArena -import Ogdoad.GoldNoEvaluator -import Ogdoad.GoldBlockCompression -import Ogdoad.GoldForkPadding -import Ogdoad.Excess -import Ogdoad.GameExterior -import Ogdoad.BrownGame -import Ogdoad.BrownSelectorPGame -import Ogdoad.GoldDiagonal -import Ogdoad.GoldExtraspecial -import Ogdoad.GoldExtraspecialTrace +import Ogdoad.Papers.TransfiniteArf +import Ogdoad.Papers.GoldArf +import Ogdoad.Papers.WittRealization +import Ogdoad.Papers.LinkingAffine +import Ogdoad.Papers.Excess +import Ogdoad.Papers.NimFastMultiplication +import Ogdoad.Papers.SemiringStability +import Ogdoad.Papers.MisereNaturalRealization + +/-! +# Ogdoad formal development + +The root imports one explicit entry point per Lean-backed paper. Shared +mathematics lives in ordinary domain modules and is imported by each paper +surface that uses it; paper entry points contain no duplicated declarations. +-/ diff --git a/formal/Ogdoad/Algebra/ArtinSchreier.lean b/formal/Ogdoad/Algebra/ArtinSchreier.lean new file mode 100644 index 00000000..de2c8713 --- /dev/null +++ b/formal/Ogdoad/Algebra/ArtinSchreier.lean @@ -0,0 +1,212 @@ +import Ogdoad.Algebra.ZModTwo + +/-! +# Shared Artin--Schreier algebra + +This module collects paper-independent characteristic-two Artin--Schreier +facts shared by the Gold, excess, and canonical-nim developments. It has no +dependency on any paper-specific surface. +-/ + +namespace Ogdoad.ArtinSchreier + +open Polynomial +open scoped CharTwo + +noncomputable section + +section Basic + +variable {K : Type*} [CommRing K] [CharP K 2] + +/-- The Artin--Schreier map is additive in characteristic two. -/ +theorem add (x y : K) : + (x + y) ^ 2 + (x + y) = (x ^ 2 + x) + (y ^ 2 + y) := by + ring_nf + simp + +/-- If `u` has source `t` and `v` sources `lambda + b*t`, then `v + b*u` +sources `lambda` whenever `b` is idempotent. -/ +theorem tower_lift (u t b v lambda : K) + (hu : u ^ 2 + u = t) (hb : b ^ 2 = b) + (hv : v ^ 2 + v = lambda + b * t) : + (v + b * u) ^ 2 + (v + b * u) = lambda := by + rw [add, hv] + rw [mul_pow, hb] + calc + lambda + b * t + (b * u ^ 2 + b * u) = + lambda + b * (t + (u ^ 2 + u)) := by ring + _ = lambda := by rw [hu, CharTwo.add_self_eq_zero]; simp + +/-- The two sources of one Artin--Schreier value differ by one. -/ +theorem companion (w lambda : K) (hw : w ^ 2 + w = lambda) : + (w + 1) ^ 2 + (w + 1) = lambda := by + rw [add, hw] + have hone : (1 : K) ^ 2 + 1 = 0 := by simp + rw [hone, add_zero] + +end Basic + +section FiniteField + +/-- The Artin--Schreier endomorphism of a finite characteristic-two field, +viewed as an `F_2`-linear map. -/ +noncomputable def linear + (K : Type*) [Field K] [Finite K] [Algebra F2 K] : K →ₗ[F2] K := + (FiniteField.frobeniusAlgHom F2 K).toLinearMap - LinearMap.id + +@[simp] +theorem linear_apply + {K : Type*} [Field K] [Finite K] [Algebra F2 K] (x : K) : + linear K x = x ^ 2 + x := by + letI : CharP K 2 := + charP_of_injective_algebraMap (algebraMap F2 K).injective 2 + simp [linear] + +/-- Absolute trace is invariant under the characteristic-two Frobenius. -/ +theorem absoluteTrace_sq + {K : Type*} [Field K] [Finite K] [Algebra F2 K] (x : K) : + Algebra.trace F2 K (x ^ 2) = Algebra.trace F2 K x := by + letI : CharP K 2 := + charP_of_injective_algebraMap (algebraMap F2 K).injective 2 + apply (algebraMap F2 K).injective + rw [trace_eq_sum_automorphisms (K := F2) (L := K) (x ^ 2)] + calc + ∑ sigma : Gal(K / F2), sigma (x ^ 2) = + ∑ sigma : Gal(K / F2), (sigma x) ^ 2 := by simp + _ = (∑ sigma : Gal(K / F2), sigma x) ^ 2 := by rw [sum_pow_char] + _ = (algebraMap F2 K (Algebra.trace F2 K x)) ^ 2 := by + rw [trace_eq_sum_automorphisms (K := F2) (L := K) x] + _ = algebraMap F2 K (Algebra.trace F2 K x) := by + rw [← map_pow, ZMod.pow_card] + +/-- Every Artin--Schreier value has zero absolute trace. -/ +theorem absoluteTrace_apply_zero + {K : Type*} [Field K] [Finite K] [Algebra F2 K] (x : K) : + Algebra.trace F2 K (x ^ 2 + x) = 0 := by + letI : CharP K 2 := + charP_of_injective_algebraMap (algebraMap F2 K).injective 2 + rw [map_add, absoluteTrace_sq] + exact CharTwo.add_self_eq_zero _ + +/-- The kernel of the Artin--Schreier map is exactly the prime field. -/ +theorem ker_linear + {K : Type*} [Field K] [Finite K] [Algebra F2 K] : + LinearMap.ker (linear K) = F2 ∙ (1 : K) := by + letI : CharP K 2 := + charP_of_injective_algebraMap (algebraMap F2 K).injective 2 + ext x + constructor + · intro hx + rw [LinearMap.mem_ker, linear_apply] at hx + have hfac : x * (x + 1) = 0 := by + calc + x * (x + 1) = x ^ 2 + x := by ring + _ = 0 := hx + rcases mul_eq_zero.mp hfac with hx0 | hx1 + · subst x + exact Submodule.zero_mem _ + · have hxone : x = 1 := by + have := eq_neg_of_add_eq_zero_left hx1 + simpa using this + subst x + exact Submodule.mem_span_singleton_self 1 + · intro hx + rw [Submodule.mem_span_singleton] at hx + obtain ⟨c, rfl⟩ := hx + rw [LinearMap.mem_ker, linear_apply] + simp only [Algebra.smul_def, mul_one] + rw [← map_pow, ZMod.pow_card] + exact CharTwo.add_self_eq_zero _ + +/-- The Artin--Schreier image is the absolute-trace kernel. -/ +theorem range_linear_eq_ker_trace + {K : Type*} [Field K] [Finite K] [Algebra F2 K] : + LinearMap.range (linear K) = LinearMap.ker (Algebra.trace F2 K) := by + letI : CharP K 2 := + charP_of_injective_algebraMap (algebraMap F2 K).injective 2 + apply Submodule.eq_of_le_of_finrank_eq + · intro y hy + rcases hy with ⟨x, rfl⟩ + rw [LinearMap.mem_ker, linear_apply] + exact absoluteTrace_apply_zero x + · have hAS := LinearMap.finrank_range_add_finrank_ker (linear K) + rw [ker_linear, finrank_span_singleton (by exact one_ne_zero)] at hAS + have htrSurj : Function.Surjective (Algebra.trace F2 K) := + Algebra.trace_surjective F2 K + have htrRange : LinearMap.range (Algebra.trace F2 K) = ⊤ := + LinearMap.range_eq_top.mpr htrSurj + have htr := LinearMap.finrank_range_add_finrank_ker (Algebra.trace F2 K) + rw [htrRange, finrank_top] at htr + norm_num at htr + omega + +/-- The finite-field Artin--Schreier exact sequence in elementwise form. -/ +theorem trace_eq_zero_iff_exists + {K : Type*} [Field K] [Finite K] [Algebra F2 K] {a : K} : + Algebra.trace F2 K a = 0 ↔ ∃ w : K, w ^ 2 + w = a := by + constructor + · intro htrace + have hmem : a ∈ LinearMap.ker (Algebra.trace F2 K) := htrace + rw [← range_linear_eq_ker_trace] at hmem + rcases hmem with ⟨w, hw⟩ + exact ⟨w, by simpa using hw⟩ + · rintro ⟨w, rfl⟩ + exact absoluteTrace_apply_zero w + +end FiniteField + +section Polynomial + +variable {K : Type*} [Field K] [CharP K 2] + +/-- The quadratic Artin--Schreier polynomial with source `a`. -/ +abbrev polynomial (a : K) : K[X] := X ^ 2 + X + C a + +@[simp] +theorem isRoot_polynomial_iff (a x : K) : + (polynomial a).IsRoot x ↔ x ^ 2 + x = a := by + rw [Polynomial.IsRoot.def] + simp only [polynomial, eval_add, eval_pow, eval_X, eval_C] + rw [add_eq_zero_iff_eq_neg, CharTwo.neg_eq] + +omit [CharP K 2] in +@[simp] +theorem natDegree_polynomial (a : K) : (polynomial a).natDegree = 2 := by + simpa [polynomial] using + (natDegree_quadratic (a := (1 : K)) (b := 1) (c := a) one_ne_zero) + +omit [CharP K 2] in +theorem monic_polynomial (a : K) : (polynomial a).Monic := by + have h := Polynomial.isMonicOfDegree_add_add_two (R := K) (1 : K) a + simpa [polynomial] using h.monic + +/-- Over a finite characteristic-two field, `X^2 + X + a` is irreducible +exactly when `a` has absolute trace one. -/ +theorem irreducible_polynomial_iff_trace_eq_one + [Finite K] [Algebra F2 K] (a : K) : + Irreducible (polynomial a) ↔ Algebra.trace F2 K a = 1 := by + have hp0 : polynomial a ≠ 0 := (monic_polynomial a).ne_zero + rw [Polynomial.irreducible_iff_roots_eq_zero_of_degree_le_three + (p := polynomial a) + (by rw [natDegree_polynomial]) + (by rw [natDegree_polynomial]; omega)] + rw [Multiset.eq_zero_iff_forall_notMem] + simp only [Polynomial.mem_roots hp0, isRoot_polynomial_iff] + constructor + · intro hnoroot + apply Ogdoad.zmod2_eq_one_of_ne_zero + intro hzero + obtain ⟨w, hw⟩ := (trace_eq_zero_iff_exists (K := K)).mp hzero + exact hnoroot w hw + · intro hone w hw + have hzero : Algebra.trace F2 K a = 0 := + (trace_eq_zero_iff_exists (K := K)).mpr ⟨w, hw⟩ + rw [hone] at hzero + exact one_ne_zero hzero + +end Polynomial + +end + +end Ogdoad.ArtinSchreier diff --git a/formal/Ogdoad/Algebra/ZModTwo.lean b/formal/Ogdoad/Algebra/ZModTwo.lean new file mode 100644 index 00000000..8b76f07f --- /dev/null +++ b/formal/Ogdoad/Algebra/ZModTwo.lean @@ -0,0 +1,54 @@ +import Mathlib + +/-! +# Shared binary scalar facts + +This module owns the elementary `ZMod 2` interface used across the quadratic- +form, FIFO, Gold, Witt-realization, and nim-multiplication developments. These +facts are algebraic infrastructure, not part of any one paper's formal surface. +-/ + +namespace Ogdoad + +/-- The common binary coefficient field used throughout the formal project. -/ +abbrev F2 := ZMod 2 + +/-- A nonzero binary scalar is the unit bit. -/ +theorem zmod2_eq_one_of_ne_zero (x : F2) (hx : x ≠ 0) : x = 1 := by + apply ZMod.val_injective + have hxval : x.val ≠ 0 := by + intro h + exact hx ((ZMod.val_eq_zero x).mp h) + have hxlt : x.val < 2 := x.val_lt + change x.val = 1 + omega + +/-- A binary scalar different from the unit bit is zero. -/ +theorem zmod2_eq_zero_of_ne_one (x : F2) (hx : x ≠ 1) : x = 0 := by + by_contra hx0 + exact hx (zmod2_eq_one_of_ne_zero x hx0) + +/-- Every binary scalar is idempotent. -/ +theorem zmod2_sq_eq_self (x : F2) : x * x = x := by + by_cases hx : x = 0 + · simp [hx] + · rw [zmod2_eq_one_of_ne_zero x hx] + simp + +/-- Distinct binary scalars differ by the unit bit. -/ +theorem zmod2_add_one_add_eq_zero_of_ne (x y : F2) (hxy : x ≠ y) : + x + 1 + y = 0 := by + have hsum0 : x + y ≠ 0 := by + intro h + have : x = y := by + calc + x = x + (y + y) := by rw [CharTwo.add_self_eq_zero, add_zero] + _ = (x + y) + y := by abel + _ = y := by rw [h, zero_add] + exact hxy this + have hsum1 : x + y = 1 := zmod2_eq_one_of_ne_zero _ hsum0 + calc + x + 1 + y = (x + y) + 1 := by abel + _ = 0 := by rw [hsum1, CharTwo.add_self_eq_zero] + +end Ogdoad diff --git a/formal/Ogdoad/Certificate/BinaryPolynomial.lean b/formal/Ogdoad/Certificate/BinaryPolynomial.lean new file mode 100644 index 00000000..2bc30997 --- /dev/null +++ b/formal/Ogdoad/Certificate/BinaryPolynomial.lean @@ -0,0 +1,65 @@ +import Mathlib.Data.Nat.Bitwise + +/-! +# Binary-polynomial certificate arithmetic + +This module owns the executable bit-polynomial arithmetic shared by exact +finite-field certificates. A natural number encodes a polynomial over +`F_2`, with bit `i` equal to the coefficient of `X^i`. + +These definitions are certificate evaluators, not a proved finite-field +implementation. Individual certificate modules separately check that their +chosen modulus is irreducible and then use native evaluation for named finite +identities. +-/ + +namespace Ogdoad.BinaryPolynomialCertificate + +/-- Shift-and-reduce multiplication with an explicit recursion budget. -/ +def mulAux (modulus degree : Nat) : Nat → Nat → Nat → Nat → Nat + | 0, _, _, acc => acc + | fuel + 1, a, b, acc => + let acc' := if b % 2 = 1 then Nat.xor acc a else acc + let a2 := Nat.shiftLeft a 1 + let a' := if a2.testBit degree then Nat.xor a2 modulus else a2 + mulAux modulus degree fuel a' (b / 2) acc' + +/-- Multiplication modulo a monic binary polynomial of the stated degree. -/ +def mul (modulus degree a b : Nat) : Nat := + mulAux modulus degree degree a b 0 + +/-- Binary powering with an explicit recursion budget. -/ +def powAux (modulus degree : Nat) : Nat → Nat → Nat → Nat → Nat + | 0, _, _, acc => acc + | fuel + 1, a, e, acc => + if e = 0 then acc + else + let acc' := if e % 2 = 1 then mul modulus degree acc a else acc + powAux modulus degree fuel (mul modulus degree a a) (e / 2) acc' + +/-- Powering modulo a monic binary polynomial. -/ +def fpow (modulus degree a e : Nat) : Nat := + powAux modulus degree (degree + 2) a e 1 + +/-- Binary-polynomial remainder with an explicit recursion budget. -/ +def polyModAux : Nat → Nat → Nat → Nat + | 0, a, _ => a + | fuel + 1, a, b => + if b = 0 ∨ Nat.log2 a < Nat.log2 b then a + else polyModAux fuel + (Nat.xor a (Nat.shiftLeft b (Nat.log2 a - Nat.log2 b))) b + +/-- Binary-polynomial remainder. -/ +def polyMod (fuel a b : Nat) : Nat := polyModAux fuel a b + +/-- Euclidean gcd with explicit outer and remainder budgets. -/ +def polyGcdAux (remainderFuel : Nat) : Nat → Nat → Nat → Nat + | 0, a, _ => a + | fuel + 1, a, b => + if b = 0 then a + else polyGcdAux remainderFuel fuel b (polyMod remainderFuel a b) + +/-- Binary-polynomial gcd using one budget for both Euclidean loops. -/ +def polyGcd (fuel a b : Nat) : Nat := polyGcdAux fuel fuel a b + +end Ogdoad.BinaryPolynomialCertificate diff --git a/formal/Ogdoad/Excess.lean b/formal/Ogdoad/Excess.lean index e569a28f..a10d7ce1 100644 --- a/formal/Ogdoad/Excess.lean +++ b/formal/Ogdoad/Excess.lean @@ -1,6 +1,5 @@ import Mathlib -import Mathlib.Algebra.Polynomial.HasseDeriv -import Mathlib.Data.Nat.Choose.Lucas +import Ogdoad.Certificate.BinaryPolynomial /-! # Lenstra-excess reductions @@ -404,6 +403,22 @@ theorem orderOf_eq_prime_of_pow_eq_one {G : Type*} [Monoid G] have hord_dvd : orderOf a ∣ p := orderOf_dvd_iff_pow_eq_one.mpr hpow exact (hp.dvd_iff_eq hord_ne_one).mp hord_dvd |>.symm +/-- Beyond the initial level, every element annihilated by the `n`-th +Fermat number is already a cube in its own cyclic subgroup. Hence a cubic +Kummer or Tate class cannot distinguish full from proper conductor in the +singleton-even target torus: `F_n` is coprime to `F_0 = 3`. -/ +theorem fermat_torsion_is_cube {G : Type*} [Group G] + (x : G) (n : Nat) (hn : n ≠ 0) + (htors : x ^ Nat.fermatNumber n = 1) : + IsPthPower 3 x := by + have hFcop : (Nat.fermatNumber n).Coprime 3 := by + simpa using Nat.coprime_fermatNumber_fermatNumber + (m := n) (n := 0) hn + have hord : orderOf x ∣ Nat.fermatNumber n := + orderOf_dvd_iff_pow_eq_one.mpr htors + exact isPthPower_of_coprime_order + (Nat.Coprime.of_dvd_left hord hFcop) + section CubicExceptionalResidue variable {R : Type*} [CommRing R] @@ -1823,29 +1838,35 @@ theorem birth_phase_finset_product_has_one_weight Finset.prod_congr rfl hphase _ = theta ^ (∑ i ∈ s, w i) := Finset.prod_pow_eq_pow_sum s w theta -/-- A global product of two coprime-primary phases is nontrivial exactly -when at least one primary phase is nontrivial. -/ +/-- Torsion classes of coprime exponents cannot cancel in one multiplicative +product. A product relation is trivial only when both primary coordinates are +trivial. -/ +theorem coprime_torsion_product_eq_one_iff + (x y : H) {a b : Nat} + (hx : x ^ a = 1) (hy : y ^ b = 1) (hab : a.Coprime b) : + x * y = 1 ↔ x = 1 ∧ y = 1 := by + constructor + · intro hxy + have hyx : y = x⁻¹ := eq_inv_of_mul_eq_one_right hxy + have hxb : x ^ b = 1 := by + have : (x⁻¹) ^ b = 1 := by simpa [hyx] using hy + simpa only [inv_pow, inv_eq_one] using this + have hoa : orderOf x ∣ a := orderOf_dvd_of_pow_eq_one hx + have hob : orderOf x ∣ b := orderOf_dvd_of_pow_eq_one hxb + have ho : orderOf x = 1 := by + apply Nat.eq_one_of_dvd_one + simpa [hab.gcd_eq_one] using Nat.dvd_gcd hoa hob + have hx1 : x = 1 := orderOf_eq_one_iff.mp ho + exact ⟨hx1, by simpa [hx1] using hxy⟩ + · rintro ⟨rfl, rfl⟩ + simp + +/-- Negated form of `coprime_torsion_product_eq_one_iff`. -/ theorem coprime_phase_product_ne_one_iff (x y : H) {a b : Nat} (hx : x ^ a = 1) (hy : y ^ b = 1) (hab : a.Coprime b) : x * y ≠ 1 ↔ x ≠ 1 ∨ y ≠ 1 := by - have hprod : x * y = 1 ↔ x = 1 ∧ y = 1 := by - constructor - · intro hxy - have hyx : y = x⁻¹ := eq_inv_of_mul_eq_one_right hxy - have hxb : x ^ b = 1 := by - have : (x⁻¹) ^ b = 1 := by simpa [hyx] using hy - simpa only [inv_pow, inv_eq_one] using this - have hoa : orderOf x ∣ a := orderOf_dvd_of_pow_eq_one hx - have hob : orderOf x ∣ b := orderOf_dvd_of_pow_eq_one hxb - have ho : orderOf x = 1 := by - apply Nat.eq_one_of_dvd_one - simpa [hab.gcd_eq_one] using Nat.dvd_gcd hoa hob - have hx1 : x = 1 := orderOf_eq_one_iff.mp ho - exact ⟨hx1, by simpa [hx1] using hxy⟩ - · rintro ⟨rfl, rfl⟩ - simp - rw [ne_eq, hprod] + rw [ne_eq, coprime_torsion_product_eq_one_iff x y hx hy hab] tauto end CubicBirthSecants @@ -1961,34 +1982,6 @@ theorem sixth_root_cube_ne_one (A : R) end SixthRootSupportArithmetic -section CurrentPrimarySupport - -variable {G : Type*} [CommGroup G] - -/-- Phases of coprime primary orders cannot cancel. Hence a product detects -only the union of their supports, not that each coordinate is nontrivial. -/ -theorem current_primary_product_eq_one_iff - (x y : G) {a b : Nat} - (hx : x ^ a = 1) (hy : y ^ b = 1) (hab : a.Coprime b) : - x * y = 1 ↔ x = 1 ∧ y = 1 := by - constructor - · intro hxy - have hyx : y = x⁻¹ := eq_inv_of_mul_eq_one_right hxy - have hxb : x ^ b = 1 := by - have : (x⁻¹) ^ b = 1 := by simpa [hyx] using hy - simpa only [inv_pow, inv_eq_one] using this - have hoa : orderOf x ∣ a := orderOf_dvd_of_pow_eq_one hx - have hob : orderOf x ∣ b := orderOf_dvd_of_pow_eq_one hxb - have ho : orderOf x = 1 := by - apply Nat.eq_one_of_dvd_one - simpa [hab.gcd_eq_one] using Nat.dvd_gcd hoa hob - have hx1 : x = 1 := orderOf_eq_one_iff.mp ho - exact ⟨hx1, by simpa [hx1] using hxy⟩ - · rintro ⟨rfl, rfl⟩ - simp - -end CurrentPrimarySupport - /-- The alternating `F₄` translate turns the selected depressed cubic into a norm-coherent twisted cubic. -/ theorem dk_twisted_translate_recursion @@ -4178,6 +4171,29 @@ section FermatRayPairingNoGo variable {F : Type*} [Field F] +/-- The standard rank-two alternating exponent form is nondegenerate but +vanishes on every diagonal. Specializing to `F = ZMod ell` gives the +algebraic core of a perfect Weil-pairing countermodel: a nonzero selected +`ell`-torsion point may pair trivially with a prescribed nonzero point on the +same isotropic line. Perfectness only guarantees some detecting partner. -/ +theorem symplectic_pairing_nondegenerate_but_diagonal_zero : + let pairing := fun P Q : F × F => P.1 * Q.2 - P.2 * Q.1 + (∀ P, P ≠ 0 → ∃ Q, pairing P Q ≠ 0) ∧ + ∃ P, P ≠ 0 ∧ pairing P P = 0 := by + dsimp only + constructor + · intro P hP + by_cases hfirst : P.1 = 0 + · have hsecond : P.2 ≠ 0 := by + intro hzero + apply hP + ext <;> simp [hfirst, hzero] + refine ⟨(1, 0), ?_⟩ + simpa [hfirst] using neg_ne_zero.mpr hsecond + · refine ⟨(0, 1), ?_⟩ + simpa using hfirst + · refine ⟨(1, 0), ?_, ?_⟩ <;> simp + /-- Even a nonzero weight-one equivariant map can kill the complete orbit of a distinguished class. The first coordinate models the ramified local-unit line detected by the Witt coefficient; the second models the @@ -4300,6 +4316,23 @@ theorem cubic_scaled_self_dual_core (x y z a : K) · rw [← hsum, add_pow_char, add_pow_char] · exact hpair +/-- Characteristic-two expression of the cubic Moore determinant in the +first two elementary symmetric coordinates. -/ +theorem cubic_moore_det_identity (x y z : K) : + x ^ 3 + y ^ 3 + z ^ 3 + x * y * z = + (x + y + z) ^ 3 + + (x + y + z) * (x * y + y * z + z * x) := by + have hfour : (4 : K) = 0 := by + change ((4 : Nat) : K) = 0 + rw [CharP.cast_eq_mod K 2 4] + norm_num + ring_nf + rw [show (9 : K) = 1 by + change ((9 : Nat) : K) = 1 + rw [CharP.cast_eq_mod K 2 9] + norm_num] + simp [hfour] + /-- The determinant of the immediate cubic Moore/circulant matrix is the cube of the lower inverse selector. Thus the selected normal-basis determinant contains no new current multiplicative coordinate. -/ @@ -4307,20 +4340,11 @@ theorem cubic_moore_det_core (x y z a : K) (hsum : x + y + z = a) (hpair : x * y + y * z + z * x = 0) : x ^ 3 + y ^ 3 + z ^ 3 + x * y * z = a ^ 3 := by - have hfour : (4 : K) = 0 := by - change ((4 : Nat) : K) = 0 - rw [CharP.cast_eq_mod K 2 4] - norm_num calc x ^ 3 + y ^ 3 + z ^ 3 + x * y * z = (x + y + z) ^ 3 + - (x + y + z) * (x * y + y * z + z * x) := by - ring_nf - rw [show (9 : K) = 1 by - change ((9 : Nat) : K) = 1 - rw [CharP.cast_eq_mod K 2 9] - norm_num] - simp [hfour] + (x + y + z) * (x * y + y * z + z * x) := + cubic_moore_det_identity x y z _ = a ^ 3 := by rw [hpair, mul_zero, add_zero, hsum] /-- Translating the selected Singer cubic by one swaps its two lower @@ -4346,20 +4370,11 @@ theorem cubic_moore_det_symmetric_core (x y z b c : K) (hsum : x + y + z = b) (hpair : x * y + y * z + z * x = c) : x ^ 3 + y ^ 3 + z ^ 3 + x * y * z = b ^ 3 + b * c := by - have hfour : (4 : K) = 0 := by - change ((4 : Nat) : K) = 0 - rw [CharP.cast_eq_mod K 2 4] - norm_num calc x ^ 3 + y ^ 3 + z ^ 3 + x * y * z = (x + y + z) ^ 3 + - (x + y + z) * (x * y + y * z + z * x) := by - ring_nf - rw [show (9 : K) = 1 by - change ((9 : Nat) : K) = 1 - rw [CharP.cast_eq_mod K 2 9] - norm_num] - simp [hfour] + (x + y + z) * (x * y + y * z + z * x) := + cubic_moore_det_identity x y z _ = b ^ 3 + b * c := by rw [hsum, hpair] /-- Determinant of the constant-off-diagonal trace Gram matrix of the @@ -4505,6 +4520,48 @@ theorem twist_eq_inv_mul rw [h] field_simp +/-- Jacobi convolution with a marked nonzero additive total. -/ +def jacobiSumAt + {F R : Type*} [Field F] [Fintype F] [CommRing R] + (chi psi : MulChar F R) (a : F) : R := + ∑ x : F, chi x * psi (a - x) + +/-- Scaling the additive total of a Jacobi convolution retains only the +product-character value at that total. -/ +theorem jacobiSumAt_eq_mul_jacobiSum + {F R : Type*} [Field F] [Fintype F] [CommRing R] + (chi psi : MulChar F R) (a : F) (ha : a ≠ 0) : + jacobiSumAt chi psi a = + chi a * psi a * jacobiSum chi psi := by + classical + rw [jacobiSumAt, jacobiSum] + calc + (∑ x : F, chi x * psi (a - x)) = + ∑ x : F, chi (a * x) * psi (a - a * x) := by + exact (Equiv.sum_comp (Equiv.mulLeft₀ a ha) + (fun x : F ↦ chi x * psi (a - x))).symm + _ = ∑ x : F, (chi a * psi a) * (chi x * psi (1 - x)) := by + apply Finset.sum_congr rfl + intro x _ + rw [map_mul, show a - a * x = a * (1 - x) by ring, map_mul] + ring + _ = chi a * psi a * ∑ x : F, chi x * psi (1 - x) := by + rw [Finset.mul_sum] + +/-- When both inputs lie on one Kummer-character line, the additive Jacobi +convolution has exactly the sum of their two character weights. -/ +theorem jacobiSumAt_pow_eq_pow_mul + {F R : Type*} [Field F] [Fintype F] [CommRing R] + (chi : MulChar F R) (r s : Nat) (a : F) (ha : a ≠ 0) : + jacobiSumAt (chi ^ r) (chi ^ s) a = + chi a ^ (r + s) * jacobiSum (chi ^ r) (chi ^ s) := by + rw [jacobiSumAt_eq_mul_jacobiSum (chi ^ r) (chi ^ s) a ha] + have hr : (chi ^ r) a = chi a ^ r := by + simpa using MulChar.pow_apply_coe chi r (Units.mk0 a ha) + have hs : (chi ^ s) a = chi a ^ s := by + simpa using MulChar.pow_apply_coe chi s (Units.mk0 a ha) + rw [hr, hs, ← pow_add] + /-- A quotient-line sum with one exceptional weight evaluates exactly to `q`. This is the finite combinatorial core of the paper's semiprimitive Gauss calculation. -/ @@ -4891,41 +4948,9 @@ Python experiment. -/ def modulus : Nat := 0x4000000000000000000200000001 def degree : Nat := 110 -def mulAux : Nat → Nat → Nat → Nat → Nat - | 0, _, _, acc => acc - | fuel + 1, a, b, acc => - let acc' := if b % 2 = 1 then Nat.xor acc a else acc - let a2 := Nat.shiftLeft a 1 - let a' := if a2.testBit degree then Nat.xor a2 modulus else a2 - mulAux fuel a' (b / 2) acc' - -def mul (a b : Nat) : Nat := mulAux degree a b 0 - -def powAux : Nat → Nat → Nat → Nat → Nat - | 0, _, _, acc => acc - | fuel + 1, a, e, acc => - if e = 0 then acc - else - let acc' := if e % 2 = 1 then mul acc a else acc - powAux fuel (mul a a) (e / 2) acc' - -def fpow (a e : Nat) : Nat := powAux (degree + 2) a e 1 - -def polyModAux : Nat → Nat → Nat → Nat - | 0, a, _ => a - | fuel + 1, a, b => - if b = 0 ∨ Nat.log2 a < Nat.log2 b then a - else polyModAux fuel - (Nat.xor a (Nat.shiftLeft b (Nat.log2 a - Nat.log2 b))) b - -def polyMod (a b : Nat) : Nat := polyModAux 300 a b - -def polyGcdAux : Nat → Nat → Nat → Nat - | 0, a, _ => a - | fuel + 1, a, b => - if b = 0 then a else polyGcdAux fuel b (polyMod a b) - -def polyGcd (a b : Nat) : Nat := polyGcdAux 300 a b +abbrev mul := BinaryPolynomialCertificate.mul modulus degree +abbrev fpow := BinaryPolynomialCertificate.fpow modulus degree +abbrev polyGcd := BinaryPolynomialCertificate.polyGcd 300 def epsilon : Nat := 0x533a83d1770ba6987acd705100a def zeta23 : Nat := 0x244532b647a337388955260d12d1 @@ -5518,25 +5543,8 @@ def eta : Nat := 1629469875507523981620540 def epsilon : Nat := 1629469875507523981620541 def beta : Nat := 2293671573472151973449566 -def mulAux : Nat → Nat → Nat → Nat → Nat - | 0, _, _, acc => acc - | fuel + 1, a, b, acc => - let acc' := if b % 2 = 1 then Nat.xor acc a else acc - let a2 := Nat.shiftLeft a 1 - let a' := if a2.testBit degree then Nat.xor a2 modulus else a2 - mulAux fuel a' (b / 2) acc' - -def mul (a b : Nat) : Nat := mulAux degree a b 0 - -def powAux : Nat → Nat → Nat → Nat → Nat - | 0, _, _, acc => acc - | fuel + 1, a, e, acc => - if e = 0 then acc - else - let acc' := if e % 2 = 1 then mul acc a else acc - powAux fuel (mul a a) (e / 2) acc' - -def fpow (a e : Nat) : Nat := powAux (degree + 2) a e 1 +abbrev mul := BinaryPolynomialCertificate.mul modulus degree +abbrev fpow := BinaryPolynomialCertificate.fpow modulus degree def etaTrace27 : Nat := Nat.xor eta (Nat.xor (fpow eta q) (fpow eta (q ^ 2))) @@ -5550,21 +5558,7 @@ def halfCirculant : Nat := (List.range 79).foldl (fun acc j => Nat.xor acc (fpow epsilon (2 ^ (j + 1)))) 0 -def polyModAux : Nat → Nat → Nat → Nat - | 0, a, _ => a - | fuel + 1, a, b => - if b = 0 ∨ Nat.log2 a < Nat.log2 b then a - else polyModAux fuel - (Nat.xor a (Nat.shiftLeft b (Nat.log2 a - Nat.log2 b))) b - -def polyMod (a b : Nat) : Nat := polyModAux 200 a b - -def polyGcdAux : Nat → Nat → Nat → Nat - | 0, a, _ => a - | fuel + 1, a, b => - if b = 0 then a else polyGcdAux fuel b (polyMod a b) - -def polyGcd (a b : Nat) : Nat := polyGcdAux 200 a b +abbrev polyGcd := BinaryPolynomialCertificate.polyGcd 200 set_option maxRecDepth 100000 in set_option maxHeartbeats 0 in @@ -5629,25 +5623,8 @@ def q : Nat := 2 ^ 16 def z : Nat := 0x5ddbf713 def z' : Nat := 0x4198613b -def mulAux : Nat → Nat → Nat → Nat → Nat - | 0, _, _, acc => acc - | fuel + 1, a, b, acc => - let acc' := if b % 2 = 1 then Nat.xor acc a else acc - let a2 := Nat.shiftLeft a 1 - let a' := if a2.testBit degree then Nat.xor a2 modulus else a2 - mulAux fuel a' (b / 2) acc' - -def mul (a b : Nat) : Nat := mulAux degree a b 0 - -def powAux : Nat → Nat → Nat → Nat → Nat - | 0, _, _, acc => acc - | fuel + 1, a, e, acc => - if e = 0 then acc - else - let acc' := if e % 2 = 1 then mul acc a else acc - powAux fuel (mul a a) (e / 2) acc' - -def fpow (a e : Nat) : Nat := powAux (degree + 2) a e 1 +abbrev mul := BinaryPolynomialCertificate.mul modulus degree +abbrev fpow := BinaryPolynomialCertificate.fpow modulus degree def fibAux : Nat → Nat → Nat → Nat → Nat | 0, _, s₀, _ => s₀ @@ -8247,6 +8224,26 @@ variable {K : Type*} [Field K] [CharP K 2] /-- The inversion-quotient coordinate on the norm-one torus. -/ def torusPhi (x : K) : K := x / (x + 1) ^ 2 +/-- On the auxiliary supersingular model `y²+y=x³`, the function +`u=(y+1)/y` has inversion-quotient coordinate exactly `x³`. This curve is +not the selected Conway curve `y²+y=x³+x²` over `F₂`; together with +`fermat_torsion_is_cube`, the identity rules out replacing the selected +five-torsion function by this natural cubic divisor/Tate class. -/ +theorem auxiliary_supersingular_function_torusPhi_eq_cube + (x y : K) (hy : y ≠ 0) (hE : y ^ 2 + y = x ^ 3) : + torusPhi ((y + 1) / y) = x ^ 3 := by + simp only [torusPhi] + have htwo : (2 : K) = 0 := CharP.cast_eq_zero K 2 + have hy1 : (y + 1) / y + 1 = 1 / y := by + field_simp + ring_nf + simp [htwo] + rw [hy1] + field_simp + calc + y * (y + 1) = y ^ 2 + y := by ring + _ = x ^ 3 := hE + /-- Tripling on the norm-one torus descends to a fixed rational map on the fibotomic coordinate. -/ theorem torusPhi_cube @@ -8448,25 +8445,16 @@ argument supplying the two nonzero low-degree polynomials remains in prose. -/ variable {F : Type*} [Field F] [CharP F 2] -theorem z36_expansions - (c x y : F) - (hc : c ^ 6 + c ^ 3 + 1 = 0) - (hx : x ^ 2 + x = c ^ 3) - (hy : y ^ 3 = c) : - (x + y) ^ 37 = - (1 + c ^ 2 + c ^ 3) + c ^ 2 * x + - (c ^ 3 + c ^ 4) * y + - (c + c ^ 3 + c ^ 4) * (y * x) + - c * (y ^ 2 * x) := by +/-- The common power-reduction block for the degree-nine cubic component at +the `h = 36` level. Keeping it here prevents the two expansions and their +minor certificates from carrying private copies of the same calculation. -/ +theorem z36_base_powers + (c : F) (hc : c ^ 6 + c ^ 3 + 1 = 0) : + c ^ 6 = c ^ 3 + 1 ∧ + c ^ 7 = c ^ 4 + c ∧ + c ^ 8 = c ^ 5 + c ^ 2 ∧ + c ^ 9 = 1 := by have htwo : (2 : F) = 0 := CharP.cast_eq_zero F 2 - have hthree : (3 : F) = 1 := by - calc - (3 : F) = 2 + 1 := by norm_num - _ = 1 := by rw [htwo]; simp - have hfour : (4 : F) = 0 := by - calc - (4 : F) = 2 + 2 := by norm_num - _ = 0 := by rw [htwo]; simp have hc6 : c ^ 6 = c ^ 3 + 1 := by apply eq_of_sub_eq_zero rw [CharTwo.sub_eq_add] @@ -8489,6 +8477,20 @@ theorem z36_expansions _ = 1 := by rw [hc6] linear_combination (c ^ 3) * htwo + exact ⟨hc6, hc7, hc8, hc9⟩ + +/-- Frobenius reductions for the quadratic component used by both symbolic +`h = 36` expansions. -/ +theorem z36_quadratic_powers + (c x : F) (hc : c ^ 6 + c ^ 3 + 1 = 0) + (hx : x ^ 2 + x = c ^ 3) : + x ^ 2 = x + c ^ 3 ∧ + x ^ 4 = x + 1 ∧ + x ^ 8 = x + c ^ 3 + 1 ∧ + x ^ 16 = x ∧ + x ^ 32 = x + c ^ 3 := by + have htwo : (2 : F) = 0 := CharP.cast_eq_zero F 2 + obtain ⟨hc6, _, _, _⟩ := z36_base_powers c hc have hx2 : x ^ 2 = x + c ^ 3 := by calc x ^ 2 = c ^ 3 - x := (eq_sub_iff_add_eq).2 hx @@ -8522,16 +8524,45 @@ theorem z36_expansions x ^ 32 = (x ^ 16) ^ 2 := by ring _ = x ^ 2 := by rw [hx16] _ = x + c ^ 3 := hx2 - have hy4 : y ^ 4 = c * y := by - calc + exact ⟨hx2, hx4, hx8, hx16, hx32⟩ + +/-- The cubic-component reductions shared by the two symbolic expansions. -/ +theorem z36_cubic_powers + (c y : F) (hc : c ^ 6 + c ^ 3 + 1 = 0) (hy : y ^ 3 = c) : + y ^ 4 = c * y ∧ y ^ 32 = c * y ^ 2 := by + obtain ⟨_, _, _, hc9⟩ := z36_base_powers c hc + constructor + · calc y ^ 4 = y ^ 3 * y := by ring _ = c * y := by rw [hy] - have hy32 : y ^ 32 = c * y ^ 2 := by - calc + · calc y ^ 32 = (y ^ 3) ^ 10 * y ^ 2 := by ring _ = c ^ 10 * y ^ 2 := by rw [hy] _ = (c ^ 9 * c) * y ^ 2 := by ring _ = c * y ^ 2 := by rw [hc9]; ring + +theorem z36_expansions + (c x y : F) + (hc : c ^ 6 + c ^ 3 + 1 = 0) + (hx : x ^ 2 + x = c ^ 3) + (hy : y ^ 3 = c) : + (x + y) ^ 37 = + (1 + c ^ 2 + c ^ 3) + c ^ 2 * x + + (c ^ 3 + c ^ 4) * y + + (c + c ^ 3 + c ^ 4) * (y * x) + + c * (y ^ 2 * x) := by + have htwo : (2 : F) = 0 := CharP.cast_eq_zero F 2 + have hthree : (3 : F) = 1 := by + calc + (3 : F) = 2 + 1 := by norm_num + _ = 1 := by rw [htwo]; simp + have hfour : (4 : F) = 0 := by + calc + (4 : F) = 2 + 2 := by norm_num + _ = 0 := by rw [htwo]; simp + obtain ⟨hc6, _, _, _⟩ := z36_base_powers c hc + obtain ⟨hx2, hx4, _, _, hx32⟩ := z36_quadratic_powers c x hc hx + obtain ⟨hy4, hy32⟩ := z36_cubic_powers c y hc hy have hz4 : (x + y) ^ 4 = (x + 1) + c * y := by calc (x + y) ^ 4 = x ^ 4 + y ^ 4 := by @@ -8568,65 +8599,14 @@ theorem z36_expansion_109 (1 + c ^ 2 + c ^ 3 + c ^ 5) * y ^ 2 + (c ^ 2 + c ^ 3 + c ^ 4 + c ^ 5) * (y ^ 2 * x) := by have htwo : (2 : F) = 0 := CharP.cast_eq_zero F 2 - have hc6 : c ^ 6 = c ^ 3 + 1 := by - apply eq_of_sub_eq_zero - rw [CharTwo.sub_eq_add] - simpa [add_assoc] using hc - have hc7 : c ^ 7 = c ^ 4 + c := by - calc - c ^ 7 = c * c ^ 6 := by ring - _ = c * (c ^ 3 + 1) := by rw [hc6] - _ = c ^ 4 + c := by ring - have hc8 : c ^ 8 = c ^ 5 + c ^ 2 := by - calc - c ^ 8 = c ^ 2 * c ^ 6 := by ring - _ = c ^ 2 * (c ^ 3 + 1) := by rw [hc6] - _ = c ^ 5 + c ^ 2 := by ring - have hc9 : c ^ 9 = 1 := by - calc - c ^ 9 = c ^ 3 * c ^ 6 := by ring - _ = c ^ 3 * (c ^ 3 + 1) := by rw [hc6] - _ = c ^ 6 + c ^ 3 := by ring - _ = 1 := by rw [hc6]; linear_combination (c ^ 3) * htwo - have hx2 : x ^ 2 = x + c ^ 3 := by - calc - x ^ 2 = c ^ 3 - x := (eq_sub_iff_add_eq).2 hx - _ = c ^ 3 + x := CharTwo.sub_eq_add _ _ - _ = x + c ^ 3 := add_comm _ _ - have hx4 : x ^ 4 = x + 1 := by - calc - x ^ 4 = (x ^ 2) ^ 2 := by ring - _ = (x + c ^ 3) ^ 2 := by rw [hx2] - _ = x ^ 2 + c ^ 6 := by rw [add_sq, htwo]; ring - _ = (x + c ^ 3) + (c ^ 3 + 1) := by rw [hx2, hc6] - _ = x + 1 := by linear_combination (c ^ 3) * htwo + obtain ⟨hc6, hc7, hc8, hc9⟩ := z36_base_powers c hc + obtain ⟨hx2, hx4, hx8, _, hx32⟩ := z36_quadratic_powers c x hc hx have hx5 : x ^ 5 = c ^ 3 := by calc x ^ 5 = x * x ^ 4 := by ring _ = x * (x + 1) := by rw [hx4] _ = x ^ 2 + x := by ring _ = c ^ 3 := hx - have hx8 : x ^ 8 = x + c ^ 3 + 1 := by - calc - x ^ 8 = (x ^ 4) ^ 2 := by ring - _ = (x + 1) ^ 2 := by rw [hx4] - _ = x ^ 2 + 1 := by rw [add_sq, htwo]; ring - _ = x + c ^ 3 + 1 := by rw [hx2] - have hx16 : x ^ 16 = x := by - calc - x ^ 16 = (x ^ 8) ^ 2 := by ring - _ = (x + c ^ 3 + 1) ^ 2 := by rw [hx8] - _ = x ^ 2 + c ^ 6 + 1 := by - rw [show x + c ^ 3 + 1 = (x + c ^ 3) + 1 by ring] - rw [add_sq, add_sq, htwo] - ring - _ = (x + c ^ 3) + (c ^ 3 + 1) + 1 := by rw [hx2, hc6] - _ = x := by linear_combination (c ^ 3 + 1) * htwo - have hx32 : x ^ 32 = x + c ^ 3 := by - calc - x ^ 32 = (x ^ 16) ^ 2 := by ring - _ = x ^ 2 := by rw [hx16] - _ = x + c ^ 3 := hx2 have hx64 : x ^ 64 = x + 1 := by calc x ^ 64 = (x ^ 32) ^ 2 := by ring @@ -8634,10 +8614,7 @@ theorem z36_expansion_109 _ = x ^ 2 + c ^ 6 := by rw [add_sq, htwo]; ring _ = (x + c ^ 3) + (c ^ 3 + 1) := by rw [hx2, hc6] _ = x + 1 := by linear_combination (c ^ 3) * htwo - have hy4 : y ^ 4 = c * y := by - calc - y ^ 4 = y ^ 3 * y := by ring - _ = c * y := by rw [hy] + obtain ⟨hy4, hy32⟩ := z36_cubic_powers c y hc hy have hy5 : y ^ 5 = c * y ^ 2 := by calc y ^ 5 = y ^ 3 * y ^ 2 := by ring @@ -8654,12 +8631,6 @@ theorem z36_expansion_109 calc y ^ 8 = (y ^ 3) ^ 2 * y ^ 2 := by ring _ = c ^ 2 * y ^ 2 := by rw [hy] - have hy32 : y ^ 32 = c * y ^ 2 := by - calc - y ^ 32 = (y ^ 3) ^ 10 * y ^ 2 := by ring - _ = c ^ 10 * y ^ 2 := by rw [hy] - _ = (c ^ 9 * c) * y ^ 2 := by ring - _ = c * y ^ 2 := by rw [hc9]; ring have hc21 : c ^ 21 = c ^ 3 := by calc c ^ 21 = (c ^ 9) ^ 2 * c ^ 3 := by ring @@ -8755,15 +8726,7 @@ theorem z36_minor_37 simpa using (CharP.cast_eq_mod F 2 3) have hfive : (5 : F) = 1 := by simpa using (CharP.cast_eq_mod F 2 5) - have hc6 : c ^ 6 = c ^ 3 + 1 := by - apply eq_of_sub_eq_zero - rw [CharTwo.sub_eq_add] - simpa [add_assoc] using hc - have hc7 : c ^ 7 = c ^ 4 + c := by - calc - c ^ 7 = c * c ^ 6 := by ring - _ = c * (c ^ 3 + 1) := by rw [hc6] - _ = c ^ 4 + c := by ring + obtain ⟨hc6, hc7, _, _⟩ := z36_base_powers c hc have heq : (1 + c ^ 2 + c ^ 3) * (c + c ^ 3 + c ^ 4) + c ^ 2 * (c ^ 3 + c ^ 4) = 1 + c ^ 3 + c ^ 4 := by @@ -8790,26 +8753,7 @@ theorem z36_minor_109 simpa using (CharP.cast_eq_mod F 2 6) have hseven : (7 : F) = 1 := by simpa using (CharP.cast_eq_mod F 2 7) - have hc6 : c ^ 6 = c ^ 3 + 1 := by - apply eq_of_sub_eq_zero - rw [CharTwo.sub_eq_add] - simpa [add_assoc] using hc - have hc7 : c ^ 7 = c ^ 4 + c := by - calc - c ^ 7 = c * c ^ 6 := by ring - _ = c * (c ^ 3 + 1) := by rw [hc6] - _ = c ^ 4 + c := by ring - have hc8 : c ^ 8 = c ^ 5 + c ^ 2 := by - calc - c ^ 8 = c ^ 2 * c ^ 6 := by ring - _ = c ^ 2 * (c ^ 3 + 1) := by rw [hc6] - _ = c ^ 5 + c ^ 2 := by ring - have hc9 : c ^ 9 = 1 := by - calc - c ^ 9 = c ^ 3 * c ^ 6 := by ring - _ = c ^ 3 * (c ^ 3 + 1) := by rw [hc6] - _ = c ^ 6 + c ^ 3 := by ring - _ = 1 := by rw [hc6]; linear_combination (c ^ 3) * htwo + obtain ⟨hc6, hc7, hc8, hc9⟩ := z36_base_powers c hc have hc10 : c ^ 10 = c := by calc c ^ 10 = c ^ 9 * c := by ring @@ -9035,6 +8979,22 @@ theorem weighted_frobenius_word (x : G) (r : Nat) congr 1 simp [Nat.mul_comm] +/-- A two-axis family formed from one Kummer class is still rank one. Here +`j` is a Frobenius position, `c` is a character or conductor coefficient, +and `w (j,c)` is an arbitrary natural multiplicity. Their entire +multiplicative reciprocity expression retains only one exponent of `x`. -/ +theorem two_axis_frobenius_word (x : G) (s : Finset (Nat × Nat)) + (w : Nat × Nat → Nat) : + (∏ jc ∈ s, (x ^ (2 ^ jc.1 * jc.2)) ^ (w jc)) = + x ^ (∑ jc ∈ s, w jc * 2 ^ jc.1 * jc.2) := by + induction s using Finset.induction_on with + | empty => simp + | @insert jc s hj ih => + rw [Finset.prod_insert hj, Finset.sum_insert hj, ih] + rw [← pow_mul, ← pow_add] + congr 1 + simp only [Nat.mul_comm, Nat.mul_left_comm] + /-- Raising a weighted Frobenius word to an Euler-test exponent evaluates the same word on the selected power-residue class `x ^ d`. -/ theorem weighted_frobenius_euler (x : G) (r d : Nat) @@ -9046,6 +9006,45 @@ theorem weighted_frobenius_euler (x : G) (r d : Nat) congr 1 exact Nat.mul_comm _ _ +/-- The oriented prefix of the absolute-Frobenius orbit of a marked phase. +Unlike a complete orbit product, this retains a starting point. The +definition by itself does not evaluate that selected starting phase. -/ +def frobeniusPrefix (x : G) (m : Nat) : G := + ∏ i ∈ Finset.range m, x ^ (2 ^ i) + +/-- An oriented Frobenius prefix is the geometric-series power of its marked +starting phase. This specializes `weighted_frobenius_word` to the unweighted +consecutive word. -/ +theorem frobeniusPrefix_eq_pow (x : G) (m : Nat) : + frobeniusPrefix x m = x ^ (2 ^ m - 1) := by + induction m with + | zero => simp [frobeniusPrefix] + | succ m ih => + rw [frobeniusPrefix, Finset.prod_range_succ] + change frobeniusPrefix x m * x ^ (2 ^ m) = _ + rw [ih, ← pow_add, pow_succ] + congr 1 + have hpos : 0 < 2 ^ m := pow_pos (by decide) m + omega + +/-- Oriented prefixes obey the Frobenius cocycle law. A recursive +Conway-ancestry formula could therefore evaluate a long prefix block by +block, but this identity alone supplies no value for its marked phase. -/ +theorem frobeniusPrefix_add (x : G) (m n : Nat) : + frobeniusPrefix x (m + n) = + frobeniusPrefix x m * (frobeniusPrefix x n) ^ (2 ^ m) := by + rw [frobeniusPrefix_eq_pow, frobeniusPrefix_eq_pow, + frobeniusPrefix_eq_pow, ← pow_mul, ← pow_add] + congr 1 + rw [pow_add] + have hm : 0 < 2 ^ m := pow_pos (by decide) m + have hn : 0 < 2 ^ n := pow_pos (by decide) n + rw [Nat.mul_sub_right_distrib, Nat.mul_comm (2 ^ n) (2 ^ m)] + simp only [one_mul] + have hle : 2 ^ m ≤ 2 ^ m * 2 ^ n := by + simpa using Nat.mul_le_mul_left (2 ^ m) hn + omega + end WeightedFrobeniusWords section CubicChosenRootAncestry @@ -9117,26 +9116,63 @@ theorem weighted_frobenius_euler_eq_one_iff (x : G) (r d ell : Nat) rw [weighted_frobenius_euler] exact pow_eq_one_iff_of_coprime (x ^ d) hell hcop -/-- Torsion classes of coprime exponents cannot cancel in one -multiplicative reciprocity product. This is the abstract group core of the -paper's C/D Kummer-field separation: a product relation can be trivial only -when its two primary coordinates are separately trivial. -/ -theorem coprime_torsion_product_eq_one_iff - (x y : G) {a b : Nat} - (hx : x ^ a = 1) (hy : y ^ b = 1) (hab : a.Coprime b) : - x * y = 1 ↔ x = 1 ∧ y = 1 := by - constructor - · intro hxy - have hxb : x ^ b = 1 := by - rw [eq_inv_of_mul_eq_one_left hxy] - simp [hy] - have hxone : x = 1 := - (pow_eq_one_iff_of_coprime x hx hab).mp hxb - constructor - · exact hxone - · simpa [hxone] using hxy - · rintro ⟨rfl, rfl⟩ - simp +/-- The two-axis reciprocity family from one Kummer class detects no more +than that one class: when its single evaluated exponent is a unit modulo +`ell`, its product is one exactly when the marked `ell`-torsion phase is one. +Thus adding a second indexing axis cannot manufacture an independent +selected coordinate. -/ +theorem two_axis_frobenius_word_eq_one_iff (x : G) (ell : Nat) + (s : Finset (Nat × Nat)) (w : Nat × Nat → Nat) + (hell : x ^ ell = 1) + (hcop : ell.Coprime (∑ jc ∈ s, w jc * 2 ^ jc.1 * jc.2)) : + (∏ jc ∈ s, (x ^ (2 ^ jc.1 * jc.2)) ^ (w jc)) = 1 ↔ x = 1 := by + rw [two_axis_frobenius_word] + exact pow_eq_one_iff_of_coprime x hell hcop + +/-- A prefix whose geometric-series exponent is a unit modulo `ell` +faithfully detects an `ell`-torsion marked phase. This repackages, rather +than proves, the selected nonvanishing required by the excess arms. -/ +theorem frobeniusPrefix_eq_one_iff (x : G) (ell m : Nat) + (hell : x ^ ell = 1) (hcop : ell.Coprime (2 ^ m - 1)) : + frobeniusPrefix x m = 1 ↔ x = 1 := by + rw [frobeniusPrefix_eq_pow] + exact pow_eq_one_iff_of_coprime x hell hcop + +/-- Once the residue degree closes, the complete absolute-Frobenius prefix +product is forced to be one. Consequently a full-orbit reciprocity product +cannot decide whether the marked phase itself is trivial. -/ +theorem frobeniusPrefix_eq_one_of_period (x : G) (ell E : Nat) + (hell : x ^ ell = 1) (hperiod : ell ∣ 2 ^ E - 1) : + frobeniusPrefix x E = 1 := by + rw [frobeniusPrefix_eq_pow] + exact orderOf_dvd_iff_pow_eq_one.mp + (dvd_trans (orderOf_dvd_of_pow_eq_one hell) hperiod) + +/-- At an even residue degree, if half-Frobenius is inversion, the oriented +half-prefix is the inverse square of the marked phase. For odd `ell` this is +a faithful re-encoding, not an evaluation, of that phase. -/ +theorem frobeniusPrefix_half_eq_inv_sq (x : G) (m : Nat) + (hhalf : x ^ (2 ^ m) = x⁻¹) : + frobeniusPrefix x m = x⁻¹ * x⁻¹ := by + rw [frobeniusPrefix_eq_pow] + have hpos : 0 < 2 ^ m := pow_pos (by decide) m + calc + x ^ (2 ^ m - 1) = x ^ (2 ^ m) * x⁻¹ := by + rw [show 2 ^ m = (2 ^ m - 1) + 1 by omega, pow_succ] + simp + _ = x⁻¹ * x⁻¹ := by rw [hhalf] + +/-- A homomorphism cannot transport a nontrivial torsion point between +coprime annihilators. Since adjacent Fermat numbers are coprime, this is the +group-theoretic obstruction to interpreting the deterministic singleton-even +resultant ancestry as an isogeny division tower. -/ +theorem monoidHom_eq_one_of_coprime_torsion + {H : Type*} [CommGroup H] + (f : G →* H) (x : G) (a b : Nat) + (hx : x ^ a = 1) (hy : (f x) ^ b = 1) (hab : a.Coprime b) : + f x = 1 := by + apply (pow_eq_one_iff_of_coprime (f x) ?_ hab).mp hy + rw [← map_pow, hx, map_one] end CoprimePowerDetection @@ -9255,15 +9291,6 @@ section RelativeTraceAxes variable {K : Type*} [Field K] [CharP K 2] -/-- Additivity of the Artin--Schreier map in characteristic two. This is -the algebraic identity behind the centered relative-trace collision in the -singleton-even arm. -/ -theorem artinSchreier_add (x y : K) : - (x ^ 2 + x) + (y ^ 2 + y) = (x + y) ^ 2 + (x + y) := by - have h2 : (2 : K) = 0 := CharP.cast_eq_zero K 2 - ring_nf - simp [h2] - omit [CharP K 2] in /-- Centering two transported torus points turns their additive collision coordinate into the Mobius coordinate of their ratio. -/ @@ -9272,11 +9299,11 @@ theorem centered_ratio_identity (alpha beta : K) (hbeta : beta ≠ 0) alpha / (alpha + beta) = (alpha / beta) / (alpha / beta + 1) := by field_simp -/-- The Mobius coordinate `rho / (rho + 1)` is fixed by a field -endomorphism exactly when `rho` is fixed. In the finite-field application -this is the half-field axis of a relative-trace collision. -/ -theorem mobius_fixed_iff (sigma : K →+* K) (rho : K) (hrho : rho ≠ 1) : - sigma (rho / (rho + 1)) = rho / (rho + 1) <-> sigma rho = rho := by +/-- The two denominators used by the centered Mobius coordinate remain +nonzero before and after an injective field endomorphism. -/ +theorem mobius_denominators + (sigma : K →+* K) (rho : K) (hrho : rho ≠ 1) : + rho + 1 ≠ 0 ∧ sigma rho + 1 ≠ 0 := by have hden : rho + 1 ≠ 0 := by intro h apply hrho @@ -9292,6 +9319,14 @@ theorem mobius_fixed_iff (sigma : K →+* K) (rho : K) (hrho : rho ≠ 1) : apply hsigma have : sigma rho = -1 := eq_neg_of_add_eq_zero_left h simpa [CharTwo.neg_eq] using this + exact ⟨hden, hsden⟩ + +/-- The Mobius coordinate `rho / (rho + 1)` is fixed by a field +endomorphism exactly when `rho` is fixed. In the finite-field application +this is the half-field axis of a relative-trace collision. -/ +theorem mobius_fixed_iff (sigma : K →+* K) (rho : K) (hrho : rho ≠ 1) : + sigma (rho / (rho + 1)) = rho / (rho + 1) <-> sigma rho = rho := by + obtain ⟨hden, hsden⟩ := mobius_denominators sigma rho hrho rw [map_div₀, map_add, map_one] constructor · intro h @@ -9306,21 +9341,7 @@ has norm one. -/ theorem mobius_mate_iff (sigma : K →+* K) (rho : K) (hrho : rho ≠ 1) : sigma (rho / (rho + 1)) = rho / (rho + 1) + 1 <-> sigma rho * rho = 1 := by - have hden : rho + 1 ≠ 0 := by - intro h - apply hrho - have : rho = -1 := eq_neg_of_add_eq_zero_left h - simpa [CharTwo.neg_eq] using this - have hsigma : sigma rho ≠ 1 := by - intro h - apply hrho - apply sigma.injective - simpa using h - have hsden : sigma rho + 1 ≠ 0 := by - intro h - apply hsigma - have : sigma rho = -1 := eq_neg_of_add_eq_zero_left h - simpa [CharTwo.neg_eq] using this + obtain ⟨hden, hsden⟩ := mobius_denominators sigma rho hrho have hmate : rho / (rho + 1) + 1 = 1 / (rho + 1) := by field_simp [hden] rw [← add_assoc, CharTwo.add_self_eq_zero, zero_add] @@ -9922,6 +9943,267 @@ theorem marked_eigenvalue_must_match chi = omega := by exact mul_right_cancel₀ hmarked hcompat +/-! An ordinary conjugacy class cannot retain the translation coordinate of +an affine Frobenius lift. The number-field realization of the affine Galois +group remains paper-level; the following identities check the exact algebraic +obstruction. -/ + +/-- Conjugating `x ↦ q*x + t` by the translation `x ↦ x+a` changes its +translation coordinate from `t` to `t + (1-q)*a`. -/ +theorem affine_translation_conjugation + {F : Type*} [Field F] (q t a x : F) : + a + (q * (x - a) + t) = q * x + (t + (1 - q) * a) := by + ring + +/-- Away from the identity scalar, translation conjugacy can gauge every +affine Frobenius coordinate to zero. -/ +theorem affine_translation_coordinate_gauge + {F : Type*} [Field F] (q t : F) (hq : q ≠ 1) : + ∃ a : F, t + (1 - q) * a = 0 := by + refine ⟨t / (q - 1), ?_⟩ + field_simp + ring + +/-- Therefore every conjugacy-invariant function on affine lifts with fixed +nonidentity scalar part is blind to the selected translation coordinate. -/ +theorem affine_class_function_blind + {F S : Type*} [Field F] + (value : F → S) (q : F) (hq : q ≠ 1) + (hconj : ∀ t a, value t = value (t + (1 - q) * a)) : + ∀ t, value t = value 0 := by + intro t + obtain ⟨a, ha⟩ := affine_translation_coordinate_gauge q t hq + rw [hconj t a, ha] + +/-- Fourier line attached to an additive character on the labeled affine +torsor. -/ +def affineFourierLine + {F M : Type*} [Field F] [CommGroup M] + (psi : AddChar F M) (r y : F) : M := + psi (-r * y) + +/-- Pulling a Fourier line through `x ↦ q*x+t` exposes the translation as +one frame-dependent off-diagonal phase instead of averaging it into a class +function. -/ +theorem affineFourierLine_pullback + {F M : Type*} [Field F] [CommGroup M] + (psi : AddChar F M) (t q r y : F) : + affineFourierLine psi r (q⁻¹ * (y - t)) = + psi (r * q⁻¹ * t) * affineFourierLine psi (r * q⁻¹) y := by + unfold affineFourierLine + rw [show -r * (q⁻¹ * (y - t)) = + r * q⁻¹ * t + -(r * q⁻¹) * y by ring] + exact AddChar.map_add_eq_mul psi _ _ + +/-- On the line indexed by the scalar part itself, the pullback coefficient is +the translation phase in the chosen affine frame. It is not gauge invariant; +the closed-path holonomy below is the intrinsic selected phase. -/ +theorem affineFourierLine_gauge_coefficient + {F M : Type*} [Field F] [CommGroup M] + (psi : AddChar F M) (t q y : F) (hq : q ≠ 0) : + affineFourierLine psi q (q⁻¹ * (y - t)) = + psi t * affineFourierLine psi 1 y := by + rw [affineFourierLine_pullback] + have hcancel : q * q⁻¹ = 1 := mul_inv_cancel₀ hq + rw [hcancel] + simp [affineFourierLine] + +/-- Every additive affine cocycle on a commutative multiplier group is a +single coboundary as soon as one nonidentity multiplier is fixed. -/ +theorem affine_cocycle_is_coboundary + {F : Type*} [Field F] + (t : Fˣ → F) + (hcocycle : ∀ q r, t (q * r) = t q + (q : F) * t r) + (q₀ : Fˣ) (hq₀ : (q₀ : F) ≠ 1) : + ∀ q, t q = (1 - (q : F)) * (t q₀ / (1 - (q₀ : F))) := by + intro q + have hcomm : + t q + (q : F) * t q₀ = + t q₀ + (q₀ : F) * t q := by + rw [← hcocycle q q₀, mul_comm, hcocycle] + have hden : (1 : F) - (q₀ : F) ≠ 0 := sub_ne_zero.mpr (Ne.symm hq₀) + field_simp [hden] + linear_combination hcomm + +/-- Conversely, every marked affine origin produces a cocycle satisfying all +composition laws. -/ +theorem affine_coboundary_is_cocycle + {F : Type*} [Field F] (a : F) : + ∀ q r : Fˣ, + (1 - ((q * r : Fˣ) : F)) * a = + (1 - (q : F)) * a + (q : F) * ((1 - (r : F)) * a) := by + intro q r + push_cast + ring + +/-- At any fixed nonidentity multiplier the cocycle value remains arbitrary: +an appropriate marked origin realizes every field value. -/ +theorem affine_cocycle_value_surjective + {F : Type*} [Field F] (q : Fˣ) (hq : (q : F) ≠ 1) (u : F) : + ∃ a : F, (1 - (q : F)) * a = u := by + refine ⟨u / (1 - (q : F)), ?_⟩ + have hden : (1 : F) - (q : F) ≠ 0 := sub_ne_zero.mpr (Ne.symm hq) + field_simp [hden] + +/-- Translation accumulated by the first `n` arrows +`x ↦ q*x + t_j` in a labeled affine groupoid. -/ +def affinePathTranslation {F : Type*} [Field F] + (q : F) (t : Nat → F) : Nat → F + | 0 => 0 + | n + 1 => q * affinePathTranslation q t n + t n + +/-- The recursive path translation is the oriented weighted sum of its +one-step translations. -/ +theorem affinePathTranslation_eq_sum + {F : Type*} [Field F] (q : F) (t : Nat → F) (n : Nat) : + affinePathTranslation q t n = + ∑ j ∈ Finset.range n, q ^ (n - 1 - j) * t j := by + induction n with + | zero => simp [affinePathTranslation] + | succ n ih => + rw [affinePathTranslation, ih, Finset.sum_range_succ] + rw [Finset.mul_sum] + congr 1 + · apply Finset.sum_congr rfl + intro j hj + have hjlt : j < n := Finset.mem_range.mp hj + have hexp : n + 1 - 1 - j = (n - 1 - j) + 1 := by omega + rw [hexp, pow_succ'] + ring + · simp + +/-- Changing the Kummer-root origin at vertex `j` by `a_j` changes the +one-step arrow by `q*a_j-a_(j+1)`, and these changes telescope along a path. -/ +theorem affinePathTranslation_gauge + {F : Type*} [Field F] (q : F) (t a : Nat → F) (n : Nat) : + affinePathTranslation q + (fun j => t j + q * a j - a (j + 1)) n = + affinePathTranslation q t n + q ^ n * a 0 - a n := by + induction n with + | zero => simp [affinePathTranslation] + | succ n ih => + simp only [affinePathTranslation, ih] + rw [pow_succ] + ring + +/-- Around a closed `E`-step Frobenius orbit, where `q^E=1` and the terminal +root origin is the initial one, the accumulated pure translation is gauge +invariant. -/ +theorem affineCycleHolonomy_gauge_invariant + {F : Type*} [Field F] (q : F) (t a : Nat → F) (E : Nat) + (hqE : q ^ E = 1) (haE : a E = a 0) : + affinePathTranslation q + (fun j => t j + q * a j - a (j + 1)) E = + affinePathTranslation q t E := by + rw [affinePathTranslation_gauge, hqE, haE] + ring + +/-- If all one-step arrows are powers of one affine lift, their full-cycle +translation is zero; a nonzero Artin phase therefore requires true groupoid +holonomy rather than a single affine complement. -/ +theorem affineCycle_constant_translation_zero + {F : Type*} [Field F] (q t : F) (E : Nat) + (hqE : q ^ E = 1) (hq : q ≠ 1) : + affinePathTranslation q (fun _ => t) E = 0 := by + let a := t / (q - 1) + have hden : q - 1 ≠ 0 := sub_ne_zero.mpr hq + have ha : q * a - a = t := by + dsimp [a] + field_simp [hden] + have hgauge := affineCycleHolonomy_gauge_invariant q + (fun _ => 0) (fun _ => a) E hqE rfl + simp only [zero_add, ha] at hgauge + have hzero : ∀ n, affinePathTranslation q (fun _ => 0) n = 0 := by + intro n + induction n with + | zero => rfl + | succ n ih => simp [affinePathTranslation, ih] + exact hgauge.trans (hzero E) + +/-- A closed Frobenius holonomy is exactly the finite-field Euler/Kummer +phase of its radicand. -/ +theorem kummer_holonomy_eq_euler_phase + {F : Type*} [Field F] (alpha beta zeta : F) (ell Q : Nat) + (hQ : 1 ≤ Q) (hdiv : ell ∣ Q - 1) (halpha : alpha ≠ 0) + (hrad : alpha ^ ell = beta) + (hhol : alpha ^ Q = zeta * alpha) : + beta ^ ((Q - 1) / ell) = zeta := by + have hphase : alpha ^ (Q - 1) = zeta := by + apply mul_right_cancel₀ halpha + calc + alpha ^ (Q - 1) * alpha = alpha ^ Q := by + rw [← pow_succ] + congr 1 + omega + _ = zeta * alpha := hhol + calc + beta ^ ((Q - 1) / ell) = + (alpha ^ ell) ^ ((Q - 1) / ell) := by rw [hrad] + _ = alpha ^ (ell * ((Q - 1) / ell)) := by rw [pow_mul] + _ = alpha ^ (Q - 1) := by rw [Nat.mul_div_cancel' hdiv] + _ = zeta := hphase + +/-- Along an open path every one-step translation can be normalized to zero; +the obstruction is precisely the endpoint mismatch, equal to the holonomy. -/ +theorem affinePath_prefix_normalization + {F : Type*} [Field F] (q : F) (t : Nat → F) : + ∀ j, t j + q * affinePathTranslation q t j - + affinePathTranslation q t (j + 1) = 0 := by + intro j + simp [affinePathTranslation] + ring + +/-- Every field value occurs as the holonomy of an `E`-edge cycle once +`E>0`; all proper edges may be chosen trivial and the value put on the closing +edge. Local prefix data therefore do not constrain the Artin phase. -/ +theorem affineCycleHolonomy_surjective + {F : Type*} [Field F] (q H : F) (E : Nat) (hE : 0 < E) : + ∃ t : Nat → F, affinePathTranslation q t E = H := by + obtain ⟨n, rfl⟩ := Nat.exists_eq_succ_of_ne_zero (Nat.ne_of_gt hE) + refine ⟨fun j => if j = n then H else 0, ?_⟩ + rw [affinePathTranslation_eq_sum] + rw [Finset.sum_eq_single n] + · simp + · intro b hb hbn + simp [hbn] + · simp + +/-- Scaling every arrow scales the path holonomy. -/ +theorem affinePathTranslation_scale + {F : Type*} [Field F] (q lambda : F) (t : Nat → F) (n : Nat) : + affinePathTranslation q (fun j => lambda * t j) n = + lambda * affinePathTranslation q t n := by + induction n with + | zero => simp [affinePathTranslation] + | succ n ih => + simp only [affinePathTranslation, ih] + ring + +/-- An affine map between two Frobenius-torsor paths transports holonomy by +its linear part plus only an endpoint term. -/ +theorem affinePath_intertwiner_holonomy + {F : Type*} [Field F] (q lambda : F) (t b : Nat → F) (n : Nat) : + affinePathTranslation q + (fun j => lambda * t j + b (j + 1) - q * b j) n = + lambda * affinePathTranslation q t n + b n - q ^ n * b 0 := by + rw [show (fun j => lambda * t j + b (j + 1) - q * b j) = + (fun j => lambda * t j + q * (-b j) - (-b (j + 1))) by + funext j + ring] + rw [affinePathTranslation_gauge, affinePathTranslation_scale] + ring + +/-- On closed Frobenius cycles, all affine-origin terms telescope, so an +affine ancestry map can only scale the intrinsic Artin phase. -/ +theorem affineCycle_intertwiner_holonomy + {F : Type*} [Field F] (q lambda : F) (t b : Nat → F) (E : Nat) + (hqE : q ^ E = 1) (hbE : b E = b 0) : + affinePathTranslation q + (fun j => lambda * t j + b (j + 1) - q * b j) E = + lambda * affinePathTranslation q t E := by + rw [affinePath_intertwiner_holonomy, hqE, hbE] + ring + end GlobalSplitRayReduction section FermatAdditiveCrossLevel diff --git a/formal/Ogdoad/Fifo.lean b/formal/Ogdoad/Fifo.lean index ac0a49af..f7bd0a2e 100644 --- a/formal/Ogdoad/Fifo.lean +++ b/formal/Ogdoad/Fifo.lean @@ -1,4 +1,4 @@ -import Mathlib +import Ogdoad.Algebra.ZModTwo /-! # The FIFO linking theorem @@ -68,21 +68,6 @@ def adjacencyBit (G : SimpleGraph V) (u v : V) : ZMod 2 := by classical exact if G.Adj u v then 1 else 0 -/-- A nonzero parity bit is the unit bit. -/ -theorem zmod2_eq_one_of_ne_zero (x : ZMod 2) (hx : x ≠ 0) : x = 1 := by - apply ZMod.val_injective - have hxval : x.val ≠ 0 := by - intro h - exact hx ((ZMod.val_eq_zero x).mp h) - have hxlt : x.val < 2 := x.val_lt - change x.val = 1 - omega - -/-- A parity bit different from the unit bit is zero. -/ -theorem zmod2_eq_zero_of_ne_one (x : ZMod 2) (hx : x ≠ 1) : x = 0 := by - by_contra hx0 - exact hx (zmod2_eq_one_of_ne_zero x hx0) - omit [Fintype V] [DecidableEq V] in /-- The adjacency bit inherits symmetry from a simple graph. -/ theorem adjacencyBit_comm (G : SimpleGraph V) (x y : V) : @@ -1838,30 +1823,6 @@ theorem StoppedBadPair.child_of_zero_open exact (Finset.notMem_erase y (S.erase x)) hz exact ⟨hyErase, hzErase, hyz, hwinchild⟩ -/-- Every scalar in `ZMod 2` is idempotent. -/ -theorem zmod2_sq_eq_self (t : ZMod 2) : t * t = t := by - by_cases ht : t = 0 - · simp [ht] - · rw [zmod2_eq_one_of_ne_zero t ht] - simp - -/-- Distinct parity bits differ by the unit bit, so the affine equality -indicator `x + 1 + y` vanishes. -/ -theorem zmod2_add_one_add_eq_zero_of_ne (x y : ZMod 2) (hxy : x ≠ y) : - x + 1 + y = 0 := by - have hsum0 : x + y ≠ 0 := by - intro h - have : x = y := by - calc - x = x + (y + y) := by rw [CharTwo.add_self_eq_zero, add_zero] - _ = (x + y) + y := by abel - _ = y := by rw [h, zero_add] - exact hxy this - have hsum1 : x + y = 1 := zmod2_eq_one_of_ne_zero _ hsum0 - calc - x + 1 + y = (x + y) + 1 := by abel - _ = 0 := by rw [hsum1, CharTwo.add_self_eq_zero] - omit [Fintype V] [DecidableEq V] in /-- Parity of an empty equality fibre. This is the abstract algebra behind the stopped bad-pair moment identity. -/ diff --git a/formal/Ogdoad/FifoFrontier.lean b/formal/Ogdoad/FifoFrontier.lean new file mode 100644 index 00000000..d9506802 --- /dev/null +++ b/formal/Ogdoad/FifoFrontier.lean @@ -0,0 +1,89 @@ +import Ogdoad.Fifo +import Ogdoad.FifoCausal +import Ogdoad.FifoSymmetry +import Ogdoad.FifoNormalization +import Ogdoad.FifoStrategy +import Ogdoad.FifoAffine +import Ogdoad.FifoCrossDescent +import Ogdoad.FifoCrossClose +import Ogdoad.FifoMixedCross +import Ogdoad.FifoSameOpenBraid +import Ogdoad.FifoDummyExitCarrier +import Ogdoad.FifoCrossExitIncidence +import Ogdoad.FifoOuterFan +import Ogdoad.FifoProtectedFan +import Ogdoad.FifoInterlace +import Ogdoad.FifoDummyDeletion +import Ogdoad.FifoRootSelector +import Ogdoad.FifoNeutralPair +import Ogdoad.FifoMinHotCurvature +import Ogdoad.FifoHub +import Ogdoad.FifoEmptyQueue +import Ogdoad.FifoTreeTrace +import Ogdoad.FifoBlockInduction +import Ogdoad.FifoOutcome +import Ogdoad.FifoOutcomeBlock +import Ogdoad.FifoOutcomeSwitch +import Ogdoad.FifoPairState +import Ogdoad.FifoStrategyBadAncestry +import Ogdoad.FifoStrategyBadAncestryClear +import Ogdoad.FifoFirstSeatRoot +import Ogdoad.FifoBadArcCycle +import Ogdoad.FifoFirstSeatStrategy +import Ogdoad.FifoFunctionalDigraphBoundary +import Ogdoad.FifoDummyFront +import Ogdoad.FifoDummyFrontAffine +import Ogdoad.FifoSeparatorFlow +import Ogdoad.FifoThreeSiblingBoundary +import Ogdoad.FifoRootCongruence +import Ogdoad.FifoBlockReversalBoundary +import Ogdoad.FifoCanonicalPositionalOdd +import Ogdoad.FifoCellSwapOutcomeBoundary +import Ogdoad.FifoCommonDummyEventBoundary +import Ogdoad.FifoCongruenceOutcomeBoundary +import Ogdoad.FifoConsumedDummyBoundary +import Ogdoad.FifoConsumedDummyShearBoundary +import Ogdoad.FifoControlledDivergence +import Ogdoad.FifoDistinctOpenForkBoundary +import Ogdoad.FifoGaussianElimination +import Ogdoad.FifoGlobalSpliceBoundary +import Ogdoad.FifoLastChargedCloseBoundary +import Ogdoad.FifoLiveDummyOpenFork +import Ogdoad.FifoOddSpikeDummyReply +import Ogdoad.FifoOddSpikeFactor +import Ogdoad.FifoPairGaussian +import Ogdoad.FifoPairZeroMomentAdjacentBoundary +import Ogdoad.FifoPairZeroMomentNormal +import Ogdoad.FifoPairZeroMomentSafety +import Ogdoad.FifoParityControlledRoot +import Ogdoad.FifoParityCounterNormal +import Ogdoad.FifoParitySeat +import Ogdoad.FifoParitySeatCloseFirst +import Ogdoad.FifoParitySeatDeviationBoundary +import Ogdoad.FifoPositionalSelectedEdgeBoundary +import Ogdoad.FifoPositionalStateDAGBoundary +import Ogdoad.FifoPrivateLeafBoundary +import Ogdoad.FifoProtectedBlockBoundary +import Ogdoad.FifoProtectedFactorBoundary +import Ogdoad.FifoPublicPolicyAffine +import Ogdoad.FifoPublicPolicyDuality +import Ogdoad.FifoPublicPolicyTopologyBoundary +import Ogdoad.FifoPublicPrefixQueueCutBoundary +import Ogdoad.FifoPublicQueueCutCounterexample +import Ogdoad.FifoPublicSeparatorAncestry +import Ogdoad.FifoPublicSeparatorAutomaton +import Ogdoad.FifoPublicSeparatorQueueDebt +import Ogdoad.FifoRootGadgetBoundary +import Ogdoad.FifoSeparatorBadSynchronizationBoundary +import Ogdoad.FifoSeparatorControlledBridgeBoundary +import Ogdoad.FifoSingletonForkBoundary +import Ogdoad.FifoMatching + +/-! +# Unified arbitrary-graph FIFO frontier + +This is the one import surface for the complete Lean-checked FIFO development +used by `writeups/linking_affine.tex`. The individual files remain small proof +modules; other papers should import only the specific solved components they +need, such as `FifoMatching`, rather than this entire open frontier. +-/ diff --git a/formal/Ogdoad/FifoNeutralPair.lean b/formal/Ogdoad/FifoNeutralPair.lean index 47981495..392fc4b1 100644 --- a/formal/Ogdoad/FifoNeutralPair.lean +++ b/formal/Ogdoad/FifoNeutralPair.lean @@ -34,7 +34,7 @@ variable {V : Type*} [Fintype V] [DecidableEq V] /-! ## Fixed-policy nodes and charged-close extraction -/ omit [Fintype V] in -/-- The legacy `InOddStrategy` relation in `Fifo.lean` is indexed by an +/-- The `InOddStrategy` relation in `Fifo.lean` is indexed by an `OddWins` proof and hence cannot distinguish two policies at the same state. -/ theorem inOddStrategy_proof_irrelevant {G : SimpleGraph V} {seat : Bool} {s t : State V} @@ -45,7 +45,7 @@ theorem inOddStrategy_proof_irrelevant rfl omit [Fintype V] in -/-- The same collapse applies to the legacy CLOSE-first certificate: it can +/-- The same collapse applies to the CLOSE-first certificate: it can state that such a witness exists at the public state, but not that a supplied `OddWins` proof denotes that particular policy. -/ theorem oddStrategyCloseFirst_proof_irrelevant @@ -57,7 +57,7 @@ theorem oddStrategyCloseFirst_proof_irrelevant rfl omit [Fintype V] in -/-- Likewise, legacy clear-deviation membership is a public-state existence +/-- Likewise, clear-deviation membership is a public-state existence claim rather than membership in a distinguishable fixed policy. -/ theorem oddStrategyHasClearDeviation_proof_irrelevant {G : SimpleGraph V} {seat : Bool} {s : State V} diff --git a/formal/Ogdoad/FifoPairZeroMomentAdjacentBoundary.lean b/formal/Ogdoad/FifoPairZeroMomentAdjacentBoundary.lean index ebd67179..8f9012c5 100644 --- a/formal/Ogdoad/FifoPairZeroMomentAdjacentBoundary.lean +++ b/formal/Ogdoad/FifoPairZeroMomentAdjacentBoundary.lean @@ -692,9 +692,9 @@ theorem zeroPairDummyStar_response_modes : all_goals decide · rfl -/-- The dummy repairs the formerly losing two-CLOSE branch by leaving an -even four-vertex residual root containing the dummy. On the star without -the dummy, the same word leaves an odd three-vertex residual root and is +/-- The dummy makes the two-CLOSE branch even-winning by leaving an even +four-vertex residual root containing the dummy. On the star without the +dummy, the same word leaves an odd three-vertex residual root and is odd-winning. -/ theorem zeroPairStar_dummy_repairs_close_branch : zeroPairStarAfterTwoCloses.untouched.card = 3 ∧ diff --git a/formal/Ogdoad/GoldArena.lean b/formal/Ogdoad/GoldArena.lean index 027ec026..70d38ff2 100644 --- a/formal/Ogdoad/GoldArena.lean +++ b/formal/Ogdoad/GoldArena.lean @@ -32,8 +32,6 @@ open Ogdoad.Fifo set_option linter.unusedSectionVars false set_option linter.unusedSimpArgs false -abbrev F2 := ZMod 2 - variable {V J : Type*} [AddCommGroup V] [Module F2 V] [FiniteDimensional F2 V] [Fintype J] [LinearOrder J] @@ -104,7 +102,7 @@ theorem diagonalSource_eq_support_sum (Q : QuadraticForm F2 V) apply Finset.sum_congr rfl intro j hj have hne : b.repr x j ≠ 0 := Finsupp.mem_support_iff.mp hj - have hone : b.repr x j = 1 := Ogdoad.Fifo.zmod2_eq_one_of_ne_zero _ hne + have hone : b.repr x j = 1 := Ogdoad.zmod2_eq_one_of_ne_zero _ hne simp [hone] /-- Two refinements with the same polar form differ by exactly their public @@ -328,7 +326,7 @@ def chargeBool (z : F2) : Bool := decide (z = 1) theorem chargeBool_eq_false_iff (z : F2) : chargeBool z = false ↔ z = 0 := by by_cases hz : z = 1 · simp [chargeBool, hz] - · have : z = 0 := Ogdoad.Fifo.zmod2_eq_zero_of_ne_one z hz + · have : z = 0 := Ogdoad.zmod2_eq_zero_of_ne_one z hz simp [chargeBool, this] /-- Deferred compiler endpoint. The types, frame, loading, public matching, @@ -548,7 +546,7 @@ theorem sourceCharge_eq_ordered_edges (Q : QuadraticForm F2 V) (original : Basis simp_rw [Fin.sum_univ_two] by_cases hQ : Q (original i) = 1 · simp [hQ] - · have hzero := Ogdoad.Fifo.zmod2_eq_zero_of_ne_one _ hQ + · have hzero := Ogdoad.zmod2_eq_zero_of_ne_one _ hQ simp [hQ, hzero] set_option maxHeartbeats 800000 in diff --git a/formal/Ogdoad/GoldBlockCompression.lean b/formal/Ogdoad/GoldBlockCompression.lean index cfbc7239..1a36809e 100644 --- a/formal/Ogdoad/GoldBlockCompression.lean +++ b/formal/Ogdoad/GoldBlockCompression.lean @@ -1,3 +1,4 @@ +import Ogdoad.Algebra.ZModTwo import Mathlib.LinearAlgebra.QuadraticForm.Basic import Mathlib.LinearAlgebra.LinearIndependent.Basic @@ -23,8 +24,6 @@ open scoped BigOperators namespace Ogdoad.GoldBlock -abbrev F2 := ZMod 2 - variable {M : Type*} [AddCommGroup M] [Module F2 M] /-- Over an `F₂`-valued target the polar form is alternating. -/ diff --git a/formal/Ogdoad/GoldDiagonal.lean b/formal/Ogdoad/GoldDiagonal.lean index c76acb29..34ed3178 100644 --- a/formal/Ogdoad/GoldDiagonal.lean +++ b/formal/Ogdoad/GoldDiagonal.lean @@ -10,10 +10,11 @@ load-bearing facts used by the construction: * if the quadratic conjugation sends `u` to `u + 1`, the trace pairing on the lower and upper basis blocks recovers `B` and `A + B` from `A + u*B`; and -* an Artin--Schreier source in the lower field lifts through the tower generator; - and -* over every finite characteristic-two field, the Artin--Schreier image is - exactly the kernel of absolute trace. +* elements from the base of a quadratic extension have zero absolute trace + upstairs. + +The reusable Artin--Schreier exact sequence and tower-source algebra live in +`Ogdoad.Algebra.ArtinSchreier` rather than in this Gold-specific module. No custom axiom supplies the final source. -/ @@ -91,160 +92,4 @@ theorem absolute_trace_of_quadratic_base_zero end AbsoluteTrace -section FiniteFieldArtinSchreier - -/-- The Artin--Schreier endomorphism of a finite characteristic-two field, -viewed as an `F_2`-linear map. -/ -noncomputable def artinSchreierLinear - (K : Type*) [Field K] [Finite K] [Algebra (ZMod 2) K] : - K →ₗ[ZMod 2] K := - (FiniteField.frobeniusAlgHom (ZMod 2) K).toLinearMap - LinearMap.id - -@[simp] -theorem artinSchreierLinear_apply - {K : Type*} [Field K] [Finite K] [Algebra (ZMod 2) K] (x : K) : - artinSchreierLinear K x = x ^ 2 + x := by - letI : CharP K 2 := - charP_of_injective_algebraMap (algebraMap (ZMod 2) K).injective 2 - simp [artinSchreierLinear] - -/-- Absolute trace is invariant under the characteristic-two Frobenius. -/ -theorem absoluteTrace_sq_eq_absoluteTrace - {K : Type*} [Field K] [Finite K] [Algebra (ZMod 2) K] - (x : K) : - Algebra.trace (ZMod 2) K (x ^ 2) = Algebra.trace (ZMod 2) K x := by - letI : CharP K 2 := - charP_of_injective_algebraMap (algebraMap (ZMod 2) K).injective 2 - apply (algebraMap (ZMod 2) K).injective - rw [trace_eq_sum_automorphisms (K := ZMod 2) (L := K) (x ^ 2)] - calc - ∑ sigma : Gal(K / ZMod 2), sigma (x ^ 2) = - ∑ sigma : Gal(K / ZMod 2), (sigma x) ^ 2 := by simp - _ = (∑ sigma : Gal(K / ZMod 2), sigma x) ^ 2 := by - rw [sum_pow_char] - _ = (algebraMap (ZMod 2) K (Algebra.trace (ZMod 2) K x)) ^ 2 := by - rw [trace_eq_sum_automorphisms (K := ZMod 2) (L := K) x] - _ = algebraMap (ZMod 2) K (Algebra.trace (ZMod 2) K x) := by - rw [← map_pow, ZMod.pow_card] - -/-- Every Artin--Schreier value has zero absolute trace. -/ -theorem absoluteTrace_artinSchreier_zero - {K : Type*} [Field K] [Finite K] [Algebra (ZMod 2) K] - (x : K) : - Algebra.trace (ZMod 2) K (x ^ 2 + x) = 0 := by - letI : CharP K 2 := - charP_of_injective_algebraMap (algebraMap (ZMod 2) K).injective 2 - rw [map_add, absoluteTrace_sq_eq_absoluteTrace] - exact CharTwo.add_self_eq_zero _ - -/-- The kernel of the Artin--Schreier map is exactly the prime field. -/ -theorem ker_artinSchreierLinear - {K : Type*} [Field K] [Finite K] [Algebra (ZMod 2) K] : - LinearMap.ker (artinSchreierLinear K) = (ZMod 2) ∙ (1 : K) := by - letI : CharP K 2 := - charP_of_injective_algebraMap (algebraMap (ZMod 2) K).injective 2 - ext x - constructor - · intro hx - rw [LinearMap.mem_ker, artinSchreierLinear_apply] at hx - have hfac : x * (x + 1) = 0 := by - calc - x * (x + 1) = x ^ 2 + x := by ring - _ = 0 := hx - rcases mul_eq_zero.mp hfac with hx0 | hx1 - · subst x - exact Submodule.zero_mem _ - · have hxone : x = 1 := by - have := eq_neg_of_add_eq_zero_left hx1 - simpa using this - subst x - exact Submodule.mem_span_singleton_self 1 - · intro hx - rw [Submodule.mem_span_singleton] at hx - obtain ⟨c, rfl⟩ := hx - rw [LinearMap.mem_ker, artinSchreierLinear_apply] - simp only [Algebra.smul_def, mul_one] - rw [← map_pow, ZMod.pow_card] - exact CharTwo.add_self_eq_zero _ - -/-- Rank--nullity and trace surjectivity identify the Artin--Schreier image -with the absolute-trace kernel. -/ -theorem range_artinSchreierLinear_eq_ker_trace - {K : Type*} [Field K] [Finite K] [Algebra (ZMod 2) K] : - LinearMap.range (artinSchreierLinear K) = - LinearMap.ker (Algebra.trace (ZMod 2) K) := by - letI : CharP K 2 := - charP_of_injective_algebraMap (algebraMap (ZMod 2) K).injective 2 - apply Submodule.eq_of_le_of_finrank_eq - · intro y hy - rcases hy with ⟨x, rfl⟩ - rw [LinearMap.mem_ker, artinSchreierLinear_apply] - exact absoluteTrace_artinSchreier_zero x - · have hAS := - LinearMap.finrank_range_add_finrank_ker (artinSchreierLinear K) - rw [ker_artinSchreierLinear, - finrank_span_singleton (by exact one_ne_zero)] at hAS - have htrSurj : Function.Surjective (Algebra.trace (ZMod 2) K) := - Algebra.trace_surjective (ZMod 2) K - have htrRange : LinearMap.range (Algebra.trace (ZMod 2) K) = ⊤ := - LinearMap.range_eq_top.mpr htrSurj - have htr := - LinearMap.finrank_range_add_finrank_ker (Algebra.trace (ZMod 2) K) - rw [htrRange, finrank_top] at htr - norm_num at htr - omega - -/-- The finite-field Artin--Schreier exact sequence, in the exact form used by -the Gold-diagonal theorem. -/ -theorem trace_eq_zero_iff_exists_artinSchreier - {K : Type*} [Field K] [Finite K] [Algebra (ZMod 2) K] - {lambda : K} : - Algebra.trace (ZMod 2) K lambda = 0 ↔ - ∃ w : K, w ^ 2 + w = lambda := by - constructor - · intro htrace - have hmem : lambda ∈ LinearMap.ker (Algebra.trace (ZMod 2) K) := htrace - rw [← range_artinSchreierLinear_eq_ker_trace] at hmem - rcases hmem with ⟨w, hw⟩ - exact ⟨w, by simpa using hw⟩ - · rintro ⟨w, rfl⟩ - exact absoluteTrace_artinSchreier_zero w - -end FiniteFieldArtinSchreier - -section ArtinSchreier - -variable {K : Type*} [CommRing K] [CharP K 2] - -/-- The Artin--Schreier map is additive in characteristic two. -/ -theorem artinSchreier_add (x y : K) : - (x + y) ^ 2 + (x + y) = (x ^ 2 + x) + (y ^ 2 + y) := by - ring_nf - simp - -/-- One quadratic-tower lift of an Artin--Schreier source. If `u^2+u=t`, -`b` is a bit, and `v` sources `lambda+b*t` downstairs, then `v+b*u` sources -`lambda` upstairs. -/ -theorem artinSchreier_tower_lift (u t b v lambda : K) - (hu : u ^ 2 + u = t) (hb : b ^ 2 = b) - (hv : v ^ 2 + v = lambda + b * t) : - (v + b * u) ^ 2 + (v + b * u) = lambda := by - rw [artinSchreier_add, hv] - rw [mul_pow, hb] - calc - lambda + b * t + (b * u ^ 2 + b * u) = - lambda + b * (t + (u ^ 2 + u)) := by ring - _ = lambda := by rw [hu, CharTwo.add_self_eq_zero]; simp - -/-- The two Artin--Schreier sources differ by one. -/ -theorem artinSchreier_companion (w lambda : K) - (hw : w ^ 2 + w = lambda) : - (w + 1) ^ 2 + (w + 1) = lambda := by - rw [artinSchreier_add, hw] - have hone : (1 : K) ^ 2 + 1 = 0 := by - simp - rw [hone, add_zero] - -end ArtinSchreier - end Ogdoad.GoldDiagonal diff --git a/formal/Ogdoad/GoldExtraspecial.lean b/formal/Ogdoad/GoldExtraspecial.lean index 065eda1f..e01afdd2 100644 --- a/formal/Ogdoad/GoldExtraspecial.lean +++ b/formal/Ogdoad/GoldExtraspecial.lean @@ -1,4 +1,4 @@ -import Mathlib.Algebra.Module.ZMod +import Ogdoad.Algebra.ZModTwo /-! # The game-native Gold--Heisenberg extension @@ -25,8 +25,6 @@ set_option linter.unusedSectionVars false namespace Ogdoad.GoldExtraspecial -abbrev F2 := ZMod 2 - @[simp] theorem two_eq_zero : (2 : F2) = 0 := by decide variable {V : Type*} [AddCommGroup V] [Module F2 V] diff --git a/formal/Ogdoad/GoldExtraspecialTrace.lean b/formal/Ogdoad/GoldExtraspecialTrace.lean index 1fac5eed..b3707115 100644 --- a/formal/Ogdoad/GoldExtraspecialTrace.lean +++ b/formal/Ogdoad/GoldExtraspecialTrace.lean @@ -23,8 +23,6 @@ namespace Ogdoad.GoldExtraspecialTrace open Ogdoad.GoldExtraspecial -abbrev F2 := ZMod 2 - variable {K : Type*} [Field K] [Finite K] [CharP K 2] [Algebra F2 K] [FiniteDimensional F2 K] @@ -88,7 +86,6 @@ theorem goldQuadratic_add (a : ℕ) (c x y : K) : goldQuadratic a c (x + y) = goldQuadratic a c x + goldQuadratic a c y + goldPolar a c x y := by rw [← goldHalfPolar_diagonal] - change goldHalfPolar a c (x + y) (x + y) = _ simp only [map_add, AddMonoidHom.add_apply] rw [goldHalfPolar_diagonal, goldHalfPolar_diagonal] have hsymm := goldHalfPolar_symmetrization a c x y diff --git a/formal/Ogdoad/GoldMatchingAlgebra.lean b/formal/Ogdoad/GoldMatchingAlgebra.lean index 570ceb8b..b85c4abd 100644 --- a/formal/Ogdoad/GoldMatchingAlgebra.lean +++ b/formal/Ogdoad/GoldMatchingAlgebra.lean @@ -15,8 +15,6 @@ open scoped BigOperators namespace Ogdoad.GoldMatching -abbrev F2 := ZMod 2 - variable {M : Type*} [AddCommGroup M] [Module F2 M] /-- A quadratic map is additive on a finite orthogonal family. -/ @@ -52,12 +50,12 @@ theorem quadratic_hyperbolic_plane have ha : a * a = a := by by_cases ha0 : a = 0 · simp [ha0] - · have ha1 : a = 1 := Ogdoad.Fifo.zmod2_eq_one_of_ne_zero a ha0 + · have ha1 : a = 1 := Ogdoad.zmod2_eq_one_of_ne_zero a ha0 simp [ha1] have hb : b * b = b := by by_cases hb0 : b = 0 · simp [hb0] - · have hb1 : b = 1 := Ogdoad.Fifo.zmod2_eq_one_of_ne_zero b hb0 + · have hb1 : b = 1 := Ogdoad.zmod2_eq_one_of_ne_zero b hb0 simp [hb1] rw [QuadraticMap.map_add Q, QuadraticMap.map_smul, QuadraticMap.map_smul, QuadraticMap.polar_smul_left, diff --git a/formal/Ogdoad/MisereGrundyObstruction.lean b/formal/Ogdoad/MisereGrundyObstruction.lean new file mode 100644 index 00000000..9ab52696 --- /dev/null +++ b/formal/Ogdoad/MisereGrundyObstruction.lean @@ -0,0 +1,214 @@ +import Ogdoad.MisereTransition +import Mathlib.Combinatorics.Hindman + +/-! +# A finite-trace obstruction for misere Grundy's game + +This module isolates the algebraic consequence of a monochromatic Schur +triple in a hypothetical finite transition trace. If one transition record +`(c, E)` has `c ^ 2` among its option values, then every positive transition +power has the next monoid power among its options. Valid-table loop-freeness +therefore forbids equality of consecutive powers of `c`. + +The combinatorial step producing such a record from a finite Grundy trace is +the distinct-summand form of Schur's theorem. It is derived here from +Mathlib's stronger formalization of Hindman's finite-sums theorem. +-/ + +namespace Ogdoad.MisereGrundyObstruction + +open Set +open Ogdoad.MisereTransition + +variable {Q : Type*} [CommMonoid Q] + +/-- Every finite coloring of the positive natural numbers has a +monochromatic triple `i`, `j`, `i + j` with `0 < i < j`. + +This is the distinct-summand form of Schur's theorem. We derive it from +Mathlib's stronger Hindman theorem: first find a monochromatic finite-sums +stream inside the positive naturals, then take one stream term and a long +enough disjoint block sum. -/ +theorem exists_distinct_summand_monochromatic + {C : Type*} [Finite C] (x : Nat → C) : + ∃ i j : Nat, 0 < i ∧ i < j ∧ x i = x j ∧ x j = x (i + j) := by + let one : Stream' Nat := Stream'.const 1 + let colors : Set (Set Nat) := + Set.range fun c : C ↦ {n : Nat | 0 < n ∧ x n = c} + have hcolors : colors.Finite := Set.finite_range _ + have fs_pos : ∀ (a : Stream' Nat) {m : Nat}, + (∀ k : Nat, 0 < a.get k) → m ∈ Hindman.FS a → 0 < m := by + intro a m ha hm + induction hm with + | head' a => exact ha 0 + | tail' a m hm ih => + apply ih + intro k + simpa using ha (k + 1) + | cons' a m hm ih => exact Nat.add_pos_left (ha 0) m + have hcover : Hindman.FS one ⊆ ⋃₀ colors := by + intro n hn + have hnpos : 0 < n := by + have hone : ∀ k : Nat, 0 < one.get k := by + intro k + simp [one] + exact fs_pos one hone hn + refine Set.mem_sUnion.mpr ⟨{m : Nat | 0 < m ∧ x m = x n}, ?_, hnpos, rfl⟩ + exact ⟨x n, rfl⟩ + obtain ⟨_, ⟨c, rfl⟩, b, hb⟩ := + Hindman.FS_partition_regular one colors hcolors hcover + let i := b.get 0 + have hiFS : i ∈ Hindman.FS b := Hindman.FS.singleton b 0 + have hi : 0 < i ∧ x i = c := hb hiFS + obtain ⟨n, hn⟩ := Hindman.FS.add hiFS + let s := Finset.range (i + 1) + let j := ∑ k ∈ s, (b.drop n).get k + have hs : s.Nonempty := by + simp [s] + have hjDrop : j ∈ Hindman.FS (b.drop n) := + Hindman.FS.finsetSum (b.drop n) s hs + have hjFS : j ∈ Hindman.FS b := + Hindman.FS_iter_tail_sub_FS b n hjDrop + have hijFS : i + j ∈ Hindman.FS b := hn j hjDrop + have hj : 0 < j ∧ x j = c := hb hjFS + have hij : 0 < i + j ∧ x (i + j) = c := hb hijFS + have hi_lt_j : i < j := by + have hterm : ∀ k : Nat, 1 ≤ (b.drop n).get k := by + intro k + have hkDrop : (b.drop n).get k ∈ Hindman.FS (b.drop n) := + Hindman.FS.singleton (b.drop n) k + exact (hb (Hindman.FS_iter_tail_sub_FS b n hkDrop)).1 + have hsum : i + 1 ≤ j := by + calc + i + 1 = ∑ k ∈ s, 1 := by simp [s] + _ ≤ ∑ k ∈ s, (b.drop n).get k := + Finset.sum_le_sum fun k _ ↦ hterm k + _ = j := rfl + omega + exact ⟨i, j, hi.1, hi_lt_j, hi.2.trans hj.2.symm, hj.2.trans hij.2.symm⟩ + +/-- In any finite-valued split trace, some positive-index record contains the +square of its own value among its options. The only game-specific input is +that every unequal split `i < j` contributes the product of the two child +values to the record at `i + j`. -/ +theorem exists_square_option_of_finite_split_trace + [Finite Q] (t : Nat → Transition Q) + (hsplit : ∀ i j : Nat, 0 < i → i < j → + (t i).value * (t j).value ∈ (t (i + j)).options) : + ∃ n : Nat, 0 < n ∧ (t n).value ^ 2 ∈ (t n).options := by + obtain ⟨i, j, hi, hij, hvij, hvsum⟩ := + exists_distinct_summand_monochromatic fun n ↦ (t n).value + refine ⟨i + j, by omega, ?_⟩ + have hopt := hsplit i j hi hij + rw [hvij, hvsum] at hopt + simpa [pow_two] using hopt + +/-- The `(n + 1)`-fold transition product. Indexing positive powers from +zero avoids introducing a separate identity transition into closure proofs. -/ +def positivePower (t : Transition Q) : Nat → Transition Q + | 0 => t + | n + 1 => product (positivePower t n) t + +/-- The value field of the `(n + 1)`-fold transition product is the expected +monoid power. -/ +theorem positivePower_value (c : Q) (E : Set Q) (n : Nat) : + (positivePower (Transition.mk c E) n).value = c ^ (n + 1) := by + induction n with + | zero => simp [positivePower] + | succ n ih => + simp [positivePower, product, ih, pow_succ] + +/-- A closed table contains every positive transition power of each of its +records. -/ +theorem positivePower_mem + {T : Set (Transition Q)} (hclosed : Closed T) + {t : Transition Q} (ht : t ∈ T) (n : Nat) : + positivePower t n ∈ T := by + induction n with + | zero => exact ht + | succ n ih => exact hclosed ih ht + +/-- If `c ^ 2` is an option value of `c`, then the `(n + 1)`-fold transition +power has `c ^ (n + 2)` among its option values. -/ +theorem next_power_mem_positivePower_options + {c : Q} {E : Set Q} (hc2 : c ^ 2 ∈ E) (n : Nat) : + c ^ (n + 2) ∈ (positivePower (Transition.mk c E) n).options := by + induction n with + | zero => simpa [positivePower] using hc2 + | succ n _ => + refine Or.inr ⟨c ^ 2, hc2, ?_⟩ + rw [positivePower_value] + simp only [pow_add] + +/-- **Consecutive-power obstruction.** In a nontrivial valid-table setting, +a record that contains the square of its value among its options forces every +two consecutive positive powers of that value to be distinct. -/ +theorem consecutive_powers_ne + {P : Set Q} {T : Set (Transition Q)} {R : Q → Nat} + (hP : P.Nonempty) (hterminal : Transition.mk 1 ∅ ∈ T) + (hreduced : Reduced P) (hclosed : Closed T) + (hparity : ParityTable P T) (hranked : Ranked T R) + {c : Q} {E : Set Q} (ht : Transition.mk c E ∈ T) + (hc2 : c ^ 2 ∈ E) (n : Nat) : + c ^ (n + 1) ≠ c ^ (n + 2) := by + intro heq + have hpowT : positivePower (Transition.mk c E) n ∈ T := + positivePower_mem hclosed ht n + have hnot := + value_not_mem_own_options hP hterminal hreduced hclosed hparity hranked hpowT + apply hnot + rw [positivePower_value, heq] + exact next_power_mem_positivePower_options hc2 n + +/-- A finite monoid element whose consecutive positive powers are all +distinct has an eventual power period of length at least two. This is the +precise finite-semigroup content needed below; it avoids committing to any +particular library definition of an aperiodic monoid. -/ +theorem finite_eventual_period_at_least_two + [Finite Q] {c : Q} + (hnext : ∀ n : Nat, c ^ (n + 1) ≠ c ^ (n + 2)) : + ∃ m d : Nat, 1 ≤ m ∧ 2 ≤ d ∧ + ∀ k : Nat, c ^ (m + k) = c ^ (m + d + k) := by + obtain ⟨i, j, hij, hpow⟩ := + Finite.exists_ne_map_eq_of_infinite (fun n : Nat ↦ c ^ (n + 1)) + rcases lt_or_gt_of_ne hij with hij | hji + · have hjne : j ≠ i + 1 := by + intro hj + subst j + exact hnext i hpow + refine ⟨i + 1, j - i, by omega, by omega, ?_⟩ + intro k + calc + c ^ (i + 1 + k) = c ^ (i + 1) * c ^ k := by rw [pow_add] + _ = c ^ (j + 1) * c ^ k := by rw [hpow] + _ = c ^ (j + 1 + k) := (pow_add c (j + 1) k).symm + _ = c ^ (i + 1 + (j - i) + k) := by congr 1; omega + · have hine : i ≠ j + 1 := by + intro hi + subst i + exact hnext j hpow.symm + refine ⟨j + 1, i - j, by omega, by omega, ?_⟩ + intro k + calc + c ^ (j + 1 + k) = c ^ (j + 1) * c ^ k := by rw [pow_add] + _ = c ^ (i + 1) * c ^ k := by rw [hpow] + _ = c ^ (i + 1 + k) := (pow_add c (i + 1) k).symm + _ = c ^ (j + 1 + (i - j) + k) := by congr 1; omega + +/-- In a finite valid-table setting, a value whose square is one of its own +option values generates an eventually periodic power sequence of period at +least two. -/ +theorem square_option_forces_nontrivial_period + [Finite Q] + {P : Set Q} {T : Set (Transition Q)} {R : Q → Nat} + (hP : P.Nonempty) (hterminal : Transition.mk 1 ∅ ∈ T) + (hreduced : Reduced P) (hclosed : Closed T) + (hparity : ParityTable P T) (hranked : Ranked T R) + {c : Q} {E : Set Q} (ht : Transition.mk c E ∈ T) + (hc2 : c ^ 2 ∈ E) : + ∃ m d : Nat, 1 ≤ m ∧ 2 ≤ d ∧ + ∀ k : Nat, c ^ (m + k) = c ^ (m + d + k) := by + apply finite_eventual_period_at_least_two + exact consecutive_powers_ne hP hterminal hreduced hclosed hparity hranked ht hc2 + +end Ogdoad.MisereGrundyObstruction diff --git a/formal/Ogdoad/MisereNaturalUniversality.lean b/formal/Ogdoad/MisereNaturalUniversality.lean new file mode 100644 index 00000000..9f3956d6 --- /dev/null +++ b/formal/Ogdoad/MisereNaturalUniversality.lean @@ -0,0 +1,658 @@ +import Ogdoad.MisereOctalCertificate + +/-! +# Finite-exception natural realization of finite misere quotients + +This module proves a universality theorem for a finite-exception heap class +that isolates one obstruction in the octal problem. A heap has one of +finitely many source-local prefix states, followed by a unary +translation-invariant tail. Each prefix heap moves only to an earlier-ranked +prefix heap; every tail heap has the one move to its predecessor. Positions +are finite words of heaps, with a move in one component. + +The class is not a superclass of finite octal games: it permits source-local +prefix moves but restricts the tail to one unary move. The construction does +**not** claim finite-octal universality. An octal digit applies its +subtraction or split to every larger heap, while the exceptional prefix moves +below are source-local. The point of the theorem is to isolate the global +cross-talk created when one tries to encode those prefix moves by octal +digits. +-/ + +namespace Ogdoad.MisereNaturalUniversality + +open Set +open Ogdoad.MisereTransition + +variable {Q : Type*} [CommMonoid Q] + +/-- A nontrivial valid table whose rank has `R 1 = 0` contains a one-move +record `(a,{1})`. -/ +theorem exists_star_transition + {P : Set Q} {T : Set (Transition Q)} {R : Q → Nat} + (hreduced : Reduced P) (hclosed : Closed T) + (hparity : ParityTable P T) (hranked : Ranked T R) + (hR1 : R 1 = 0) (hne : ∃ x : Q, x ≠ 1) : + ∃ a : Q, a ≠ 1 ∧ Transition.mk a ({1} : Set Q) ∈ T := by + classical + have hex : ∃ n : Nat, ∃ x : Q, x ≠ 1 ∧ R x = n := by + obtain ⟨x, hx⟩ := hne + exact ⟨R x, x, hx, rfl⟩ + let n := Nat.find hex + obtain ⟨a, ha1, haR⟩ := Nat.find_spec hex + obtain ⟨E, haT, hdesc⟩ := hranked a + have hEsub : E ⊆ ({1} : Set Q) := by + intro e he + by_contra he1 + have hleast : n ≤ R e := by + apply Nat.find_min' hex + exact ⟨e, he1, rfl⟩ + have hlt : R e < n := by + change R e < Nat.find hex + rw [← haR] + exact hdesc e he + omega + have hEne : E.Nonempty := by + by_contra hnone + have hEempty : E = ∅ := not_nonempty_iff_eq_empty.mp hnone + obtain ⟨F, h1T, h1desc⟩ := hranked (1 : Q) + have hFempty : F = ∅ := by + apply not_nonempty_iff_eq_empty.mp + rintro ⟨f, hf⟩ + have hlt := h1desc f hf + rw [hR1] at hlt + omega + have haval : a = 1 := value_eq_of_options_eq + hreduced hclosed hparity hranked haT h1T (by simp [hEempty, hFempty]) + exact ha1 haval + have hE : E = ({1} : Set Q) := by + apply Subset.antisymm hEsub + rintro x rfl + obtain ⟨e, he⟩ := hEne + have he1 : e = 1 := by simpa using hEsub he + simpa [← he1] using he + exact ⟨a, ha1, by simpa [hE] using haT⟩ + +/-- Powers of the star record are exactly the transition records of a unary +heap chain. -/ +theorem unary_chain_transition + {T : Set (Transition Q)} (hclosed : Closed T) + {a : Q} (hstar : Transition.mk a ({1} : Set Q) ∈ T) : + ∀ k : Nat, Transition.mk (a ^ (k + 1)) ({a ^ k} : Set Q) ∈ T := by + intro k + induction k with + | zero => simpa using hstar + | succ k ih => + have hp := hclosed ih hstar + simpa [product, leftMul, pow_succ, mul_comm, mul_left_comm, mul_assoc] using hp + +/-- Prefix heaps carry quotient values; `tail k` is the `(k+1)`st heap after +an inert padding heap. -/ +inductive Heap (Q : Type*) where + | base (q : Q) + | pad + | tail (k : Nat) + deriving DecidableEq + +namespace Heap + +variable (E : Q → Set Q) (a : Q) + +def value : Heap Q → Q + | .base q => q + | .pad => 1 + | .tail k => a ^ (k + 1) + +def options : Heap Q → Set (Heap Q) + | .base q => (fun e => Heap.base e) '' E q + | .pad => ∅ + | .tail 0 => {.pad} + | .tail (k + 1) => {.tail k} + +def rank (R : Q → Nat) : Heap Q → Nat + | .base q => R q + | .pad => 0 + | .tail k => k + 1 + +end Heap + +abbrev Position (Q : Type*) := List (Heap Q) + +namespace Position + +variable (E : Q → Set Q) (a : Q) + +def value : Position Q → Q + | [] => 1 + | h :: t => Heap.value a h * value t + +/-- Componentwise moves in a word of heaps. -/ +def options : Position Q → Set (Position Q) + | [] => ∅ + | h :: t => + (fun o => o :: t) '' Heap.options E h ∪ + (fun u => h :: u) '' options t + +def optionValues (p : Position Q) : Set Q := + value a '' options E p + +def transition (p : Position Q) : Transition Q := + Transition.mk (value a p) (optionValues E a p) + +def rank (R : Q → Nat) : Position Q → Nat + | [] => 0 + | h :: t => Heap.rank R h + rank R t + +@[simp] theorem value_append (p c : Position Q) : + value a (p ++ c) = value a p * value a c := by + induction p with + | nil => simp [value] + | cons h p ih => simp [value, ih, mul_assoc] + +end Position + +section Records + +variable {P : Set Q} {T : Set (Transition Q)} {R : Q → Nat} +variable (E : Q → Set Q) (a : Q) + +theorem heap_option_values + (h : Heap Q) : + Heap.value a '' Heap.options E h = + match h with + | .base q => E q + | .pad => ∅ + | .tail 0 => ({1} : Set Q) + | .tail (k + 1) => ({a ^ (k + 1)} : Set Q) := by + cases h with + | base q => + ext x + simp [Heap.options, Heap.value] + | pad => simp [Heap.options, Heap.value] + | tail k => + cases k with + | zero => simp [Heap.options, Heap.value] + | succ k => simp [Heap.options, Heap.value] + +theorem heap_transition_mem + (hclosed : Closed T) + (htable : ∀ q, Transition.mk q (E q) ∈ T) + (hstar : Transition.mk a ({1} : Set Q) ∈ T) + (hidentity : Transition.mk (1 : Q) ∅ ∈ T) + (h : Heap Q) : + Transition.mk (Heap.value a h) + (Heap.value a '' Heap.options E h) ∈ T := by + cases h with + | base q => + rw [heap_option_values E a] + simpa [Heap.value] using htable q + | pad => + rw [heap_option_values E a] + simpa [Heap.value] using hidentity + | tail k => + cases k with + | zero => + rw [heap_option_values E a] + simpa [Heap.value] using unary_chain_transition hclosed hstar 0 + | succ k => + rw [heap_option_values E a] + simpa [Heap.value] using unary_chain_transition hclosed hstar (k + 1) + +theorem position_transition_cons (h : Heap Q) (t : Position Q) : + Position.transition E a (h :: t) = + product + (Transition.mk (Heap.value a h) + (Heap.value a '' Heap.options E h)) + (Position.transition E a t) := by + rw [Transition.mk.injEq] + constructor + · simp [Position.transition, Position.value, product] + · ext x + simp only [Position.transition, Position.optionValues, Position.options, + Position.value, product, leftMul, Set.mem_union, Set.mem_image] + constructor + · rintro ⟨u, hu | hu, rfl⟩ + · obtain ⟨o, ho, rfl⟩ := hu + exact Or.inl ⟨Heap.value a o, ⟨o, ho, rfl⟩, + by simp [Position.value, mul_comm]⟩ + · obtain ⟨v, hv, rfl⟩ := hu + exact Or.inr ⟨Position.value a v, ⟨v, hv, rfl⟩, by simp [Position.value]⟩ + · rintro (hx | hx) + · obtain ⟨y, ⟨o, ho, rfl⟩, rfl⟩ := hx + exact ⟨o :: t, Or.inl ⟨o, ho, rfl⟩, + by simp [Position.value, mul_comm]⟩ + · obtain ⟨y, ⟨v, hv, rfl⟩, rfl⟩ := hx + exact ⟨h :: v, Or.inr ⟨v, hv, rfl⟩, by simp [Position.value]⟩ + +theorem position_transition_mem + (hclosed : Closed T) + (htable : ∀ q, Transition.mk q (E q) ∈ T) + (hstar : Transition.mk a ({1} : Set Q) ∈ T) + (hidentity : Transition.mk (1 : Q) ∅ ∈ T) : + ∀ p : Position Q, Position.transition E a p ∈ T := by + intro p + induction p with + | nil => simpa [Position.transition, Position.value, Position.optionValues, + Position.options] using hidentity + | cons h t ih => + rw [position_transition_cons E a] + exact hclosed (heap_transition_mem E a hclosed htable hstar hidentity h) ih + +end Records + +section Termination + +variable {R : Q → Nat} +variable (E : Q → Set Q) + +omit [CommMonoid Q] in +theorem heap_option_rank_lt + (hdesc : ∀ q e, e ∈ E q → R e < R q) + {h o : Heap Q} (ho : o ∈ Heap.options E h) : + Heap.rank R o < Heap.rank R h := by + cases h with + | base q => + obtain ⟨e, he, rfl⟩ := ho + simpa [Heap.rank] using hdesc q e he + | pad => simp [Heap.options] at ho + | tail k => + cases k with + | zero => + simp only [Heap.options, Set.mem_singleton_iff] at ho + subst o + simp [Heap.rank] + | succ k => + simp only [Heap.options, Set.mem_singleton_iff] at ho + subst o + simp [Heap.rank] + +omit [CommMonoid Q] in +theorem position_option_rank_lt + (hdesc : ∀ q e, e ∈ E q → R e < R q) + {p u : Position Q} (hu : u ∈ Position.options E p) : + Position.rank R u < Position.rank R p := by + induction p generalizing u with + | nil => simp [Position.options] at hu + | cons h t ih => + rcases hu with hu | hu + · obtain ⟨o, ho, rfl⟩ := hu + have hlt := heap_option_rank_lt E hdesc ho + simp only [Position.rank] + omega + · obtain ⟨v, hv, rfl⟩ := hu + have hlt := ih hv + simp only [Position.rank] + omega + +end Termination + +section Outcome + +variable {P : Set Q} {T : Set (Transition Q)} {R : Q → Nat} +variable (E : Q → Set Q) (a : Q) + +/-- The algebraically predicted previous-player-win predicate. -/ +def IsP (p : Position Q) : Prop := Position.value a p ∈ P + +/-- Misere recursion for an arbitrary candidate outcome predicate. -/ +def OutcomeRec (W : Position Q → Prop) : Prop := + ∀ p, + W p ↔ (Position.options E p).Nonempty ∧ + ∀ u ∈ Position.options E p, ¬ W u + +theorem isP_recursion + (hparity : ParityTable P T) + (htrans : ∀ p : Position Q, Position.transition E a p ∈ T) + (p : Position Q) : + IsP (P := P) a p ↔ + (Position.options E p).Nonempty ∧ + ∀ u ∈ Position.options E p, ¬ IsP (P := P) a u := by + have hp := hparity (htrans p) + change Position.value a p ∈ P ↔ _ + change Position.value a p ∈ P ↔ + (Position.optionValues E a p).Nonempty ∧ + ∀ y ∈ Position.optionValues E a p, y ∉ P at hp + rw [hp] + constructor + · rintro ⟨⟨v, hv⟩, hsafe⟩ + obtain ⟨u, hu, rfl⟩ := hv + refine ⟨⟨u, hu⟩, ?_⟩ + intro w hw hPw + exact hsafe (Position.value a w) ⟨w, hw, rfl⟩ hPw + · rintro ⟨⟨u, hu⟩, hsafe⟩ + refine ⟨⟨Position.value a u, u, hu, rfl⟩, ?_⟩ + rintro v ⟨w, hw, rfl⟩ hPv + exact hsafe w hw hPv + +/-- On the acyclic finite-exception ruleset, the misere recursion has a unique +solution. Hence `IsP` is the actual all-position outcome predicate, not merely +an algebraic labeling. -/ +theorem outcome_unique + (hdesc : ∀ q e, e ∈ E q → R e < R q) + (hparity : ParityTable P T) + (htrans : ∀ p : Position Q, Position.transition E a p ∈ T) + {W : Position Q → Prop} (hW : OutcomeRec E W) : + ∀ p, W p ↔ IsP (P := P) a p := by + intro p + induction hmeasure : Position.rank R p using Nat.strong_induction_on generalizing p with + | h n ih => + rw [hW p, isP_recursion E a hparity htrans p] + apply and_congr Iff.rfl + constructor + · intro hall u hu hPu + have hlt := position_option_rank_lt E hdesc hu + rw [hmeasure] at hlt + exact (ih (Position.rank R u) hlt u rfl).not.mp (hall u hu) hPu + · intro hall u hu hWu + have hlt := position_option_rank_lt E hdesc hu + rw [hmeasure] at hlt + exact hall u hu ((ih (Position.rank R u) hlt u rfl).mp hWu) + +/-- Indistinguishability in the generated universe, expressed by the proved +outcome predicate. -/ +def Indist (p q : Position Q) : Prop := + ∀ c : Position Q, + IsP (P := P) a (p ++ c) ↔ IsP (P := P) a (q ++ c) + +theorem indist_iff_value_eq + (hreduced : Reduced P) (p q : Position Q) : + Indist (P := P) a p q ↔ Position.value a p = Position.value a q := by + constructor + · intro hind + by_contra hne + obtain ⟨z, hz⟩ := hreduced hne + have hc := hind [.base z] + simp only [IsP, Position.value_append, Position.value, Heap.value, + mul_one] at hc + exact hz.elim (λ h => h.2 (hc.mp h.1)) (λ h => h.1 (hc.mpr h.2)) + · intro heq c + simp only [IsP, Position.value_append, heq] + +end Outcome + +section Universality + +variable {P : Set Q} {T : Set (Transition Q)} {R : Q → Nat} + +/-- **Finite-exception heap universality.** Every nontrivial reduced ranked +valid transition table supplies finitely many abstract ranked prefix heap +rules and a unary uniform tail. Every position record remains in the table, +the induced outcome predicate is the unique misere-recursive outcome, every +quotient value is represented by a one-heap position, and +indistinguishability is exactly equality of quotient values. + +When `Q` is finite, the prefix rule list is finite. The paper-level transport +to a numerical one-species ruleset enumerates the base constructors by +increasing rank, inserts `pad`, and then enumerates the tail constructors. +That enumeration is not part of the Lean statement below. -/ +theorem finite_exception_heap_normal_form + [Finite Q] + (hreduced : Reduced P) (hclosed : Closed T) + (hparity : ParityTable P T) (hranked : Ranked T R) + (hR1 : R 1 = 0) (hne : ∃ x : Q, x ≠ 1) : + ∃ (E : Q → Set Q) (a : Q), + (∀ q, Transition.mk q (E q) ∈ T) ∧ + (∀ q e, e ∈ E q → R e < R q) ∧ + (∀ p : Position Q, Position.transition E a p ∈ T) ∧ + OutcomeRec E (IsP (P := P) a) ∧ + (∀ W : Position Q → Prop, OutcomeRec E W → + ∀ p, W p ↔ IsP (P := P) a p) ∧ + (∀ p q : Position Q, + Indist (P := P) a p q ↔ Position.value a p = Position.value a q) ∧ + (∀ q : Q, Position.value a [.base q] = q) := by + classical + obtain ⟨a, _, hstar⟩ := + exists_star_transition hreduced hclosed hparity hranked hR1 hne + choose E htable hdesc using hranked + have hE1 : E 1 = ∅ := by + apply not_nonempty_iff_eq_empty.mp + rintro ⟨e, he⟩ + have hlt := hdesc 1 e he + rw [hR1] at hlt + omega + have hidentity : Transition.mk (1 : Q) ∅ ∈ T := by + simpa [hE1] using htable 1 + have htrans : ∀ p : Position Q, Position.transition E a p ∈ T := + position_transition_mem E a hclosed htable hstar hidentity + refine ⟨E, a, htable, hdesc, htrans, ?_, ?_, ?_, ?_⟩ + · exact isP_recursion E a hparity htrans + · intro W hW + exact outcome_unique E a hdesc hparity htrans hW + · exact indist_iff_value_eq a hreduced + · intro q + simp [Position.value, Heap.value] + +end Universality + +/-- A periodic heap trace cannot use a fixed active lag while insisting that +every such option strictly lowers the well-founded table rank. Thus Siegel's +descending representatives cannot simply be repeated in an octal tail. -/ +theorem no_periodic_strict_lag + (r : Nat → Nat) {N p d : Nat} (hp : 1 ≤ p) + (hperiod : ∀ n, N ≤ n → r (n + p) = r n) + (hdesc : ∀ n, N ≤ n → r n < r (n + d)) : False := by + have hperiod_iter : ∀ k : Nat, r (N + k * p) = r N := by + intro k + induction k with + | zero => simp + | succ k ih => + have hk := hperiod (N + k * p) (by omega) + rw [Nat.succ_mul, ← Nat.add_assoc] + exact hk.trans ih + have hstrict_iter : ∀ k : Nat, 1 ≤ k → r N < r (N + k * d) := by + intro k hk + induction k with + | zero => omega + | succ k ih => + by_cases hk0 : k = 0 + · subst k + simpa using hdesc N (by omega) + · have hprev : r N < r (N + k * d) := ih (by omega) + have hnext := hdesc (N + k * d) (by omega) + rw [Nat.succ_mul, ← Nat.add_assoc] + exact hprev.trans hnext + have hlt := hstrict_iter p hp + have heq := hperiod_iter d + have harg : N + p * d = N + d * p := by simp [Nat.mul_comm] + rw [harg, heq] at hlt + exact Nat.lt_irrefl _ hlt + +section OctalPadObstruction + +open Ogdoad.MisereOctalCertificate + +/-- A one-remainder digit at place `k` gives every later heap a legal option. +This is independent of the quotient labels assigned to the heaps. -/ +theorem one_bit_forces_nonempty + {A : Type*} (x : Nat → A) {one : Set Nat} {k N : Nat} + (hk : k ∈ one) (hkn : k < N) : + (oneRemainderOptions x one N).Nonempty := by + exact ⟨x (N - k), k, hk, hkn, rfl⟩ + +/-- A split digit at place `k` gives every heap of size at least `k+2` a +legal split option. -/ +theorem two_bit_forces_nonempty + (x : Nat → Q) {two : Set Nat} {k N : Nat} + (hk : k ∈ two) (hkn : k + 2 ≤ N) : + (twoRemainderOptions x two N).Nonempty := by + refine ⟨x 1 * x (N - k - 1), k, hk, hkn, ?_⟩ + exact ⟨1, N - k - 1, by omega, by omega, by omega, rfl⟩ + +/-- If a heap at least two places beyond the mask bound is terminal, then the +code has no persistent one-remainder or split bits. Whole-heap bits do not +enter: they are source-local and inactive past their own digit. -/ +theorem persistent_masks_empty_of_terminal_past_bound + (x : Nat → Q) {whole one two : Set Nat} {d N : Nat} + (hbounded : MasksBounded whole one two d) + (hN : d + 2 ≤ N) + (hterminal : octalOptions x whole one two N = ∅) : + one = ∅ ∧ two = ∅ := by + rcases hbounded with ⟨_, hone, htwo⟩ + constructor + · apply not_nonempty_iff_eq_empty.mp + rintro ⟨k, hk⟩ + have hkn : k < N := lt_of_le_of_lt (hone k hk) (by omega) + obtain ⟨q, hq⟩ := one_bit_forces_nonempty x hk hkn + have hqoctal : q ∈ octalOptions x whole one two N := by + simp only [octalOptions, Set.mem_union] + exact Or.inl (Or.inr hq) + rw [hterminal] at hqoctal + exact hqoctal + · apply not_nonempty_iff_eq_empty.mp + rintro ⟨k, hk⟩ + have hkn : k + 2 ≤ N := le_trans (Nat.add_le_add_right (htwo k hk) 2) hN + obtain ⟨q, hq⟩ := two_bit_forces_nonempty x hk hkn + have hqoctal : q ∈ octalOptions x whole one two N := by + simp only [octalOptions, Set.mem_union] + exact Or.inr hq + rw [hterminal] at hqoctal + exact hqoctal + +/-- In a deterministic valid table with two distinct nonidentity values, a +chosen descending record must contain a nonidentity option. Otherwise both +values would have the same singleton option set `{1}` and determinism would +identify them. -/ +theorem exists_nonidentity_option_of_three_values + {P : Set Q} {T : Set (Transition Q)} {R : Q → Nat} + (hreduced : Reduced P) (hclosed : Closed T) + (hparity : ParityTable P T) (hranked : Ranked T R) + (hR1 : R 1 = 0) + (E : Q → Set Q) + (htable : ∀ q, Transition.mk q (E q) ∈ T) + (hdesc : ∀ q e, e ∈ E q → R e < R q) + (hthree : ∃ x y : Q, x ≠ 1 ∧ y ≠ 1 ∧ x ≠ y) : + ∃ q e : Q, q ≠ 1 ∧ e ∈ E q ∧ e ≠ 1 := by + classical + have hE1 : E 1 = ∅ := by + apply not_nonempty_iff_eq_empty.mp + rintro ⟨e, he⟩ + have hlt := hdesc 1 e he + rw [hR1] at hlt + omega + by_contra hnone + push Not at hnone + obtain ⟨x, y, hx1, hy1, hxy⟩ := hthree + have option_set_eq_singleton (q : Q) (hq1 : q ≠ 1) : E q = {1} := by + have hnonempty : (E q).Nonempty := by + by_contra hempty + have hEq : E q = ∅ := not_nonempty_iff_eq_empty.mp hempty + have hqeq : q = 1 := value_eq_of_options_eq + hreduced hclosed hparity hranked (htable q) (htable 1) + (by simp [hEq, hE1]) + exact hq1 hqeq + apply Subset.antisymm + · intro e he + simpa using hnone q e hq1 he + · rintro e rfl + obtain ⟨z, hz⟩ := hnonempty + have hz1 : z = 1 := hnone q z hq1 hz + simpa [← hz1] using hz + have hxeq : E x = {1} := option_set_eq_singleton x hx1 + have hyeq : E y = {1} := option_set_eq_singleton y hy1 + have : x = y := value_eq_of_options_eq + hreduced hclosed hparity hranked (htable x) (htable y) + (by rw [hxeq, hyeq]) + exact hxy this + +/-- A nonidentity option of an earlier octal heap forces the later heap to be +nonterminal. A whole-heap removal contributes only `1`; a one- or +two-remainder witness remains legal when the source heap is enlarged. -/ +theorem nonidentity_option_persists_to_later_heap + (x : Nat → Q) {whole one two : Set Nat} {n N : Nat} {e : Q} + (he : e ∈ octalOptions x whole one two n) (he1 : e ≠ 1) + (hnN : n < N) : + (octalOptions x whole one two N).Nonempty := by + simp only [octalOptions, Set.mem_union] at he + rcases he with (hwhole | hone) | htwo + · exact (he1 hwhole.1).elim + · rcases hone with ⟨k, hk, hkn, _⟩ + obtain ⟨q, hq⟩ := one_bit_forces_nonempty x hk (lt_trans hkn hnN) + refine ⟨q, ?_⟩ + simp only [octalOptions, Set.mem_union] + exact Or.inl (Or.inr hq) + · rcases htwo with ⟨k, hk, hkn, _⟩ + obtain ⟨q, hq⟩ := two_bit_forces_nonempty x hk + (le_trans hkn (Nat.le_of_lt hnN)) + refine ⟨q, ?_⟩ + simp only [octalOptions, Set.mem_union] + exact Or.inr hq + +/-- **Actual-geometry pad obstruction.** If every chosen ranked record is +encoded exactly by a representative heap lying before an inert pad, then a +table with two distinct nonidentity values is impossible. Unlike +`finite_exception_inert_pad_obstruction`, this uses the compiler's ordering of +all representatives before the pad and needs no bound on the octal masks. -/ +theorem finite_exception_later_inert_pad_obstruction + {P : Set Q} {T : Set (Transition Q)} {R : Q → Nat} + (hreduced : Reduced P) (hclosed : Closed T) + (hparity : ParityTable P T) (hranked : Ranked T R) + (hR1 : R 1 = 0) + (E : Q → Set Q) + (htable : ∀ q, Transition.mk q (E q) ∈ T) + (hdesc : ∀ q e, e ∈ E q → R e < R q) + (hthree : ∃ x y : Q, x ≠ 1 ∧ y ≠ 1 ∧ x ≠ y) + (x : Nat → Q) (representative : Q → Nat) + {whole one two : Set Nat} {N : Nat} + (hencode : ∀ q, octalOptions x whole one two (representative q) = E q) + (hbefore : ∀ q, representative q < N) + (hterminal : octalOptions x whole one two N = ∅) : False := by + obtain ⟨q, e, _, he, he1⟩ := exists_nonidentity_option_of_three_values + hreduced hclosed hparity hranked hR1 E htable hdesc hthree + have heoctal : e ∈ octalOptions x whole one two (representative q) := by + rw [hencode q] + exact he + obtain ⟨z, hz⟩ := nonidentity_option_persists_to_later_heap + x heoctal he1 (hbefore q) + rw [hterminal] at hz + exact hz + +/-- **Pad obstruction for the finite-exception compiler.** Suppose each +chosen ranked prefix option set is encoded exactly by an octal heap. With two +distinct nonidentity values, one prefix record contains a nonidentity option. +Such an option cannot come from a whole-heap bit, so some persistent one- or +two-remainder bit is active. It then contradicts a later inert pad beyond the +code support. + +This rules out only a verbatim octal encoding of the normal-form compiler; it +does not rule out a different encoding with an active bridge. -/ +theorem finite_exception_inert_pad_obstruction + {P : Set Q} {T : Set (Transition Q)} {R : Q → Nat} + (hreduced : Reduced P) (hclosed : Closed T) + (hparity : ParityTable P T) (hranked : Ranked T R) + (hR1 : R 1 = 0) + (E : Q → Set Q) + (htable : ∀ q, Transition.mk q (E q) ∈ T) + (hdesc : ∀ q e, e ∈ E q → R e < R q) + (hthree : ∃ x y : Q, x ≠ 1 ∧ y ≠ 1 ∧ x ≠ y) + (x : Nat → Q) (representative : Q → Nat) + {whole one two : Set Nat} {d N : Nat} + (hencode : ∀ q, octalOptions x whole one two (representative q) = E q) + (hbounded : MasksBounded whole one two d) + (hN : d + 2 ≤ N) + (hterminal : octalOptions x whole one two N = ∅) : False := by + obtain ⟨q, e, hq1, he, he1⟩ := exists_nonidentity_option_of_three_values + hreduced hclosed hparity hranked hR1 E htable hdesc hthree + have heoctal : e ∈ octalOptions x whole one two (representative q) := by + rw [hencode q] + exact he + have hpersistent : one.Nonempty ∨ two.Nonempty := by + simp only [octalOptions, Set.mem_union] at heoctal + rcases heoctal with (hwhole | hone) | htwo + · exact (he1 hwhole.1).elim + · rcases hone with ⟨k, hk, _⟩ + exact Or.inl ⟨k, hk⟩ + · rcases htwo with ⟨k, hk, _⟩ + exact Or.inr ⟨k, hk⟩ + have hempty := persistent_masks_empty_of_terminal_past_bound + x hbounded hN hterminal + rcases hpersistent with hone | htwo + · rw [hempty.1] at hone + rcases hone with ⟨k, hk⟩ + exact hk + · rw [hempty.2] at htwo + rcases htwo with ⟨k, hk⟩ + exact hk + +end OctalPadObstruction + +end Ogdoad.MisereNaturalUniversality diff --git a/formal/Ogdoad/MisereOctalCertificate.lean b/formal/Ogdoad/MisereOctalCertificate.lean new file mode 100644 index 00000000..12ef65e3 --- /dev/null +++ b/formal/Ogdoad/MisereOctalCertificate.lean @@ -0,0 +1,363 @@ +import Ogdoad.MisereTransition + +/-! +# Exact periodic certificates for finite octal traces + +This module upgrades periodicity of the bare split convolution to periodicity +of the complete option-value record of a finite octal code. In particular, +once a proposed heap word is ultimately periodic, membership of one full +period of tail records in a transition table certifies membership of every +later record. No bounded-game or bounded-multiplicity computation appears +in the statement. + +The three masks use the usual octal convention: `whole` permits removal of a +whole heap, `one` permits one nonempty remainder, and `two` permits two +nonempty remainders. Equal parts are included; passing to unordered splits +does not change their set of quotient values in a commutative monoid. +-/ + +namespace Ogdoad.MisereOctalCertificate + +open Set +open Ogdoad.MisereTransition + +variable {Q : Type*} [CommMonoid Q] + +section ExactQuotient + +variable {G : Type*} + +/-- The defining previous-player-win recurrence for a well-founded misere +ruleset. The terminal position has no options and is therefore not in `W`. -/ +def MisereRecurrence (options : G → Set G) (W : G → Prop) : Prop := + ∀ g, W g ↔ (options g).Nonempty ∧ ∀ h ∈ options g, ¬W h + +/-- Contextual indistinguishability in a universe closed under sum. -/ +def Indistinguishable [Add G] (W : G → Prop) (g h : G) : Prop := + ∀ z, W (g + z) ↔ W (h + z) + +omit [CommMonoid Q] in +/-- Transition-table parity proves outcome correctness for every position, +not only for heaps, once every option decreases a rank and every position's +exact option image is a table record. -/ +theorem outcome_iff_value_mem + {P : Set Q} {T : Set (Transition Q)} + {options : G → Set G} {W : G → Prop} {rank : G → Nat} {value : G → Q} + (hdesc : ∀ g h, h ∈ options g → rank h < rank g) + (hrecurrence : MisereRecurrence options W) + (hparity : ParityTable P T) + (hrecord : ∀ g, Transition.mk (value g) (value '' options g) ∈ T) : + ∀ g, W g ↔ value g ∈ P := by + intro g + induction g using (measure rank).wf.induction with + | h g ih => + have htable : + ParityCorrect P (Transition.mk (value g) (value '' options g)) := + hparity (hrecord g) + change value g ∈ P ↔ + (value '' options g).Nonempty ∧ ∀ q ∈ value '' options g, q ∉ P at htable + rw [hrecurrence g, htable] + constructor + · rintro ⟨⟨h, hh⟩, hall⟩ + refine ⟨⟨value h, h, hh, rfl⟩, ?_⟩ + rintro q ⟨k, hk, rfl⟩ hkP + exact hall k hk ((ih k (hdesc g k hk)).mpr hkP) + · rintro ⟨⟨q, h, hh, rfl⟩, hall⟩ + refine ⟨⟨h, hh⟩, ?_⟩ + intro k hk hkW + exact hall (value k) ⟨k, hk, rfl⟩ ((ih k (hdesc g k hk)).mp hkW) + +/-- **Exact quotient certificate.** Suppose position values multiply under +sum, every quotient value is represented by a position, and table parity has +already proved the outcome criterion. Then equality of values is exactly +contextual indistinguishability. Reduction supplies a separating context in +the converse direction. -/ +theorem value_eq_iff_indistinguishable + [Add G] {P : Set Q} {W : G → Prop} {value : G → Q} + (hreduced : Reduced P) + (houtcome : ∀ g, W g ↔ value g ∈ P) + (hadd : ∀ g h, value (g + h) = value g * value h) + (hsurjective : Function.Surjective value) (g h : G) : + value g = value h ↔ Indistinguishable W g h := by + constructor + · intro hvalue z + rw [houtcome, houtcome, hadd, hadd, hvalue] + · intro hindist + by_contra hne + obtain ⟨q, hsep⟩ := hreduced hne + obtain ⟨z, rfl⟩ := hsurjective q + have hsame : value g * value z ∈ P ↔ value h * value z ∈ P := by + rw [← hadd, ← hadd, ← houtcome, ← houtcome] + exact hindist z + rcases hsep with hsep | hsep + · exact hsep.2 (hsame.mp hsep.1) + · exact hsep.1 (hsame.mpr hsep.2) + +/-- The rank/parity and reduction arguments combine into one reusable exact +quotient theorem. -/ +theorem exact_quotient_of_transition_certificate + [Add G] {P : Set Q} {T : Set (Transition Q)} + {options : G → Set G} {W : G → Prop} {rank : G → Nat} {value : G → Q} + (hdesc : ∀ g h, h ∈ options g → rank h < rank g) + (hrecurrence : MisereRecurrence options W) + (hparity : ParityTable P T) + (hrecord : ∀ g, Transition.mk (value g) (value '' options g) ∈ T) + (hreduced : Reduced P) + (hadd : ∀ g h, value (g + h) = value g * value h) + (hsurjective : Function.Surjective value) (g h : G) : + value g = value h ↔ Indistinguishable W g h := by + apply value_eq_iff_indistinguishable hreduced + · exact outcome_iff_value_mem hdesc hrecurrence hparity hrecord + · exact hadd + · exact hsurjective + +end ExactQuotient + +/-- Quotient values of options that leave one nonempty heap. -/ +def oneRemainderOptions (x : Nat → Q) (one : Set Nat) (n : Nat) : Set Q := + {q | ∃ k, k ∈ one ∧ k < n ∧ q = x (n - k)} + +/-- Quotient values of options that leave two nonempty heaps. -/ +def twoRemainderOptions (x : Nat → Q) (two : Set Nat) (n : Nat) : Set Q := + {q | ∃ k, k ∈ two ∧ k + 2 ≤ n ∧ q ∈ splitSet x (n - k)} + +/-- The terminal quotient value contributed by a whole-heap removal. -/ +def wholeHeapOptions (whole : Set Nat) (n : Nat) : Set Q := + {q | q = 1 ∧ n ∈ whole} + +/-- The complete option-value set for a heap of size `n`. -/ +def octalOptions + (x : Nat → Q) (whole one two : Set Nat) (n : Nat) : Set Q := + wholeHeapOptions whole n ∪ + oneRemainderOptions x one n ∪ twoRemainderOptions x two n + +/-- All nonzero digits of the three octal masks occur by place `d`. -/ +def MasksBounded + (whole one two : Set Nat) (d : Nat) : Prop := + (∀ k, k ∈ whole → k ≤ d) ∧ + (∀ k, k ∈ one → k ≤ d) ∧ + ∀ k, k ∈ two → k ≤ d + +theorem wholeHeapOptions_eq_empty_of_bound + {whole : Set Nat} {d n : Nat} + (hwhole : ∀ k, k ∈ whole → k ≤ d) (hdn : d < n) : + wholeHeapOptions (Q := Q) whole n = ∅ := by + have hn : n ∉ whole := by + intro hn + exact (not_le_of_gt hdn) (hwhole n hn) + ext q + simp [wholeHeapOptions, hn] + +omit [CommMonoid Q] in +/-- The one-remainder contribution inherits the period of the heap word once +all permitted removals lie wholly inside the periodic tail. -/ +theorem oneRemainderOptions_add_period + (x : Nat → Q) {one : Set Nat} {d N p n : Nat} + (hN : 1 ≤ N) (hp : 1 ≤ p) + (hone : ∀ k, k ∈ one → k ≤ d) + (hperiod : ∀ m, N ≤ m → x (m + p) = x m) + (hn : 2 * N + p + d ≤ n) : + oneRemainderOptions x one (n + p) = oneRemainderOptions x one n := by + ext q + constructor + · rintro ⟨k, hk, _, hq⟩ + have hkd : k ≤ d := hone k hk + have hkn : k < n := by omega + have hNk : N ≤ n - k := by omega + have hidx : n + p - k = (n - k) + p := by omega + refine ⟨k, hk, hkn, ?_⟩ + rw [hidx, hperiod (n - k) hNk] at hq + exact hq + · rintro ⟨k, hk, hkn, hq⟩ + have hkd : k ≤ d := hone k hk + have hNk : N ≤ n - k := by omega + have hidx : n + p - k = (n - k) + p := by omega + refine ⟨k, hk, by omega, ?_⟩ + rw [hidx, hperiod (n - k) hNk] + exact hq + +/-- The two-remainder contribution inherits the period of the heap word past +the explicit preperiod-crossing bound. -/ +theorem twoRemainderOptions_add_period + (x : Nat → Q) {two : Set Nat} {d N p n : Nat} + (hN : 1 ≤ N) (hp : 1 ≤ p) + (htwo : ∀ k, k ∈ two → k ≤ d) + (hperiod : ∀ m, N ≤ m → x (m + p) = x m) + (hn : 2 * N + p + d ≤ n) : + twoRemainderOptions x two (n + p) = twoRemainderOptions x two n := by + ext q + constructor + · rintro ⟨k, hk, _, hq⟩ + have hkd : k ≤ d := htwo k hk + have hres : 2 * N + p ≤ n - k := by omega + have hidx : n + p - k = (n - k) + p := by omega + have hsplit : splitSet x ((n - k) + p) = splitSet x (n - k) := + splitSet_add_period x hN hp hperiod hres + refine ⟨k, hk, by omega, ?_⟩ + rw [hidx, hsplit] at hq + exact hq + · rintro ⟨k, hk, hkn, hq⟩ + have hkd : k ≤ d := htwo k hk + have hres : 2 * N + p ≤ n - k := by omega + have hidx : n + p - k = (n - k) + p := by omega + have hsplit : splitSet x ((n - k) + p) = splitSet x (n - k) := + splitSet_add_period x hN hp hperiod hres + refine ⟨k, hk, by omega, ?_⟩ + rw [hidx, hsplit] + exact hq + +/-- A finite octal code applied to an ultimately periodic heap word has an +ultimately periodic complete option-value word. -/ +theorem octalOptions_add_period + (x : Nat → Q) {whole one two : Set Nat} {d N p n : Nat} + (hN : 1 ≤ N) (hp : 1 ≤ p) + (hbounded : MasksBounded whole one two d) + (hperiod : ∀ m, N ≤ m → x (m + p) = x m) + (hn : 2 * N + p + d ≤ n) : + octalOptions x whole one two (n + p) = + octalOptions x whole one two n := by + rcases hbounded with ⟨hwhole, hone, htwo⟩ + have hdn : d < n := by omega + have hdnp : d < n + p := by omega + unfold octalOptions + rw [wholeHeapOptions_eq_empty_of_bound hwhole hdnp] + rw [wholeHeapOptions_eq_empty_of_bound hwhole hdn] + rw [oneRemainderOptions_add_period x hN hp hone hperiod hn] + rw [twoRemainderOptions_add_period x hN hp htwo hperiod hn] + +/-- The complete transition record attached to one heap. -/ +def octalRecord + (x : Nat → Q) (whole one two : Set Nat) (n : Nat) : Transition Q := + Transition.mk (x n) (octalOptions x whole one two n) + +/-- An exact octal heap record in a reduced ranked parity table cannot contain +its own heap value among its option values. -/ +theorem octal_value_not_mem_own_options + (x : Nat → Q) {whole one two : Set Nat} + {P : Set Q} {T : Set (Transition Q)} {R : Q → Nat} {n : Nat} + (hP : P.Nonempty) (hterminal : Transition.mk 1 ∅ ∈ T) + (hreduced : Reduced P) (hclosed : Closed T) + (hparity : ParityTable P T) (hranked : Ranked T R) + (hrecord : octalRecord x whole one two n ∈ T) : + x n ∉ octalOptions x whole one two n := by + simpa [octalRecord] using + value_not_mem_own_options hP hterminal hreduced hclosed hparity hranked hrecord + +/-- If the whole-heap bit is present at `n`, the value of `H_n` cannot be the +terminal value. -/ +theorem octal_whole_bit_value_ne_one + (x : Nat → Q) {whole one two : Set Nat} + {P : Set Q} {T : Set (Transition Q)} {R : Q → Nat} {n : Nat} + (hP : P.Nonempty) (hterminal : Transition.mk 1 ∅ ∈ T) + (hreduced : Reduced P) (hclosed : Closed T) + (hparity : ParityTable P T) (hranked : Ranked T R) + (hrecord : octalRecord x whole one two n ∈ T) + (hnwhole : n ∈ whole) : + x n ≠ 1 := by + intro hxn + apply octal_value_not_mem_own_options x hP hterminal hreduced hclosed + hparity hranked hrecord + simp [octalOptions, wholeHeapOptions, hnwhole, hxn] + +/-- If removing `k` may leave one nonempty heap, a heap cannot have the same +value as that remainder. -/ +theorem octal_one_bit_value_ne_remainder + (x : Nat → Q) {whole one two : Set Nat} + {P : Set Q} {T : Set (Transition Q)} {R : Q → Nat} {n k : Nat} + (hP : P.Nonempty) (hterminal : Transition.mk 1 ∅ ∈ T) + (hreduced : Reduced P) (hclosed : Closed T) + (hparity : ParityTable P T) (hranked : Ranked T R) + (hrecord : octalRecord x whole one two n ∈ T) + (hkone : k ∈ one) (hkn : k < n) : + x n ≠ x (n - k) := by + intro hxn + apply octal_value_not_mem_own_options x hP hterminal hreduced hclosed + hparity hranked hrecord + simp only [octalOptions, mem_union] + exact Or.inl (Or.inr ⟨k, hkone, hkn, hxn⟩) + +/-- If removing `k` may leave two nonempty heaps of sizes `i,j`, the source +heap cannot have the product of those two heap values. -/ +theorem octal_two_bit_value_ne_split_product + (x : Nat → Q) {whole one two : Set Nat} + {P : Set Q} {T : Set (Transition Q)} {R : Q → Nat} + {n k i j : Nat} + (hP : P.Nonempty) (hterminal : Transition.mk 1 ∅ ∈ T) + (hreduced : Reduced P) (hclosed : Closed T) + (hparity : ParityTable P T) (hranked : Ranked T R) + (hrecord : octalRecord x whole one two n ∈ T) + (hktwo : k ∈ two) (hkn : k + 2 ≤ n) + (hi : 1 ≤ i) (hj : 1 ≤ j) (hij : i + j = n - k) : + x n ≠ x i * x j := by + intro hxn + apply octal_value_not_mem_own_options x hP hterminal hreduced hclosed + hparity hranked hrecord + simp only [octalOptions, mem_union] + exact Or.inr ⟨k, hktwo, hkn, ⟨i, j, hi, hj, hij, hxn⟩⟩ + +/-- Both fields of the heap transition record inherit the asserted period. -/ +theorem octalRecord_add_period + (x : Nat → Q) {whole one two : Set Nat} {d N p n : Nat} + (hN : 1 ≤ N) (hp : 1 ≤ p) + (hbounded : MasksBounded whole one two d) + (hperiod : ∀ m, N ≤ m → x (m + p) = x m) + (hn : 2 * N + p + d ≤ n) : + octalRecord x whole one two (n + p) = + octalRecord x whole one two n := by + unfold octalRecord + rw [hperiod n (by omega)] + rw [octalOptions_add_period x hN hp hbounded hperiod hn] + +/-- **Exact infinite-tail certificate.** If the heap word has an asserted +period and the `p` records in one tail period belong to a transition table, +then every later record belongs to that table. -/ +theorem octalRecord_mem_of_fundamental_period + (x : Nat → Q) {whole one two : Set Nat} {T : Set (Transition Q)} + {d N p B : Nat} + (hN : 1 ≤ N) (hp : 1 ≤ p) + (hbounded : MasksBounded whole one two d) + (hperiod : ∀ m, N ≤ m → x (m + p) = x m) + (hB : 2 * N + p + d ≤ B) + (hfundamental : ∀ n, B ≤ n → n < B + p → + octalRecord x whole one two n ∈ T) : + ∀ n, B ≤ n → octalRecord x whole one two n ∈ T := by + intro n hn + induction n using Nat.strong_induction_on with + | h n ih => + by_cases hfirst : n < B + p + · exact hfundamental n hn hfirst + · have hBp : B + p ≤ n := by omega + have hpn : p ≤ n := by omega + have hnsub : B ≤ n - p := by omega + have hlower : 2 * N + p + d ≤ n - p := le_trans hB hnsub + have hlt : n - p < n := by omega + have hprevious : octalRecord x whole one two (n - p) ∈ T := + ih (n - p) hlt hnsub + have hrecord := + octalRecord_add_period x hN hp hbounded hperiod hlower + have hdecomp : n - p + p = n := Nat.sub_add_cancel hpn + rw [hdecomp] at hrecord + rw [hrecord] + exact hprevious + +/-- A finite prefix through the first certified tail period proves transition- +table membership for the complete infinite heap trace. -/ +theorem octalRecord_mem_of_finite_certificate + (x : Nat → Q) {whole one two : Set Nat} {T : Set (Transition Q)} + {d N p B : Nat} + (hN : 1 ≤ N) (hp : 1 ≤ p) + (hbounded : MasksBounded whole one two d) + (hperiod : ∀ m, N ≤ m → x (m + p) = x m) + (hB : 2 * N + p + d ≤ B) + (hfinite : ∀ n, n < B + p → octalRecord x whole one two n ∈ T) : + ∀ n, octalRecord x whole one two n ∈ T := by + intro n + by_cases hn : B ≤ n + · apply octalRecord_mem_of_fundamental_period x hN hp hbounded hperiod hB + · intro m hmB hm + exact hfinite m hm + · exact hn + · apply hfinite n + omega + +end Ogdoad.MisereOctalCertificate diff --git a/formal/Ogdoad/MisereTraceLanguage.lean b/formal/Ogdoad/MisereTraceLanguage.lean new file mode 100644 index 00000000..d19ea8da --- /dev/null +++ b/formal/Ogdoad/MisereTraceLanguage.lean @@ -0,0 +1,228 @@ +import Ogdoad.MisereOctalCertificate + +/-! +# Unary language equations for finite octal traces + +For a fixed candidate heap word, the indices carrying a quotient value form a +unary language. The indices at which an option value `q` occurs are described +by a finite Boolean/convolution expression: a finite whole-heap constant, +finite shifts of value languages, and finite shifts of pairwise sums of value +languages whose monoid products are `q`. + +The exact option-set language `exactOptionLanguage ... S` is the Boolean cell +where precisely the option values in `S` occur. A partial deterministic table +therefore gives a system of unary Boolean language equations. Its failure +language records exactly the coefficients at which the table is undefined. + +This module proves only that exact dictionary. It does not import any external +formal-language decidability or undecidability theorem. +-/ + +namespace Ogdoad.MisereTraceLanguage + +open Set +open Ogdoad.MisereTransition +open Ogdoad.MisereOctalCertificate + +variable {Q : Type*} [CommMonoid Q] + +/-- The unary language of heap indices carrying the value `q`. -/ +def valueLanguage (x : Nat → Q) (q : Q) : Set Nat := + {n | x n = q} + +/-- The unary language `A_q` of heap indices at which `q` occurs as an option +value. The three disjuncts are respectively the whole-heap constant, a finite +shift of a value language, and a shifted additive convolution of two value +languages with prescribed monoid product. -/ +def optionValueLanguage + (x : Nat → Q) (whole one two : Set Nat) (q : Q) : Set Nat := + {n | + (q = 1 ∧ n ∈ whole) ∨ + (∃ k, k ∈ one ∧ k < n ∧ q = x (n - k)) ∨ + ∃ k, k ∈ two ∧ k + 2 ≤ n ∧ + ∃ i j, 1 ≤ i ∧ 1 ≤ j ∧ i + j = n - k ∧ q = x i * x j} + +/-- Coefficientwise, `A_q` says exactly that `q` belongs to the octal option +set. -/ +theorem mem_optionValueLanguage_iff + (x : Nat → Q) (whole one two : Set Nat) (q : Q) (n : Nat) : + n ∈ optionValueLanguage x whole one two q ↔ + q ∈ octalOptions x whole one two n := by + simp only [optionValueLanguage, mem_setOf_eq, octalOptions, mem_union, + wholeHeapOptions, oneRemainderOptions, twoRemainderOptions, splitSet] + constructor + · rintro (hwhole | hone | htwo) + · exact Or.inl (Or.inl hwhole) + · exact Or.inl (Or.inr hone) + · exact Or.inr htwo + · rintro ((hwhole | hone) | htwo) + · exact Or.inl hwhole + · exact Or.inr (Or.inl hone) + · exact Or.inr (Or.inr htwo) + +/-- The Boolean cell `D_S`: exactly the option values in `S`, and no others, +occur at this coefficient. Writing the condition as an iff is the pointwise +form of intersecting all positive `A_q` and all negative complements. -/ +def exactOptionLanguage + (x : Nat → Q) (whole one two : Set Nat) (S : Set Q) : Set Nat := + {n | ∀ q, q ∈ S ↔ n ∈ optionValueLanguage x whole one two q} + +/-- Membership in `D_S` is equivalent to equality of the complete octal option +set with `S`. -/ +theorem mem_exactOptionLanguage_iff + (x : Nat → Q) (whole one two : Set Nat) (S : Set Q) (n : Nat) : + n ∈ exactOptionLanguage x whole one two S ↔ + octalOptions x whole one two n = S := by + constructor + · intro hn + ext q + rw [← mem_optionValueLanguage_iff] + exact (hn q).symm + · intro hoptions q + rw [mem_optionValueLanguage_iff, hoptions] + +/-- One coefficient of the partial table-driven octal recurrence. -/ +def traceStep + (next : Set Q → Option Q) (x : Nat → Q) + (whole one two : Set Nat) (n : Nat) : Option Q := + next (octalOptions x whole one two n) + +/-- A candidate heap word obeys every defined coefficient of the partial +table. This weak form remains meaningful even when the table later fails. -/ +def RespectsDefinedTrace + (next : Set Q → Option Q) (x : Nat → Q) + (whole one two : Set Nat) : Prop := + ∀ n q, traceStep next x whole one two n = some q → x n = q + +/-- A total table-driven trace: every coefficient is defined and returns the +asserted heap value. -/ +def IsTrace + (next : Set Q → Option Q) (x : Nat → Q) + (whole one two : Set Nat) : Prop := + ∀ n, traceStep next x whole one two n = some (x n) + +/-- The failure-totalized coefficient. `0 : WithZero Q` is a new absorbing +failure value, distinct from every successful quotient value. -/ +def totalizedTraceStep + (next : Set Q → Option Q) (x : Nat → Q) + (whole one two : Set Nat) (n : Nat) : WithZero Q := + match traceStep next x whole one two n with + | none => 0 + | some q => q + +/-- The unary language of coefficients at which the partial table is +undefined. -/ +def failureLanguage + (next : Set Q → Option Q) (x : Nat → Q) + (whole one two : Set Nat) : Set Nat := + {n | traceStep next x whole one two n = none} + +/-- The adjoined failure value occurs exactly on the failure language. -/ +theorem totalizedTraceStep_eq_zero_iff + (next : Set Q → Option Q) (x : Nat → Q) + (whole one two : Set Nat) (n : Nat) : + totalizedTraceStep next x whole one two n = 0 ↔ + n ∈ failureLanguage next x whole one two := by + unfold totalizedTraceStep failureLanguage + cases hstep : traceStep next x whole one two n <;> simp [hstep] + +/-- The table fails at `n` exactly when the exact Boolean cell `D_S` at `n` +is labelled undefined. -/ +theorem mem_failureLanguage_iff_exists_exact + (next : Set Q → Option Q) (x : Nat → Q) + (whole one two : Set Nat) (n : Nat) : + n ∈ failureLanguage next x whole one two ↔ + ∃ S, n ∈ exactOptionLanguage x whole one two S ∧ next S = none := by + constructor + · intro hn + refine ⟨octalOptions x whole one two n, ?_, ?_⟩ + · exact (mem_exactOptionLanguage_iff x whole one two _ n).2 rfl + · exact hn + · rintro ⟨S, hnS, hnone⟩ + have hoptions := + (mem_exactOptionLanguage_iff x whole one two S n).1 hnS + simpa [failureLanguage, traceStep, hoptions] using hnone + +/-- The partial recurrence is defined at every coefficient iff its failure +language is empty. -/ +theorem trace_total_iff_failureLanguage_eq_empty + (next : Set Q → Option Q) (x : Nat → Q) + (whole one two : Set Nat) : + (∀ n, ∃ q, traceStep next x whole one two n = some q) ↔ + failureLanguage next x whole one two = ∅ := by + constructor + · intro htotal + apply Set.eq_empty_iff_forall_notMem.2 + intro n hn + obtain ⟨q, hq⟩ := htotal n + rw [failureLanguage, mem_setOf_eq] at hn + rw [hn] at hq + contradiction + · intro hempty n + have hn : n ∉ failureLanguage next x whole one two := by + simp [hempty] + rw [failureLanguage, mem_setOf_eq] at hn + generalize hstep : traceStep next x whole one two n = step + cases step with + | none => exact False.elim (hn hstep) + | some q => exact ⟨q, rfl⟩ + +/-- A candidate word is a genuine total trace exactly when it respects every +defined table coefficient and the failure language is empty. -/ +theorem isTrace_iff_respectsDefinedTrace_and_failureLanguage_eq_empty + (next : Set Q → Option Q) (x : Nat → Q) + (whole one two : Set Nat) : + IsTrace next x whole one two ↔ + RespectsDefinedTrace next x whole one two ∧ + failureLanguage next x whole one two = ∅ := by + constructor + · intro htrace + constructor + · intro n q hq + have hxn := htrace n + rw [hq] at hxn + exact Option.some.inj hxn.symm + · apply (trace_total_iff_failureLanguage_eq_empty next x whole one two).1 + intro n + exact ⟨x n, htrace n⟩ + · rintro ⟨hrespects, hempty⟩ n + obtain ⟨q, hq⟩ := + (trace_total_iff_failureLanguage_eq_empty next x whole one two).2 hempty n + have hxn : x n = q := hrespects n q hq + simpa [hxn] using hq + +/-- On a total trace, the value language `X_q` is exactly the union of the +Boolean cells `D_S` whose table label is `q`. -/ +theorem mem_valueLanguage_iff_exists_exact + (next : Set Q → Option Q) (x : Nat → Q) + (whole one two : Set Nat) + (htrace : IsTrace next x whole one two) (q : Q) (n : Nat) : + n ∈ valueLanguage x q ↔ + ∃ S, n ∈ exactOptionLanguage x whole one two S ∧ next S = some q := by + constructor + · intro hn + refine ⟨octalOptions x whole one two n, ?_, ?_⟩ + · exact (mem_exactOptionLanguage_iff x whole one two _ n).2 rfl + · have htraceN := htrace n + have hxn : x n = q := hn + rw [hxn] at htraceN + exact htraceN + · rintro ⟨S, hnS, hnext⟩ + have hoptions := + (mem_exactOptionLanguage_iff x whole one two S n).1 hnS + have htraceN := htrace n + rw [traceStep, hoptions] at htraceN + rw [hnext] at htraceN + exact Option.some.inj htraceN.symm + +/-- Set-level form of the unary Boolean language equation for `X_q`. -/ +theorem valueLanguage_eq_table_cells + (next : Set Q → Option Q) (x : Nat → Q) + (whole one two : Set Nat) + (htrace : IsTrace next x whole one two) (q : Q) : + valueLanguage x q = + {n | ∃ S, n ∈ exactOptionLanguage x whole one two S ∧ next S = some q} := by + ext n + exact mem_valueLanguage_iff_exists_exact next x whole one two htrace q n + +end Ogdoad.MisereTraceLanguage diff --git a/formal/Ogdoad/MisereTransition.lean b/formal/Ogdoad/MisereTransition.lean new file mode 100644 index 00000000..1064bd8b --- /dev/null +++ b/formal/Ogdoad/MisereTransition.lean @@ -0,0 +1,283 @@ +import Mathlib.Tactic + +/-! +# Valid transition tables for finite misere quotients + +This module isolates the algebraic core of Siegel's transition-table +realization theorem. The main result is a determinism lemma that is useful +for natural heap rulesets: in a reduced valid table, an option-value set has +at most one value. The proof is not a finite search. It chooses a +least-rank context separating two alleged values and uses closure to push the +separation to an option of strictly smaller rank. + +The definitions use `Set` rather than a finiteness hypothesis. Finiteness is +needed later for decision procedures and periodicity bounds, but not for the +determinism argument. +-/ + +namespace Ogdoad.MisereTransition + +open Set + +variable {Q : Type*} [CommMonoid Q] + +/-- A quotient value together with the set of quotient values of its options. -/ +structure Transition (Q : Type*) where + value : Q + options : Set Q + +/-- Left multiplication of every member of an option-value set. -/ +def leftMul (x : Q) (E : Set Q) : Set Q := + (fun y => x * y) '' E + +/-- The transition pair of a disjunctive sum. -/ +def product (s t : Transition Q) : Transition Q where + value := s.value * t.value + options := leftMul t.value s.options ∪ leftMul s.value t.options + +/-- Misere parity recursion for one transition pair. The identity terminal +pair has empty option set and is therefore outside `P`. -/ +def ParityCorrect (P : Set Q) (t : Transition Q) : Prop := + t.value ∈ P ↔ t.options.Nonempty ∧ ∀ y ∈ t.options, y ∉ P + +/-- A context separates two quotient values when exactly one product is in +the distinguished `P`-portion. -/ +def Separates (P : Set Q) (x y z : Q) : Prop := + (x * z ∈ P ∧ y * z ∉ P) ∨ (x * z ∉ P ∧ y * z ∈ P) + +/-- The bipartite monoid is reduced: every two distinct values have a +separating context. -/ +def Reduced (P : Set Q) : Prop := + ∀ ⦃x y : Q⦄, x ≠ y → ∃ z, Separates P x y z + +/-- Transition-table closure under disjunctive sum. -/ +def Closed (T : Set (Transition Q)) : Prop := + ∀ ⦃s t : Transition Q⦄, s ∈ T → t ∈ T → product s t ∈ T + +/-- Every table entry obeys misere parity. -/ +def ParityTable (P : Set Q) (T : Set (Transition Q)) : Prop := + ∀ ⦃t : Transition Q⦄, t ∈ T → ParityCorrect P t + +/-- A rank witnesses one well-founded representative for every quotient +value. Only this descending representative is used by the determinism proof. -/ +def Ranked (T : Set (Transition Q)) (R : Q → Nat) : Prop := + ∀ x, ∃ E, Transition.mk x E ∈ T ∧ ∀ y ∈ E, R y < R x + +/-- The meximal set of `x`: no context makes both `x` and `y` previous-player +wins. -/ +def Meximal (P : Set Q) (x : Q) : Set Q := + {y | ∀ z, ¬(x * z ∈ P ∧ y * z ∈ P)} + +/-- Every option of a valid transition lies in the value's meximal set. -/ +theorem options_subset_meximal + {P : Set Q} {T : Set (Transition Q)} {R : Q → Nat} + (hclosed : Closed T) (hparity : ParityTable P T) + (hranked : Ranked T R) {t : Transition Q} (ht : t ∈ T) : + t.options ⊆ Meximal P t.value := by + intro e he z hboth + obtain ⟨F, hzT, _⟩ := hranked z + have hprod : product t (Transition.mk z F) ∈ T := hclosed ht hzT + have hsafe := (hparity hprod).mp hboth.1 + have hze : z * e ∈ (product t (Transition.mk z F)).options := + Or.inl ⟨e, he, rfl⟩ + exact hsafe.2 (z * e) hze (by simpa [mul_comm] using hboth.2) + +private theorem lower_separator_of_same_options_oriented + {P : Set Q} {T : Set (Transition Q)} + (hclosed : Closed T) (hparity : ParityTable P T) + {x y z : Q} {E F : Set Q} + (hx : Transition.mk x E ∈ T) (hy : Transition.mk y E ∈ T) + (hz : Transition.mk z F ∈ T) + (hxz : x * z ∈ P) (hyz : y * z ∉ P) : + ∃ f ∈ F, x * f ∉ P ∧ y * f ∈ P := by + let tx : Transition Q := Transition.mk x E + let ty : Transition Q := Transition.mk y E + let tz : Transition Q := Transition.mk z F + have htxz : product tx tz ∈ T := hclosed hx hz + have htyz : product ty tz ∈ T := hclosed hy hz + have hgoodX := (hparity htxz).mp hxz + have hbadY : + ¬((product ty tz).options.Nonempty ∧ + ∀ q ∈ (product ty tz).options, q ∉ P) := by + intro h + exact hyz ((hparity htyz).mpr h) + have hnonemptyY : (product ty tz).options.Nonempty := by + obtain ⟨q, hq⟩ := hgoodX.1 + rcases hq with hq | hq + · obtain ⟨e, he, rfl⟩ := hq + exact ⟨z * e, Or.inl ⟨e, he, rfl⟩⟩ + · obtain ⟨f, hf, rfl⟩ := hq + exact ⟨y * f, Or.inr ⟨f, hf, rfl⟩⟩ + have hnotsafeY : ¬∀ q ∈ (product ty tz).options, q ∉ P := by + intro hs + exact hbadY ⟨hnonemptyY, hs⟩ + push Not at hnotsafeY + obtain ⟨q, hqY, hqP⟩ := hnotsafeY + rcases hqY with hqY | hqY + · obtain ⟨e, he, rfl⟩ := hqY + exact False.elim (hgoodX.2 (z * e) (Or.inl ⟨e, he, rfl⟩) hqP) + · obtain ⟨f, hf, rfl⟩ := hqY + refine ⟨f, hf, ?_, hqP⟩ + exact hgoodX.2 (x * f) (Or.inr ⟨f, hf, rfl⟩) + +/-- If equal option sets were assigned two values separated by `z`, then a +descending transition for `z` contains an option that already separates the +same two values. -/ +theorem lower_separator_of_same_options + {P : Set Q} {T : Set (Transition Q)} + (hclosed : Closed T) (hparity : ParityTable P T) + {x y z : Q} {E F : Set Q} + (hx : Transition.mk x E ∈ T) (hy : Transition.mk y E ∈ T) + (hz : Transition.mk z F ∈ T) + (hsep : Separates P x y z) : + ∃ f ∈ F, Separates P x y f := by + rcases hsep with hsep | hsep + · obtain ⟨f, hf, hxf, hyf⟩ := + lower_separator_of_same_options_oriented hclosed hparity + hx hy hz hsep.1 hsep.2 + exact ⟨f, hf, Or.inr ⟨hxf, hyf⟩⟩ + · obtain ⟨f, hf, hyf, hxf⟩ := + lower_separator_of_same_options_oriented hclosed hparity + hy hx hz hsep.2 hsep.1 + exact ⟨f, hf, Or.inl ⟨hxf, hyf⟩⟩ + +/-- **Transition determinism.** In a reduced parity-correct closed table with +a well-founded rank, the option-value set determines the quotient value. -/ +theorem value_eq_of_options_eq + {P : Set Q} {T : Set (Transition Q)} {R : Q → Nat} + (hreduced : Reduced P) (hclosed : Closed T) + (hparity : ParityTable P T) (hranked : Ranked T R) + {s t : Transition Q} (hs : s ∈ T) (ht : t ∈ T) + (hoptions : s.options = t.options) : s.value = t.value := by + classical + by_contra hne + have hsep0 : ∃ z, Separates P s.value t.value z := hreduced hne + have hexistsRank : + ∃ n, ∃ z, Separates P s.value t.value z ∧ R z = n := by + obtain ⟨z, hz⟩ := hsep0 + exact ⟨R z, z, hz, rfl⟩ + let n := Nat.find hexistsRank + obtain ⟨z, hzsep, hzrank⟩ := Nat.find_spec hexistsRank + obtain ⟨F, hzT, hdesc⟩ := hranked z + have htSame : Transition.mk t.value s.options ∈ T := by + simpa [hoptions] using ht + obtain ⟨f, hf, hfsep⟩ := + lower_separator_of_same_options hclosed hparity hs htSame hzT hzsep + have hleast : n ≤ R f := by + apply Nat.find_min' hexistsRank + exact ⟨f, hfsep, rfl⟩ + have hsmaller : R f < n := by + change R f < Nat.find hexistsRank + rw [← hzrank] + exact hdesc f hf + omega + +/-- Every quotient value has a context that carries it into the `P`-portion. +The nonempty-option case follows already from parity of the square transition: +unless `x` or `x * x` is in `P`, some option `x * e` must be. An empty +descending option set identifies `x` with the terminal value by transition +determinism. -/ +theorem exists_context_mem_P + {P : Set Q} {T : Set (Transition Q)} {R : Q → Nat} + (hP : P.Nonempty) (hterminal : Transition.mk 1 ∅ ∈ T) + (hreduced : Reduced P) (hclosed : Closed T) + (hparity : ParityTable P T) (hranked : Ranked T R) (x : Q) : + ∃ z, x * z ∈ P := by + classical + obtain ⟨E, hxT, _⟩ := hranked x + by_cases hE : E.Nonempty + · by_cases hxP : x ∈ P + · exact ⟨1, by simpa using hxP⟩ + · let tx : Transition Q := Transition.mk x E + have hsq : product tx tx ∈ T := hclosed hxT hxT + by_cases hxxP : x * x ∈ P + · exact ⟨x, hxxP⟩ + · obtain ⟨e, he⟩ := hE + have hoptionsNonempty : (product tx tx).options.Nonempty := + ⟨x * e, Or.inl ⟨e, he, rfl⟩⟩ + have hnotsafe : ¬∀ q ∈ (product tx tx).options, q ∉ P := by + intro hsafe + exact hxxP ((hparity hsq).mpr ⟨hoptionsNonempty, hsafe⟩) + push Not at hnotsafe + obtain ⟨q, hq, hqP⟩ := hnotsafe + rcases hq with hq | hq + · obtain ⟨e, he, rfl⟩ := hq + exact ⟨e, hqP⟩ + · obtain ⟨e, he, rfl⟩ := hq + exact ⟨e, hqP⟩ + · have hEempty : E = ∅ := not_nonempty_iff_eq_empty.mp hE + have hx1 : x = 1 := + value_eq_of_options_eq hreduced hclosed hparity hranked + hxT hterminal (by simpa using hEempty) + obtain ⟨p, hp⟩ := hP + exact ⟨p, by simpa [hx1] using hp⟩ + +/-- No transition value can occur among its own option values. Otherwise the +meximal obstruction would forbid the `P`-reaching context supplied by +`exists_context_mem_P`. -/ +theorem value_not_mem_own_options + {P : Set Q} {T : Set (Transition Q)} {R : Q → Nat} + (hP : P.Nonempty) (hterminal : Transition.mk 1 ∅ ∈ T) + (hreduced : Reduced P) (hclosed : Closed T) + (hparity : ParityTable P T) (hranked : Ranked T R) + {t : Transition Q} (ht : t ∈ T) : + t.value ∉ t.options := by + intro hself + have hmex : t.value ∈ Meximal P t.value := + options_subset_meximal hclosed hparity hranked ht hself + obtain ⟨z, hzP⟩ := + exists_context_mem_P hP hterminal hreduced hclosed hparity hranked t.value + exact hmex z ⟨hzP, hzP⟩ + +section PeriodicConvolution + +/-- Products contributed by positive two-heap splits with total size `m`. +Using ordered pairs avoids division; commutativity identifies this set with +the usual octal convention that lists each unordered split once. -/ +def splitSet (x : Nat → Q) (m : Nat) : Set Q := + {q | ∃ i j, 1 ≤ i ∧ 1 ≤ j ∧ i + j = m ∧ q = x i * x j} + +/-- An ultimately periodic heap word has an ultimately periodic split +convolution. The bound includes the preperiod-crossing pairs explicitly. -/ +theorem splitSet_add_period + (x : Nat → Q) {N p m : Nat} + (hN : 1 ≤ N) (hp : 1 ≤ p) + (hperiod : ∀ n, N ≤ n → x (n + p) = x n) + (hm : 2 * N + p ≤ m) : + splitSet x (m + p) = splitSet x m := by + ext q + constructor + · rintro ⟨i, j, hi, hj, hij, rfl⟩ + by_cases hiTail : N + p ≤ i + · refine ⟨i - p, j, ?_, hj, ?_, ?_⟩ + · omega + · omega + · have hxi : x i = x (i - p) := by + have hbase : N ≤ i - p := by omega + have := hperiod (i - p) hbase + simpa [Nat.sub_add_cancel (by omega : p ≤ i)] using this + rw [hxi] + · have hjTail : N + p ≤ j := by omega + refine ⟨i, j - p, hi, ?_, ?_, ?_⟩ + · omega + · omega + · have hxj : x j = x (j - p) := by + have hbase : N ≤ j - p := by omega + have := hperiod (j - p) hbase + simpa [Nat.sub_add_cancel (by omega : p ≤ j)] using this + rw [hxj] + · rintro ⟨i, j, hi, hj, hij, rfl⟩ + by_cases hiTail : N ≤ i + · refine ⟨i + p, j, ?_, hj, ?_, ?_⟩ + · omega + · omega + · rw [hperiod i hiTail] + · have hjTail : N ≤ j := by omega + refine ⟨i, j + p, hi, ?_, ?_, ?_⟩ + · omega + · omega + · rw [hperiod j hjTail] + +end PeriodicConvolution + +end Ogdoad.MisereTransition diff --git a/formal/Ogdoad/NimFastMultiplication.lean b/formal/Ogdoad/NimFastMultiplication.lean new file mode 100644 index 00000000..3f1e40bd --- /dev/null +++ b/formal/Ogdoad/NimFastMultiplication.lean @@ -0,0 +1,160 @@ +import Ogdoad.Algebra.ArtinSchreier + +/-! +# Fast multiplication in canonical nim coordinates: algebraic glue + +The asymptotic algorithms used in the paper live outside Lean. This module +checks the load-bearing algebraic identities in their specialization to the +literal characteristic-two Conway tower: + +* an Artin--Schreier generator may be shifted by a downstairs solution of the + source difference; +* relative trace one carries the source trace upward; +* the direct quadratic product has the stated three-variable-product split; +* the corresponding two-coordinate basis change is an involution and evaluates + to the same field element; +* ring equivalences preserve the canonical tower equations; and +* transporting multiplication through a ring equivalence returns exactly the + original basis coordinates. + +No complexity claim, concrete nimber construction, or De Feo--Schost algorithm +is formalized here. The paper entry point imports the reusable finite-field +trace-one irreducibility criterion from `Ogdoad.Algebra.ArtinSchreier`. +-/ + +namespace Ogdoad.NimFastMultiplication + +noncomputable section + +open scoped CharTwo + +section ArtinSchreierShift + +variable {K : Type*} [CommRing K] [CharP K 2] + +/-- If `u` has Artin--Schreier source `g` and `delta` has source `a + g`, +then the shifted generator `u + delta` has source `a`. -/ +theorem shifted_generator_source (u delta g a : K) + (hu : u ^ 2 + u = g) (hdelta : delta ^ 2 + delta = a + g) : + (u + delta) ^ 2 + (u + delta) = a := by + rw [ArtinSchreier.add, hu, hdelta] + calc + g + (a + g) = a + (g + g) := by ac_rfl + _ = a := by rw [CharTwo.add_self_eq_zero, add_zero] + +/-- Change the two coefficients for the generator replacement +`s = u + delta`: `A + B*s = (A + B*delta) + B*u`. -/ +def shiftPair (delta : K) (p : K × K) : K × K := + (p.1 + p.2 * delta, p.2) + +/-- In characteristic two the coefficient change for `u <-> u + delta` is its +own inverse. -/ +@[simp] +theorem shiftPair_involutive (delta : K) (p : K × K) : + shiftPair delta (shiftPair delta p) = p := by + rcases p with ⟨A, B⟩ + apply Prod.ext + · change (A + B * delta) + B * delta = A + rw [add_assoc, CharTwo.add_self_eq_zero, add_zero] + · rfl + +/-- Evaluation of a pair of coefficients in a quadratic generator. -/ +def evalPair (u : K) (p : K × K) : K := p.1 + p.2 * u + +omit [CharP K 2] in +/-- The affine coefficient shift represents the same field element. -/ +theorem evalPair_shift (u delta : K) (p : K × K) : + evalPair (u + delta) p = evalPair u (shiftPair delta p) := by + rcases p with ⟨A, B⟩ + simp only [evalPair, shiftPair] + ring + +end ArtinSchreierShift + +section TraceCarry + +variable {F E L : Type*} [Field F] [Field E] [Field L] + [Algebra F E] [Algebra E L] [Algebra F L] + [IsScalarTower F E L] + [FiniteDimensional F E] [FiniteDimensional E L] + +/-- If a new quadratic generator has relative trace one, multiplying it by +the old source preserves absolute trace. -/ +theorem trace_carry (a : E) (u : L) + (hu : Algebra.trace E L u = 1) : + Algebra.trace F L (algebraMap E L a * u) = + Algebra.trace F E a := by + rw [← Algebra.trace_trace (R := F) (S := E) (T := L)] + rw [← Algebra.smul_def, LinearMap.map_smul, hu, smul_eq_mul, mul_one] + +end TraceCarry + +section ThreeMultiplySplit + +variable {K : Type*} [CommRing K] [CharP K 2] + +/-- Karatsuba form of multiplication across one canonical quadratic level. +The three variable products are `x0*y0`, `x1*y1`, and +`(x0+x1)*(y0+y1)`; multiplication by the fixed source `a` is linear data. -/ +theorem canonical_quadratic_mul_split (u a x0 x1 y0 y1 : K) + (hu : u ^ 2 + u = a) : + (x0 + u * x1) * (y0 + u * y1) = + (x0 * y0 + a * (x1 * y1)) + + u * ((x0 + x1) * (y0 + y1) + x0 * y0) := by + rw [← hu] + ring_nf + simp + +end ThreeMultiplySplit + +section TowerEquations + +variable {A B : Type*} [CommRing A] [CommRing B] + +/-- The source in the literal Conway quadratic tower. -/ +def towerRhs (c : Nat → A) (i : Nat) : A := + ∏ j ∈ Finset.range i, c j + +/-- Abstract form of the canonical equations `c_i^2 + c_i = prod_{j sigma (c i)) := by + intro i + simpa [towerRhs] using congrArg sigma (h i) + +end TowerEquations + +section CoordinateTransport + +variable {F A B I : Type*} + [Field F] [Ring A] [Ring B] [Algebra F A] + +/-- Multiply basis-coordinate vectors by transporting their decoded elements +through a ring equivalence and returning through the same equivalence. -/ +noncomputable def transportedCoordinateMul + (basis : Module.Basis I F A) (sigma : A ≃+* B) + (x y : I →₀ F) : I →₀ F := + basis.repr + (sigma.symm + (sigma (basis.repr.symm x) * sigma (basis.repr.symm y))) + +/-- Transported multiplication returns exactly the coordinates of the product +in the original basis. This is the formal exact-output boundary used for the +canonical nim word after the fast-basis multiplication. -/ +theorem transportedCoordinateMul_eq + (basis : Module.Basis I F A) (sigma : A ≃+* B) + (x y : I →₀ F) : + transportedCoordinateMul basis sigma x y = + basis.repr (basis.repr.symm x * basis.repr.symm y) := by + simp [transportedCoordinateMul] + +end CoordinateTransport + +end + +end Ogdoad.NimFastMultiplication diff --git a/formal/Ogdoad/Papers/Excess.lean b/formal/Ogdoad/Papers/Excess.lean new file mode 100644 index 00000000..4068d1ec --- /dev/null +++ b/formal/Ogdoad/Papers/Excess.lean @@ -0,0 +1,3 @@ +import Ogdoad.Excess + +/-! # Lean surface for `writeups/excess.tex` -/ diff --git a/formal/Ogdoad/Papers/GoldArf.lean b/formal/Ogdoad/Papers/GoldArf.lean new file mode 100644 index 00000000..4c7bf5f6 --- /dev/null +++ b/formal/Ogdoad/Papers/GoldArf.lean @@ -0,0 +1,19 @@ +import Ogdoad.Algebra.ArtinSchreier +import Ogdoad.Quadratic.CharTwo +import Ogdoad.FifoMatching +import Ogdoad.ImpartialRealizer +import Ogdoad.GoldSemantics +import Ogdoad.GoldMatchingAlgebra +import Ogdoad.PhysicalDeferred +import Ogdoad.GoldArena +import Ogdoad.GoldNoEvaluator +import Ogdoad.GoldBlockCompression +import Ogdoad.GoldForkPadding +import Ogdoad.GameExterior +import Ogdoad.BrownGame +import Ogdoad.BrownSelectorPGame +import Ogdoad.GoldDiagonal +import Ogdoad.GoldExtraspecial +import Ogdoad.GoldExtraspecialTrace + +/-! # Lean surface for `writeups/goldarf.tex` -/ diff --git a/formal/Ogdoad/Papers/LinkingAffine.lean b/formal/Ogdoad/Papers/LinkingAffine.lean new file mode 100644 index 00000000..c5087fe4 --- /dev/null +++ b/formal/Ogdoad/Papers/LinkingAffine.lean @@ -0,0 +1,3 @@ +import Ogdoad.FifoFrontier + +/-! # Lean surface for `writeups/linking_affine.tex` -/ diff --git a/formal/Ogdoad/Papers/MisereNaturalRealization.lean b/formal/Ogdoad/Papers/MisereNaturalRealization.lean new file mode 100644 index 00000000..96bd7ffc --- /dev/null +++ b/formal/Ogdoad/Papers/MisereNaturalRealization.lean @@ -0,0 +1,5 @@ +import Ogdoad.MisereNaturalUniversality +import Ogdoad.MisereTraceLanguage +import Ogdoad.MisereGrundyObstruction + +/-! # Lean surface for `writeups/misere_natural_realization.tex` -/ diff --git a/formal/Ogdoad/Papers/NimFastMultiplication.lean b/formal/Ogdoad/Papers/NimFastMultiplication.lean new file mode 100644 index 00000000..fe2a2723 --- /dev/null +++ b/formal/Ogdoad/Papers/NimFastMultiplication.lean @@ -0,0 +1,4 @@ +import Ogdoad.Algebra.ArtinSchreier +import Ogdoad.NimFastMultiplication + +/-! # Lean surface for `writeups/nim_fast_multiplication.tex` -/ diff --git a/formal/Ogdoad/Papers/SemiringStability.lean b/formal/Ogdoad/Papers/SemiringStability.lean new file mode 100644 index 00000000..bb16d82c --- /dev/null +++ b/formal/Ogdoad/Papers/SemiringStability.lean @@ -0,0 +1,3 @@ +import Ogdoad.SemiringQuadratic + +/-! # Lean surface for `writeups/semiring_stability.tex` -/ diff --git a/formal/Ogdoad/Papers/TransfiniteArf.lean b/formal/Ogdoad/Papers/TransfiniteArf.lean new file mode 100644 index 00000000..c1298139 --- /dev/null +++ b/formal/Ogdoad/Papers/TransfiniteArf.lean @@ -0,0 +1,3 @@ +import Ogdoad.Quadratic.CharTwo + +/-! # Lean surface for `writeups/transfinite_arf.tex` -/ diff --git a/formal/Ogdoad/Papers/WittRealization.lean b/formal/Ogdoad/Papers/WittRealization.lean new file mode 100644 index 00000000..6a130e04 --- /dev/null +++ b/formal/Ogdoad/Papers/WittRealization.lean @@ -0,0 +1,8 @@ +import Ogdoad.WittRealizationExact +import Ogdoad.WittRamification +import Ogdoad.WittRealization +import Ogdoad.WittRamifiedRestriction +import Ogdoad.WittTransfer +import Ogdoad.WittSingularBoundary + +/-! # Lean surface for `writeups/witt_realization.tex` -/ diff --git a/formal/Ogdoad/PhysicalDeferred.lean b/formal/Ogdoad/PhysicalDeferred.lean index 7f14da54..df76ba4c 100644 --- a/formal/Ogdoad/PhysicalDeferred.lean +++ b/formal/Ogdoad/PhysicalDeferred.lean @@ -22,8 +22,6 @@ open Module namespace Ogdoad.PhysicalDeferred -abbrev F2 := ZMod 2 - variable {V : Type*} [Fintype V] [LinearOrder V] noncomputable def inducedEdgeCharge (G : SimpleGraph V) (S : Finset V) : F2 := by @@ -658,7 +656,7 @@ theorem quadratic_support_expansion (Q : QuadraticMap F2 M F2) let S := (b.repr x).support have hcoeff : ∀ i ∈ S, b.repr x i = 1 := by intro i hi - exact Ogdoad.Fifo.zmod2_eq_one_of_ne_zero _ (Finsupp.mem_support_iff.mp hi) + exact Ogdoad.zmod2_eq_one_of_ne_zero _ (Finsupp.mem_support_iff.mp hi) have hx : ∑ i ∈ S, b i = x := by calc ∑ i ∈ S, b i = ∑ i ∈ S, b.repr x i • b i := by diff --git a/formal/Ogdoad/Quadratic/CharTwo.lean b/formal/Ogdoad/Quadratic/CharTwo.lean new file mode 100644 index 00000000..619548a0 --- /dev/null +++ b/formal/Ogdoad/Quadratic/CharTwo.lean @@ -0,0 +1,13 @@ +import Ogdoad.Off +import Ogdoad.SymplecticBasis +import Ogdoad.WittFrame +import Ogdoad.CharTwoClassification + +/-! +# Shared characteristic-two quadratic-form development + +This domain bundle is the common import surface for papers that use the +finite-dimensional characteristic-two classification. The theorem-bearing +modules remain independently reusable; this file contains no paper-specific +declarations. +-/ diff --git a/formal/Ogdoad/SemiringQuadratic.lean b/formal/Ogdoad/SemiringQuadratic.lean new file mode 100644 index 00000000..ac34a4e7 --- /dev/null +++ b/formal/Ogdoad/SemiringQuadratic.lean @@ -0,0 +1,1103 @@ +import Mathlib + +/-! +# Quadratic pairs over cancellative semirings + +This file formalizes the finite-polynomial proxy used for the closed +finite-CNF Hessenberg semiring in `writeups/semiring_stability.tex`. + +For `MvPolynomial (Fin d) Nat`, Lean proves internally: + +* every companion over an additively cancellative semiring is balanced on the + diagonal; +* the coefficient semiring has no half of one, no nonzero self-double, and no + scalar unit other than one; +* every invertible square matrix is a permutation matrix; +* regularity of a pair's companion adjoint gives such an invertible Gram + matrix; and +* symmetry and balance turn its permutation into an explicit disjoint union + of hyperbolic planes, with even rank and zero quadratic basis labels. + +The named endpoint `hessenbergPolynomial_regular_isometric_hyperbolic` starts +from the actual adjoint-bijectivity definition of regularity and constructs a +linear isometry of quadratic pairs with the canonical hyperbolic sum. The +identification of this polynomial semiring with the ordinal fragment below +`omega^(omega^d)` remains the stated CNF bridge. + +The file also checks the exact group-theoretic `ZMod 2` endpoint of the +supertropical ring-image argument and exposes Mathlib's classical +diagonalization theorem. The concrete supertropical scalar extension remains +outside the kernel. +-/ + +namespace Ogdoad.SemiringQuadratic + +/-- A semiring has no half of one when no element doubles to `1`. This is the +small fragment of characteristic-zero arithmetic needed to exclude a fixed +point in a balanced permutation Gram matrix. -/ +class NoHalfOne (R : Type*) [AddMonoidWithOne R] : Prop where + two_mul_ne_one : ∀ a : R, a + a ≠ 1 + +/-- Doubling detects zero. This holds in every zerosumfree cancellative +semiring, in particular in the Hessenberg fragments. -/ +class NoSelfSumZero (R : Type*) [AddMonoid R] : Prop where + eq_zero_of_add_self_eq_zero : ∀ a : R, a + a = 0 → a = 0 + +/-- A quadratic pair in the semiring sense. The companion is packaged as a +bilinear map; unlike over a ring, it is part of the input. -/ +structure QuadraticPair (R V : Type*) [CommSemiring R] [AddCommMonoid V] + [Module R V] where + q : V → R + b : V →ₗ[R] V →ₗ[R] R + map_smul : ∀ (a : R) (x : V), q (a • x) = a ^ 2 * q x + polar : ∀ x y : V, q (x + y) = q x + q y + b x y + symmetric : ∀ x y : V, b x y = b y x + +/-- An isometry preserves both the quadratic function and its specified +companion. -/ +structure QuadraticPair.Isometry + {R V W : Type*} [CommSemiring R] + [AddCommMonoid V] [Module R V] [AddCommMonoid W] [Module R W] + (Q₁ : QuadraticPair R V) (Q₂ : QuadraticPair R W) where + toLinearEquiv : V ≃ₗ[R] W + map_q : ∀ x, Q₂.q (toLinearEquiv x) = Q₁.q x + map_b : ∀ x y, Q₂.b (toLinearEquiv x) (toLinearEquiv y) = Q₁.b x y + +/-- A standard coordinate vector, written without choosing a module basis. -/ +def basisVector {R I : Type*} [Zero R] [One R] [DecidableEq I] + (i : I) : I → R := + fun j => if j = i then 1 else 0 + +theorem basisVector_eq_single {R I : Type*} [Zero R] [One R] + [DecidableEq I] (i : I) : basisVector (R := R) i = Pi.single i 1 := by + ext j + by_cases h : i = j + · subst j + simp [basisVector] + · simp [basisVector, Ne.symm h] + +/-- Quadratic labels and companion Gram coefficients on a finite standard +basis determine the entire quadratic pair. This is the formal coordinate +expansion theorem used to interpret `CoefficientIsometry` as genuine pair +data rather than a lossy invariant. -/ +theorem quadraticPair_coefficients_determine + {R I : Type*} [CommSemiring R] [Fintype I] [DecidableEq I] + (Q₁ Q₂ : QuadraticPair R (I → R)) + (hq : ∀ i, Q₁.q (basisVector i) = Q₂.q (basisVector i)) + (hg : ∀ i j, + Q₁.b (basisVector i) (basisVector j) = + Q₂.b (basisVector i) (basisVector j)) : + Q₁.q = Q₂.q ∧ Q₁.b = Q₂.b := by + have hb : Q₁.b = Q₂.b := by + apply (LinearMap.toMatrix₂' R).injective + ext i j + simp only [LinearMap.toMatrix₂'_apply] + simpa only [← basisVector_eq_single] using hg i j + refine ⟨?_, hb⟩ + funext x + have hzero (Q : QuadraticPair R (I → R)) : Q.q 0 = 0 := by + have h := Q.map_smul 0 (0 : I → R) + simpa using h + have hs : ∀ s : Finset I, + Q₁.q (∑ i ∈ s, x i • basisVector i) = + Q₂.q (∑ i ∈ s, x i • basisVector i) := by + intro s + induction s using Finset.induction with + | empty => simp [hzero] + | @insert a s ha ih => + rw [Finset.sum_insert ha] + rw [Q₁.polar, Q₂.polar, Q₁.map_smul, Q₂.map_smul, + hq a, hb, ih] + have hx : (∑ i : I, x i • basisVector i) = x := by + ext j + simp [basisVector] + rw [← hx] + exact hs Finset.univ + +section Balance + +variable {R V : Type*} [CommSemiring R] [IsCancelAdd R] + [AddCommMonoid V] [Module R V] + +/-- Over an additively cancellative semiring, a quadratic form has at most one +companion. -/ +theorem companion_unique (q : V → R) + (b c : V →ₗ[R] V →ₗ[R] R) + (hb : ∀ x y, q (x + y) = q x + q y + b x y) + (hc : ∀ x y, q (x + y) = q x + q y + c x y) : b = c := by + ext x y + have h : q x + q y + b x y = q x + q y + c x y := + (hb x y).symm.trans (hc x y) + exact add_left_cancel h + +/-- Additive cancellation forces the diagonal of every companion to be +`2 q(x)`. -/ +theorem companion_diagonal (Q : QuadraticPair R V) (x : V) : + Q.b x x = Q.q x + Q.q x := by + have hscale : Q.q (x + x) = + (Q.q x + Q.q x) + (Q.q x + Q.q x) := by + calc + Q.q (x + x) = Q.q ((1 + 1 : R) • x) := by simp [add_smul] + _ = (1 + 1 : R) ^ 2 * Q.q x := Q.map_smul (1 + 1) x + _ = (Q.q x + Q.q x) + (Q.q x + Q.q x) := by ring + have hpolar := Q.polar x x + have hcancel : + (Q.q x + Q.q x) + (Q.q x + Q.q x) = + (Q.q x + Q.q x) + Q.b x x := by + exact hscale.symm.trans hpolar + exact (add_left_cancel hcancel).symm + +/-- Hence a companion diagonal entry can never be `1` in a semiring with no +half of one. -/ +theorem companion_diagonal_ne_one [NoHalfOne R] + (Q : QuadraticPair R V) (x : V) : Q.b x x ≠ 1 := by + rw [companion_diagonal Q x] + exact NoHalfOne.two_mul_ne_one (Q.q x) + +end Balance + +section PolynomialFragment + +/-- The finite-CNF Hessenberg fragment below `omega^(omega^d)` is represented +by `MvPolynomial (Fin d) Nat`: exponent natural sum is addition of exponent +vectors, and natural product is polynomial multiplication. Applying the +constant-coefficient map shows directly that this semiring has no half of +one. -/ +instance mvPolynomialNoHalfOne (sigma : Type*) : + NoHalfOne (MvPolynomial sigma Nat) where + two_mul_ne_one p hp := by + have hconstant := congrArg MvPolynomial.constantCoeff hp + simp only [map_add, map_one] at hconstant + omega + +instance mvPolynomialNoSelfSumZero (sigma : Type*) : + NoSelfSumZero (MvPolynomial sigma Nat) where + eq_zero_of_add_self_eq_zero p hp := by + ext m + have hcoeff := congrArg (MvPolynomial.coeff m) hp + rw [MvPolynomial.coeff_add, MvPolynomial.coeff_zero] at hcoeff + rw [MvPolynomial.coeff_zero] + omega + +/-- The polynomial Hessenberg model has only the trivial scalar unit. We map +coefficients to `Int`, use the reduced-ring unit classification there, and +pull the resulting constant polynomial back coefficientwise. -/ +theorem mvPolynomial_isUnit_eq_one (sigma : Type*) + (p : MvPolynomial sigma Nat) (hp : IsUnit p) : p = 1 := by + classical + let f : Nat →+* Int := Nat.castRingHom Int + have hmap : IsUnit (MvPolynomial.map f p) := hp.map (MvPolynomial.map f) + obtain ⟨r, _hr, heq⟩ := + MvPolynomial.isUnit_iff_eq_C_of_isReduced.mp hmap + have hpC : p = MvPolynomial.C (p.coeff 0) := by + ext m + by_cases hm : m = 0 + · subst m + rw [MvPolynomial.coeff_zero_C] + · have hc := congrArg (MvPolynomial.coeff m) heq + rw [MvPolynomial.coeff_map, MvPolynomial.coeff_C] at hc + have h0m : (0 : sigma →₀ Nat) ≠ m := Ne.symm hm + simp [h0m] at hc + have hz : p.coeff m = 0 := hc + rw [hz, MvPolynomial.coeff_C] + simp [h0m] + have hn : IsUnit (p.coeff 0) := hp.map MvPolynomial.constantCoeff + have hone : p.coeff 0 = 1 := Nat.isUnit_iff.mp hn + rw [hpC, hone, map_one] + +end PolynomialFragment + +section PolynomialMatrices + +/-- Evaluation with every variable equal to one sums all coefficients. -/ +def mvCoeffSum (sigma : Type*) : MvPolynomial sigma Nat →+* Nat := + MvPolynomial.eval fun _ => 1 + +/-- Because all coefficients are nonnegative, coefficient sum detects the +zero polynomial. -/ +theorem mvCoeffSum_eq_zero {sigma : Type*} (p : MvPolynomial sigma Nat) + (h : mvCoeffSum sigma p = 0) : p = 0 := by + classical + rw [mvCoeffSum, MvPolynomial.eval_eq] at h + ext m + have hm : p.coeff m = 0 := by + by_cases hmem : m ∈ p.support + · simpa using (Finset.sum_eq_zero_iff.mp h) m hmem + · exact MvPolynomial.notMem_support_iff.mp hmem + simp [hm] + +/-- A finite sum of natural-coefficient multivariate polynomials is one only +when exactly one summand is one and all others vanish. -/ +theorem mvPolynomial_fin_sum_eq_one {sigma : Type*} {n : Nat} + (f : Fin n → MvPolynomial sigma Nat) (h : ∑ i, f i = 1) : + ∃ k, f k = 1 ∧ ∀ j, j ≠ k → f j = 0 := by + classical + let d : Fin n →₀ Nat := + Finsupp.equivFunOnFinite.symm (fun i => mvCoeffSum sigma (f i)) + have hsum : ∑ i, mvCoeffSum sigma (f i) = 1 := by + rw [← map_sum, h, map_one] + have hd : d.sum (fun _ v => v) = 1 := by + rw [Finsupp.sum_fintype _ _ (by simp)] + simpa [d] using hsum + obtain ⟨k, hk⟩ := (Finsupp.sum_eq_one_iff d).mp hd + have hjzero : ∀ j, j ≠ k → f j = 0 := by + intro j hj + apply mvCoeffSum_eq_zero + have he := DFunLike.congr_fun hk j + simpa [d, Finsupp.single_apply, hj] using he + refine ⟨k, ?_, hjzero⟩ + calc + f k = ∑ j, f j := by + symm + apply Finset.sum_eq_single k + · intro b _ hbk + exact hjzero b hbk + · simp + _ = 1 := h + +/-- Coefficient sum likewise detects every zero summand of a finite sum. -/ +theorem mvPolynomial_fin_sum_eq_zero {sigma : Type*} {n : Nat} + (f : Fin n → MvPolynomial sigma Nat) (h : ∑ i, f i = 0) : + ∀ i, f i = 0 := by + intro i + apply mvCoeffSum_eq_zero + have hsum : ∑ i, mvCoeffSum sigma (f i) = 0 := by + rw [← map_sum, h, map_zero] + exact (Finset.sum_eq_zero_iff.mp hsum) i (Finset.mem_univ i) + +/-- The unique-base theorem specialized and proved internally for the +polynomial Hessenberg model: a two-sided invertible matrix is a permutation +matrix, because the coefficient semiring is entire and has only unit one. -/ +theorem mvPolynomial_inverseMatrix_permutation {sigma : Type*} {n : Nat} + (A B : Matrix (Fin n) (Fin n) (MvPolynomial sigma Nat)) + (hAB : A * B = 1) (hBA : B * A = 1) : + ∃ p : Equiv.Perm (Fin n), + ∀ i j, A i j = if j = p i then 1 else 0 := by + classical + have hchoice : ∀ i, ∃ k, + A i k = 1 ∧ B k i = 1 ∧ + ∀ j, j ≠ k → A i j * B j i = 0 := by + intro i + have hdiag : ∑ k, A i k * B k i = 1 := by + have hentry := congrFun (congrFun hAB i) i + simpa [Matrix.mul_apply] using hentry + obtain ⟨k, hkprod, hkzero⟩ := + mvPolynomial_fin_sum_eq_one (fun k => A i k * B k i) hdiag + have hAu : IsUnit (A i k) := + IsUnit.of_mul_eq_one (B k i) hkprod + have hBu : IsUnit (B k i) := + IsUnit.of_mul_eq_one (A i k) (by simpa [mul_comm] using hkprod) + exact ⟨k, mvPolynomial_isUnit_eq_one sigma (A i k) hAu, + mvPolynomial_isUnit_eq_one sigma (B k i) hBu, hkzero⟩ + let pfun : Fin n → Fin n := fun i => Classical.choose (hchoice i) + have hAone : ∀ i, A i (pfun i) = 1 := + fun i => (Classical.choose_spec (hchoice i)).1 + have hBone : ∀ i, B (pfun i) i = 1 := + fun i => (Classical.choose_spec (hchoice i)).2.1 + have hArow : ∀ i j, A i j = if j = pfun i then 1 else 0 := by + intro i j + by_cases hj : j = pfun i + · simp [hj, hAone] + · have hentry := congrFun (congrFun hBA (pfun i)) j + have hsum : ∑ k, B (pfun i) k * A k j = 0 := by + simpa [Matrix.mul_apply, Matrix.one_apply, Ne.symm hj] using hentry + have hterm := mvPolynomial_fin_sum_eq_zero + (fun k => B (pfun i) k * A k j) hsum i + have hz : A i j = 0 := by simpa [hBone i] using hterm + simp [hj, hz] + have hinj : Function.Injective pfun := by + intro i j hij + by_contra hne + have hentry := congrFun (congrFun hAB i) j + have hsum : ∑ k, A i k * B k j = 0 := by + simpa [Matrix.mul_apply, Matrix.one_apply, hne] using hentry + have hterm := mvPolynomial_fin_sum_eq_zero + (fun k => A i k * B k j) hsum (pfun i) + rw [hAone i, one_mul, hij, hBone j] at hterm + exact one_ne_zero hterm + let p : Equiv.Perm (Fin n) := Equiv.ofBijective pfun + ((Fintype.bijective_iff_injective_and_card pfun).2 ⟨hinj, rfl⟩) + refine ⟨p, ?_⟩ + intro i j + exact hArow i j + +end PolynomialMatrices + +section RegularPermutationGram + +variable {R : Type*} [CommSemiring R] [NoHalfOne R] + [NoSelfSumZero R] + {n : Nat} + +/-- Once the cited unique-base theorem has made a regular adjoint into a +permutation matrix, symmetry and balance force a fixed-point-free involution, +and every basis vector has quadratic value zero. -/ +theorem symmetric_balanced_permutationGram + (q : Fin n → R) (g : Fin n → Fin n → R) (p : Equiv.Perm (Fin n)) + (hgram : ∀ i j, g i j = if j = p i then 1 else 0) + (hsymmetric : ∀ i j, g i j = g j i) + (hbalanced : ∀ i, g i i = q i + q i) : + (∀ i, p i ≠ i) ∧ Function.Involutive p ∧ (∀ i, q i = 0) := by + have hfixed : ∀ i, p i ≠ i := by + intro i hpi + have hdiag_one : g i i = 1 := by simp [hgram, hpi] + have htwo_one : q i + q i = 1 := by + rw [← hbalanced i] + exact hdiag_one + exact NoHalfOne.two_mul_ne_one (q i) htwo_one + have hinvolutive : Function.Involutive p := by + intro i + have hforward : g i (p i) = 1 := by simp [hgram] + have hbackward : g (p i) i = 1 := by + rw [← hsymmetric i (p i)] + exact hforward + rw [hgram] at hbackward + by_contra hne + have hcond : ¬i = p (p i) := fun h ↦ hne h.symm + rw [if_neg hcond] at hbackward + exact NoHalfOne.two_mul_ne_one (0 : R) (by simpa using hbackward) + have hqzero : ∀ i, q i = 0 := by + intro i + have hdiag_zero : g i i = 0 := by + rw [hgram, if_neg (fun h ↦ hfixed i h.symm)] + have hdouble_zero : q i + q i = 0 := by + rw [← hbalanced i] + exact hdiag_zero + exact NoSelfSumZero.eq_zero_of_add_self_eq_zero (q i) hdouble_zero + exact ⟨hfixed, hinvolutive, hqzero⟩ + +end RegularPermutationGram + +section RegularPairPresentation + +variable {R : Type*} [CommSemiring R] + {n : Nat} + +/-- The adjoint of the companion, viewed as a linear map into the dual. -/ +def QuadraticPair.adjoint (Q : QuadraticPair R (Fin n → R)) : + (Fin n → R) →ₗ[R] Module.Dual R (Fin n → R) := + Q.b + +/-- The standard coordinate vector of a finite free semimodule. -/ +def coordinateVector (i : Fin n) : Fin n → R := + basisVector i + +/-- Coordinates on the dual, obtained by evaluating a functional on the +standard basis. -/ +def dualCoordinates : Module.Dual R (Fin n → R) →ₗ[R] (Fin n → R) where + toFun f i := f (coordinateVector i) + map_add' f g := by ext i; simp + map_smul' a f := by ext i; simp + +/-- Reconstruct a functional from its finite coordinate vector. -/ +def fromDualCoordinates : (Fin n → R) →ₗ[R] Module.Dual R (Fin n → R) where + toFun c := + { toFun := fun x => ∑ i, c i * x i + map_add' := by intro x y; simp [mul_add, Finset.sum_add_distrib] + map_smul' := by + intro a x + simp [Finset.mul_sum, mul_left_comm] } + map_add' c d := by + ext x + simp [Finset.sum_add_distrib, add_mul] + map_smul' a c := by + apply LinearMap.ext + intro x + change (∑ i, (a * c i) * x i) = a * ∑ i, c i * x i + rw [Finset.mul_sum] + apply Finset.sum_congr rfl + intro i _ + rw [mul_assoc] + +theorem dualCoordinates_left + (f : Module.Dual R (Fin n → R)) : + fromDualCoordinates (dualCoordinates f) = f := by + apply LinearMap.ext + intro x + change (∑ i, f (coordinateVector i) * x i) = f x + calc + ∑ i, f (coordinateVector i) * x i = + ∑ i, x i * f (coordinateVector i) := by + apply Finset.sum_congr rfl + intro i _ + rw [mul_comm] + _ = f (∑ i, x i • coordinateVector i) := by simp + _ = f x := by + congr 1 + ext j + simp [coordinateVector, basisVector] + +theorem dualCoordinates_right (c : Fin n → R) : + dualCoordinates (fromDualCoordinates c) = c := by + ext i + simp [dualCoordinates, fromDualCoordinates, coordinateVector, basisVector] + +/-- Finite free duality in explicit standard coordinates. -/ +def dualCoordinatesEquiv : + Module.Dual R (Fin n → R) ≃ₗ[R] (Fin n → R) where + __ := dualCoordinates + invFun := fromDualCoordinates + left_inv := dualCoordinates_left + right_inv := dualCoordinates_right + +/-- Regularity means that the companion adjoint is bijective. -/ +def QuadraticPair.Regular (Q : QuadraticPair R (Fin n → R)) : Prop := + Function.Bijective Q.adjoint + +/-- The adjoint followed by the explicit dual-coordinate equivalence. -/ +def QuadraticPair.adjointCoordinates + (Q : QuadraticPair R (Fin n → R)) : Module.End R (Fin n → R) := + dualCoordinates.comp Q.adjoint + +/-- Quadratic labels and Gram coefficients in the standard basis. -/ +def QuadraticPair.basisQ (Q : QuadraticPair R (Fin n → R)) (i : Fin n) : R := + Q.q (coordinateVector i) + +def QuadraticPair.gram (Q : QuadraticPair R (Fin n → R)) + (i j : Fin n) : R := + Q.b (coordinateVector i) (coordinateVector j) + +/-- The companion Gram matrix in standard coordinates. -/ +def QuadraticPair.gramMatrix (Q : QuadraticPair R (Fin n → R)) : + Matrix (Fin n) (Fin n) R := + fun i j => Q.gram i j + +/-- Coordinate regularity: the Gram matrix is a unit in the matrix monoid. +For finite free modules this is the matrix form of the companion adjoint being +an isomorphism. -/ +def QuadraticPair.MatrixRegular (Q : QuadraticPair R (Fin n → R)) : Prop := + IsUnit Q.gramMatrix + +/-- Bijectivity of the companion adjoint implies invertibility of its Gram +matrix. Symmetry identifies the matrix of the coordinate adjoint with the +Gram matrix rather than its transpose. -/ +theorem QuadraticPair.matrixRegular_of_regular + (Q : QuadraticPair R (Fin n → R)) (hreg : Q.Regular) : + Q.MatrixRegular := by + have hbij : Function.Bijective Q.adjointCoordinates := + dualCoordinatesEquiv.bijective.comp hreg + have huEnd : IsUnit Q.adjointCoordinates := + (Module.End.isUnit_iff Q.adjointCoordinates).2 hbij + have huMat : IsUnit Q.adjointCoordinates.toMatrix' := + LinearMap.isUnit_toMatrix'_iff.mpr huEnd + have heq : Q.adjointCoordinates.toMatrix' = Q.gramMatrix := by + ext i j + simp only [LinearMap.toMatrix'_apply] + change Q.b (Pi.single j 1) (coordinateVector i) = Q.gram i j + rw [← basisVector_eq_single] + exact Q.symmetric (coordinateVector j) (coordinateVector i) + rwa [heq] at huMat + +/-- Intermediate data saying that a regular Gram matrix is a permutation +matrix. For the polynomial Hessenberg proxy this certificate is constructed +internally by `hessenbergPolynomial_regular_permutationCertificate`. -/ +structure RegularPermutationCertificate + (Q : QuadraticPair R (Fin n → R)) where + regular : Q.MatrixRegular + perm : Equiv.Perm (Fin n) + gram_eq : ∀ i j, Q.gram i j = if j = perm i then 1 else 0 + +/-- Over the polynomial Hessenberg model, matrix regularity itself produces +the permutation certificate; no unique-base theorem remains as a hypothesis. -/ +theorem hessenbergPolynomial_regular_permutationCertificate + {sigma : Type*} + (Q : QuadraticPair (MvPolynomial sigma Nat) + (Fin n → MvPolynomial sigma Nat)) + (hreg : Q.MatrixRegular) : + Nonempty (RegularPermutationCertificate Q) := by + classical + obtain ⟨u, hu⟩ := hreg + have hreg' : Q.MatrixRegular := ⟨u, hu⟩ + let B : Matrix (Fin n) (Fin n) (MvPolynomial sigma Nat) := ↑(u⁻¹) + have hAB : Q.gramMatrix * B = 1 := by + rw [← hu] + dsimp [B] + exact Units.mul_inv u + have hBA : B * Q.gramMatrix = 1 := by + rw [← hu] + dsimp [B] + exact Units.inv_mul u + obtain ⟨p, hp⟩ := + mvPolynomial_inverseMatrix_permutation Q.gramMatrix B hAB hBA + exact ⟨⟨hreg', p, hp⟩⟩ + +end RegularPairPresentation + +section HyperbolicMatching + +open Function + +variable {n : Nat} + +/-- The order-selected representative of each two-cycle of `p`. -/ +def PairRep (p : Equiv.Perm (Fin n)) := {i : Fin n // i < p i} + +instance pairRepFinite (p : Equiv.Perm (Fin n)) : Finite (PairRep p) := by + unfold PairRep + infer_instance + +instance pairRepDecidableEq (p : Equiv.Perm (Fin n)) : DecidableEq (PairRep p) := by + unfold PairRep + infer_instance + +noncomputable instance pairRepFintype (p : Equiv.Perm (Fin n)) : + Fintype (PairRep p) := Fintype.ofFinite _ + +/-- The two coordinates in the plane represented by `r`: `false` is `r`, +and `true` is its mate. -/ +def matchingIndex (p : Equiv.Perm (Fin n)) : PairRep p × Bool → Fin n + | (r, false) => r.1 + | (r, true) => p r.1 + +theorem matchingIndex_bijective (p : Equiv.Perm (Fin n)) + (hfixed : ∀ i, p i ≠ i) (hinv : Function.Involutive p) : + Function.Bijective (matchingIndex p) := by + constructor + · rintro ⟨i, bi⟩ ⟨j, bj⟩ h + cases bi <;> cases bj + · simp only [matchingIndex] at h + have hij : i = j := Subtype.ext h + cases hij + rfl + · simp only [matchingIndex] at h + have hij : i.1 = p j.1 := h + have hi : p j.1 < p (p j.1) := by simpa only [hij] using i.2 + have hi' : p j.1 < j.1 := by simpa only [hinv j.1] using hi + exact (lt_asymm hi' j.2).elim + · simp only [matchingIndex] at h + have hij : p i.1 = j.1 := h + have hj : p i.1 < p (p i.1) := by simpa only [← hij] using j.2 + have hj' : p i.1 < i.1 := by simpa only [hinv i.1] using hj + exact (lt_asymm i.2 hj').elim + · simp only [matchingIndex] at h + have hij : i.1 = j.1 := p.injective h + have hij' : i = j := Subtype.ext hij + cases hij' + rfl + · intro y + by_cases hlt : y < p y + · exact ⟨(⟨y, hlt⟩, false), rfl⟩ + · have hne : y ≠ p y := fun h => hfixed y h.symm + have hmate : p y < y := lt_of_le_of_ne (le_of_not_gt hlt) hne.symm + let r : PairRep p := ⟨p y, by simpa [hinv y] using hmate⟩ + exact ⟨(r, true), by simp [matchingIndex, r, hinv y]⟩ + +/-- A fixed-point-free involution is explicitly a disjoint union of Boolean +pairs, oriented by the order on `Fin n`. -/ +noncomputable def matchingEquiv (p : Equiv.Perm (Fin n)) + (hfixed : ∀ i, p i ≠ i) (hinv : Function.Involutive p) : + PairRep p × Bool ≃ Fin n := + Equiv.ofBijective (matchingIndex p) (matchingIndex_bijective p hfixed hinv) + +@[simp] theorem matchingEquiv_apply_false (p : Equiv.Perm (Fin n)) + (hfixed : ∀ i, p i ≠ i) (hinv : Function.Involutive p) + (r : PairRep p) : matchingEquiv p hfixed hinv (r, false) = r.1 := rfl + +@[simp] theorem matchingEquiv_apply_true (p : Equiv.Perm (Fin n)) + (hfixed : ∀ i, p i ≠ i) (hinv : Function.Involutive p) + (r : PairRep p) : matchingEquiv p hfixed hinv (r, true) = p r.1 := rfl + +theorem matchingEquiv_mate (p : Equiv.Perm (Fin n)) + (hfixed : ∀ i, p i ≠ i) (hinv : Function.Involutive p) + (x : PairRep p × Bool) : + p (matchingEquiv p hfixed hinv x) = + matchingEquiv p hfixed hinv (x.1, !x.2) := by + rcases x with ⟨r, b⟩ + cases b + · rfl + · exact hinv r.1 + +variable {R : Type*} [CommSemiring R] [NoHalfOne R] + [NoSelfSumZero R] + +/-- The canonical Gram function for hyperbolic planes indexed by `I`. -/ +noncomputable def hyperbolicGram (I : Type*) : + I × Bool → I × Bool → R := by + classical + exact fun x y => if y = (x.1, !x.2) then 1 else 0 + +/-- The quadratic function on an orthogonal family of hyperbolic planes. -/ +def hyperbolicQ {I : Type*} [Fintype I] (x : I × Bool → R) : R := + ∑ i, x (i, false) * x (i, true) + +/-- The canonical symmetric companion of `hyperbolicQ`. -/ +def hyperbolicCompanion {I : Type*} [Fintype I] : + (I × Bool → R) →ₗ[R] (I × Bool → R) →ₗ[R] R := + LinearMap.mk₂ R + (fun x y => ∑ i, + (x (i, false) * y (i, true) + x (i, true) * y (i, false))) + (by + intro x z y + rw [← Finset.sum_add_distrib] + apply Finset.sum_congr rfl + intro i _ + simp only [Pi.add_apply] + ring) + (by + intro a x y + rw [Finset.smul_sum] + apply Finset.sum_congr rfl + intro i _ + simp only [Pi.smul_apply, smul_eq_mul] + ring) + (by + intro x y z + rw [← Finset.sum_add_distrib] + apply Finset.sum_congr rfl + intro i _ + simp only [Pi.add_apply] + ring) + (by + intro a x y + rw [Finset.smul_sum] + apply Finset.sum_congr rfl + intro i _ + simp only [Pi.smul_apply, smul_eq_mul] + ring) + +/-- A canonical orthogonal sum of hyperbolic planes, indexed by `I`. -/ +def hyperbolicPair {I : Type*} [Fintype I] : + QuadraticPair R (I × Bool → R) where + q := hyperbolicQ + b := hyperbolicCompanion + map_smul a x := by + change (∑ i, (a * x (i, false)) * (a * x (i, true))) = + a ^ 2 * ∑ i, x (i, false) * x (i, true) + rw [Finset.mul_sum] + apply Finset.sum_congr rfl + intro i _ + ring + polar x y := by + rw [hyperbolicQ, hyperbolicQ, hyperbolicQ] + change (∑ i, (x (i, false) + y (i, false)) * + (x (i, true) + y (i, true))) = + (∑ i, x (i, false) * x (i, true)) + + (∑ i, y (i, false) * y (i, true)) + + (∑ i, (x (i, false) * y (i, true) + x (i, true) * y (i, false))) + rw [← Finset.sum_add_distrib, ← Finset.sum_add_distrib] + apply Finset.sum_congr rfl + intro i _ + ring + symmetric x y := by + change (∑ i, (x (i, false) * y (i, true) + x (i, true) * y (i, false))) = + ∑ i, (y (i, false) * x (i, true) + y (i, true) * x (i, false)) + apply Finset.sum_congr rfl + intro i _ + ring + +omit [NoHalfOne R] [NoSelfSumZero R] in +theorem hyperbolicPair_basisQ {I : Type*} [Fintype I] [DecidableEq I] + (x : I × Bool) : + (hyperbolicPair (R := R) (I := I)).q (basisVector x) = 0 := by + rcases x with ⟨i, b⟩ + cases b <;> simp [hyperbolicPair, hyperbolicQ, basisVector] + +omit [NoHalfOne R] [NoSelfSumZero R] in +theorem hyperbolicPair_gram {I : Type*} [Fintype I] [DecidableEq I] + (x y : I × Bool) : + (hyperbolicPair (R := R) (I := I)).b (basisVector x) (basisVector y) = + hyperbolicGram I x y := by + classical + rcases x with ⟨i, b⟩ + rcases y with ⟨j, c⟩ + cases b <;> cases c <;> + simp [hyperbolicPair, hyperbolicCompanion, hyperbolicGram, basisVector] + +/-- A coordinate isometry records the complete reindexing of quadratic basis +labels and companion Gram coefficients. For free modules with unique bases, +this is exactly the coefficient-level content of an isometry. -/ +structure CoefficientIsometry {I J : Type*} + (q₁ : I → R) (g₁ : I → I → R) + (q₂ : J → R) (g₂ : J → J → R) where + toEquiv : I ≃ J + map_q : ∀ i, q₂ (toEquiv i) = q₁ i + map_g : ∀ i j, g₂ (toEquiv i) (toEquiv j) = g₁ i j + +/-- Reindex a finite coordinate module along an equivalence. -/ +def reindexLinearEquiv {I J : Type*} (E : I ≃ J) : + (I → R) ≃ₗ[R] (J → R) := + LinearEquiv.piCongrLeft R (fun _ : J => R) E + +omit [NoHalfOne R] [NoSelfSumZero R] in +theorem reindexLinearEquiv_basisVector {I J : Type*} + [DecidableEq I] [DecidableEq J] (E : I ≃ J) (i : I) : + reindexLinearEquiv (R := R) E (basisVector i) = basisVector (E i) := by + ext j + simp only [reindexLinearEquiv, LinearEquiv.piCongrLeft, + LinearEquiv.piCongrLeft'] + by_cases h : j = E i + · subst j + simp [basisVector] + · have hs : E.symm j ≠ i := by + intro hs + apply h + rw [← E.apply_symm_apply j, hs] + simp [basisVector, hs, h] + +/-- Pull a quadratic pair back along a coordinate reindexing. -/ +def QuadraticPair.reindex {I J : Type*} + (Q : QuadraticPair R (J → R)) (E : I ≃ J) : + QuadraticPair R (I → R) where + q x := Q.q (reindexLinearEquiv E x) + b := Q.b.compl₁₂ (reindexLinearEquiv E).toLinearMap + (reindexLinearEquiv E).toLinearMap + map_smul a x := by + simpa using Q.map_smul a (reindexLinearEquiv E x) + polar x y := by + simpa using Q.polar (reindexLinearEquiv E x) (reindexLinearEquiv E y) + symmetric x y := + Q.symmetric (reindexLinearEquiv E x) (reindexLinearEquiv E y) + +omit [NoHalfOne R] [NoSelfSumZero R] in +/-- A coefficient isometry between finite quadratic-pair presentations lifts +to an actual linear isometry of the pairs. -/ +theorem coefficientIsometry_to_pairIsometry + {I J : Type*} [Fintype I] [DecidableEq I] + [Fintype J] [DecidableEq J] + (q₁ : I → R) (g₁ : I → I → R) + (Q : QuadraticPair R (J → R)) + (C : CoefficientIsometry q₁ g₁ + (fun j => Q.q (basisVector j)) + (fun j k => Q.b (basisVector j) (basisVector k))) + (Q₁ : QuadraticPair R (I → R)) + (hq₁ : ∀ i, Q₁.q (basisVector i) = q₁ i) + (hg₁ : ∀ i j, + Q₁.b (basisVector i) (basisVector j) = g₁ i j) : + Nonempty (QuadraticPair.Isometry Q₁ Q) := by + let Qr := Q.reindex C.toEquiv + have hq : ∀ i, Q₁.q (basisVector i) = Qr.q (basisVector i) := by + intro i + rw [hq₁ i] + change q₁ i = Q.q (reindexLinearEquiv C.toEquiv (basisVector i)) + rw [reindexLinearEquiv_basisVector] + exact (C.map_q i).symm + have hg : ∀ i j, + Q₁.b (basisVector i) (basisVector j) = + Qr.b (basisVector i) (basisVector j) := by + intro i j + rw [hg₁ i j] + change g₁ i j = Q.b + (reindexLinearEquiv C.toEquiv (basisVector i)) + (reindexLinearEquiv C.toEquiv (basisVector j)) + rw [reindexLinearEquiv_basisVector, reindexLinearEquiv_basisVector] + exact (C.map_g i j).symm + obtain ⟨hqall, hball⟩ := + quadraticPair_coefficients_determine Q₁ Qr hq hg + exact ⟨⟨reindexLinearEquiv C.toEquiv, + fun x => by exact (congrFun hqall x).symm, + fun x y => by + exact (DFunLike.congr_fun (DFunLike.congr_fun hball x) y).symm⟩⟩ + +omit [NoHalfOne R] [NoSelfSumZero R] in +/-- The canonical hyperbolic coefficient presentation therefore lifts to an +actual quadratic-pair isometry. -/ +theorem hyperbolicCoefficientIsometry_to_pairIsometry + {I J : Type*} [Fintype I] [DecidableEq I] + [Fintype J] [DecidableEq J] + (Q : QuadraticPair R (J → R)) + (C : CoefficientIsometry + (fun _ : I × Bool => (0 : R)) (hyperbolicGram I) + (fun j => Q.q (basisVector j)) + (fun j k => Q.b (basisVector j) (basisVector k))) : + Nonempty (QuadraticPair.Isometry + (hyperbolicPair (R := R) (I := I)) Q) := by + apply coefficientIsometry_to_pairIsometry _ _ Q C + (hyperbolicPair (R := R) (I := I)) + · exact hyperbolicPair_basisQ + · exact hyperbolicPair_gram + +/-- End-to-end Hessenberg proxy theorem: a regular symmetric balanced +permutation Gram presentation admits explicit hyperbolic coordinates. In +those coordinates all quadratic basis labels vanish and the entire Gram +function is the canonical orthogonal sum of hyperbolic planes. -/ +theorem symmetric_balanced_permutationGram_hyperbolic + (q : Fin n → R) (g : Fin n → Fin n → R) (p : Equiv.Perm (Fin n)) + (hgram : ∀ i j, g i j = if j = p i then 1 else 0) + (hsymmetric : ∀ i j, g i j = g j i) + (hbalanced : ∀ i, g i i = q i + q i) : + ∃ (E : PairRep p × Bool ≃ Fin n), + (∀ x, q (E x) = 0) ∧ + (∀ x y, g (E x) (E y) = hyperbolicGram (PairRep p) x y) := by + classical + obtain ⟨hfixed, hinv, hq⟩ := + symmetric_balanced_permutationGram q g p hgram hsymmetric hbalanced + let E := matchingEquiv p hfixed hinv + refine ⟨E, fun x => hq (E x), ?_⟩ + intro x y + rw [hgram] + rw [matchingEquiv_mate p hfixed hinv x] + rw [hyperbolicGram] + by_cases h : y = (x.1, !x.2) + · simp [h, E] + · have hE : E y ≠ matchingEquiv p hfixed hinv (x.1, !x.2) := + fun he => h (E.injective he) + simp [h, hE] + +/-- Classification phrased as a coefficient isometry from a canonical +orthogonal sum of hyperbolic planes to the given regular presentation. -/ +theorem symmetric_balanced_permutationGram_isometric_hyperbolic + (q : Fin n → R) (g : Fin n → Fin n → R) (p : Equiv.Perm (Fin n)) + (hgram : ∀ i j, g i j = if j = p i then 1 else 0) + (hsymmetric : ∀ i j, g i j = g j i) + (hbalanced : ∀ i, g i i = q i + q i) : + Nonempty (CoefficientIsometry + (fun _ : PairRep p × Bool => (0 : R)) + (hyperbolicGram (PairRep p)) q g) := by + obtain ⟨E, hq, hg⟩ := symmetric_balanced_permutationGram_hyperbolic + q g p hgram hsymmetric hbalanced + exact ⟨⟨E, hq, hg⟩⟩ + +/-- Once the unique-base certificate is supplied, symmetry and balance are +derived from the quadratic pair itself and Lean constructs the complete +hyperbolic coordinate isometry. -/ +theorem regularCertificate_isometric_hyperbolic + [IsCancelAdd R] + (Q : QuadraticPair R (Fin n → R)) + (C : RegularPermutationCertificate Q) : + Nonempty (CoefficientIsometry + (fun _ : PairRep C.perm × Bool => (0 : R)) + (hyperbolicGram (PairRep C.perm)) Q.basisQ Q.gram) := by + apply symmetric_balanced_permutationGram_isometric_hyperbolic + Q.basisQ Q.gram C.perm C.gram_eq + · intro i j + exact Q.symmetric (coordinateVector i) (coordinateVector j) + · intro i + exact companion_diagonal Q (coordinateVector i) + +/-- The same hypotheses force the rank to be even; the witness is the number +of order-selected matching edges. -/ +theorem symmetric_balanced_permutationGram_even + (q : Fin n → R) (g : Fin n → Fin n → R) (p : Equiv.Perm (Fin n)) + (hgram : ∀ i j, g i j = if j = p i then 1 else 0) + (hsymmetric : ∀ i j, g i j = g j i) + (hbalanced : ∀ i, g i i = q i + q i) : Even n := by + letI : Finite (PairRep p) := + Finite.of_injective (fun r : PairRep p => r.1) Subtype.val_injective + letI : Fintype (PairRep p) := Fintype.ofFinite _ + obtain ⟨E, -, -⟩ := symmetric_balanced_permutationGram_hyperbolic + q g p hgram hsymmetric hbalanced + have hc := Fintype.card_congr E + simp only [Fintype.card_prod, Fintype.card_bool, Fintype.card_fin] at hc + refine ⟨Fintype.card (PairRep p), ?_⟩ + omega + +/-- A regular permutation certificate also forces even rank. -/ +theorem regularCertificate_even [IsCancelAdd R] + (Q : QuadraticPair R (Fin n → R)) + (C : RegularPermutationCertificate Q) : Even n := by + apply symmetric_balanced_permutationGram_even + Q.basisQ Q.gram C.perm C.gram_eq + · intro i j + exact Q.symmetric (coordinateVector i) (coordinateVector j) + · intro i + exact companion_diagonal Q (coordinateVector i) + +/-- Concrete finite-CNF proxy corollary. No abstract semiring hypotheses +remain: `MvPolynomial (Fin d) Nat` is the exact polynomial model used for the +closed Hessenberg fragment. -/ +theorem hessenbergPolynomial_permutationGram_hyperbolic + (d n : Nat) + (q : Fin n → MvPolynomial (Fin d) Nat) + (g : Fin n → Fin n → MvPolynomial (Fin d) Nat) + (p : Equiv.Perm (Fin n)) + (hgram : ∀ i j, g i j = if j = p i then 1 else 0) + (hsymmetric : ∀ i j, g i j = g j i) + (hbalanced : ∀ i, g i i = q i + q i) : + ∃ (E : PairRep p × Bool ≃ Fin n), + (∀ x, q (E x) = 0) ∧ + (∀ x y, g (E x) (E y) = + hyperbolicGram (PairRep p) x y) := by + exact symmetric_balanced_permutationGram_hyperbolic + q g p hgram hsymmetric hbalanced + +/-- Flagship finite-CNF proxy theorem. For an actual regular quadratic pair +over the polynomial Hessenberg model, the cited unique-base output is the +single bridge hypothesis; the complete hyperbolic classification after that +bridge is kernel-checked. -/ +theorem hessenbergPolynomial_regularCertificate_isometric_hyperbolic + (d n : Nat) + (Q : QuadraticPair (MvPolynomial (Fin d) Nat) + (Fin n → MvPolynomial (Fin d) Nat)) + (C : RegularPermutationCertificate Q) : + Nonempty (CoefficientIsometry + (fun _ : PairRep C.perm × Bool => + (0 : MvPolynomial (Fin d) Nat)) + (hyperbolicGram (PairRep C.perm)) Q.basisQ Q.gram) := + regularCertificate_isometric_hyperbolic Q C + +/-- Fully internal finite-CNF Hessenberg collapse. Starting only from an +actual quadratic pair and invertibility of its companion Gram matrix, Lean +constructs the permutation certificate, proves even rank, and returns a +coefficient isometry with a canonical orthogonal sum of hyperbolic planes. -/ +theorem hessenbergPolynomial_matrixRegular_hyperbolic + (d n : Nat) + (Q : QuadraticPair (MvPolynomial (Fin d) Nat) + (Fin n → MvPolynomial (Fin d) Nat)) + (hreg : Q.MatrixRegular) : + Even n ∧ + ∃ p : Equiv.Perm (Fin n), + Nonempty (CoefficientIsometry + (fun _ : PairRep p × Bool => + (0 : MvPolynomial (Fin d) Nat)) + (hyperbolicGram (PairRep p)) Q.basisQ Q.gram) := by + obtain ⟨C⟩ := + hessenbergPolynomial_regular_permutationCertificate Q hreg + exact ⟨regularCertificate_even Q C, C.perm, + regularCertificate_isometric_hyperbolic Q C⟩ + +/-- Gold--Arf-style endpoint for the finite-CNF Hessenberg proxy. Its input +is the paper's actual regularity condition--bijectivity of the companion +adjoint--rather than a permutation or matching hypothesis. The proof +internally passes through dual coordinates, invertible-matrix monomiality, +balance, and the fixed-point-free matching construction. -/ +theorem hessenbergPolynomial_regular_hyperbolic + (d n : Nat) + (Q : QuadraticPair (MvPolynomial (Fin d) Nat) + (Fin n → MvPolynomial (Fin d) Nat)) + (hreg : Q.Regular) : + Even n ∧ + ∃ p : Equiv.Perm (Fin n), + Nonempty (CoefficientIsometry + (fun _ : PairRep p × Bool => + (0 : MvPolynomial (Fin d) Nat)) + (hyperbolicGram (PairRep p)) Q.basisQ Q.gram) := + hessenbergPolynomial_matrixRegular_hyperbolic d n Q + (Q.matrixRegular_of_regular hreg) + +/-- Strongest endpoint: every regular quadratic pair over the finite +polynomial Hessenberg model is actually isometric, as a pair preserving both +`q` and `b`, to a canonical orthogonal sum of hyperbolic planes. -/ +theorem hessenbergPolynomial_regular_isometric_hyperbolic + (d n : Nat) + (Q : QuadraticPair (MvPolynomial (Fin d) Nat) + (Fin n → MvPolynomial (Fin d) Nat)) + (hreg : Q.Regular) : + Even n ∧ + ∃ p : Equiv.Perm (Fin n), + Nonempty (QuadraticPair.Isometry + (hyperbolicPair + (R := MvPolynomial (Fin d) Nat) (I := PairRep p)) Q) := by + have hmreg := Q.matrixRegular_of_regular hreg + obtain ⟨C⟩ := + hessenbergPolynomial_regular_permutationCertificate Q hmreg + obtain ⟨CI⟩ := regularCertificate_isometric_hyperbolic Q C + exact ⟨regularCertificate_even Q C, C.perm, + hyperbolicCoefficientIsometry_to_pairIsometry Q CI⟩ + +end HyperbolicMatching + +section CancellativeTargetBoundary + +variable {Source Target : Type*} [AddCommMonoid Source] + [AddCancelCommMonoid Target] + +/-- An additive invariant into a cancellative monoid kills any class that its +chosen observation identifies with its double. In the thermograph +application the external input is that freezing gives the same wall pair to a +nonzero infinitesimal and its double. -/ +theorem additiveInvariant_eq_zero_of_eq_double + (encode : Source →+ Target) (x : Source) + (h : encode x = encode (x + x)) : encode x = 0 := by + rw [map_add] at h + have hcancel : encode x + 0 = encode x + encode x := by simpa using h + exact (add_left_cancel hcancel).symm + +end CancellativeTargetBoundary + +section ParityCollapse + +variable {G : Type*} [AddCommGroup G] + +/-- Once diagonalization has reduced every scalar-extension class to a natural +multiple of one loop and the hyperbolic plane has imposed `2 • loop = 0`, the +multiple depends only on rank parity. The paper supplies the classical +diagonalization and identifies the loop for the two supertropical companion +lifts; this theorem checks the universal group-theoretic collapse. -/ +theorem twoTorsion_nsmul_eq_parity (loop : G) (htwo : 2 • loop = 0) + (n : Nat) : n • loop = (n % 2) • loop := by + induction n using Nat.strong_induction_on with + | h n ih => + rcases n with _ | _ | n + · simp + · simp + · calc + (n + 2) • loop = n • loop + 2 • loop := by rw [add_nsmul] + _ = n • loop := by rw [htwo, add_zero] + _ = (n % 2) • loop := ih n (by omega) + _ = ((n + 2) % 2) • loop := by congr 1; omega + +/-- A parity detector proves that the two-torsion loop surviving the +hyperbolic relation is nonzero. -/ +theorem loop_ne_zero_of_parityDetector (loop : G) + (parity : G →+ ZMod 2) (hloop : parity loop = 1) : loop ≠ 0 := by + intro hzero + rw [hzero, map_zero] at hloop + exact zero_ne_one hloop + +/-- The exact presentation delivered by diagonalization, normalization, and +the hyperbolic change of basis in the supertropical ring-image quotient. + +The fields deliberately record the mathematical joins separately: `cyclic` +is the diagonalization/normalization statement, `hyperbolic` is the relation +coming from the two presentations of the classical hyperbolic plane, and +`parity_loop` is the noncollapse certificate. -/ +structure RankParityPresentation (G : Type*) [AddCommGroup G] where + loop : G + parity : G →+ ZMod 2 + cyclic : ∀ x : G, ∃ n : Int, x = n • loop + hyperbolic : 2 • loop = 0 + parity_loop : parity loop = 1 + +/-- The inverse to rank parity: a residue class acts on the distinguished +loop. The hyperbolic relation is precisely what makes this well-defined. -/ +def RankParityPresentation.fromParity (P : RankParityPresentation G) : + ZMod 2 →+ G := + ZMod.lift 2 ⟨zmultiplesHom G P.loop, by + change (2 : Int) • P.loop = 0 + simpa only [ofNat_zsmul] using P.hyperbolic⟩ + +/-- A group with the exact ring-image presentation is not merely generated +by an element of order at most two: its supplied parity detector is an +additive equivalence with `ZMod 2`. This is the universal group-theoretic +endpoint of the characteristic-not-two supertropical argument. -/ +def RankParityPresentation.equivZModTwo + (P : RankParityPresentation G) : G ≃+ ZMod 2 where + toFun := P.parity + invFun := P.fromParity + map_add' := P.parity.map_add + left_inv x := by + obtain ⟨n, rfl⟩ := P.cyclic x + simp [RankParityPresentation.fromParity, P.parity_loop] + right_inv z := by + obtain ⟨n, rfl⟩ := ZMod.intCast_surjective z + simp [RankParityPresentation.fromParity, P.parity_loop] + +/-- Named end-to-end group theorem used by both the canonical balanced and +the polar-companion scalar-extension presentations. -/ +theorem universal_ringImage_quotient_equiv_zmodTwo + (P : RankParityPresentation G) : Nonempty (G ≃+ ZMod 2) := + ⟨P.equivZModTwo⟩ + +end ParityCollapse + +section ClassicalDiagonalization + +open QuadraticMap + +variable {K V : Type*} [Field K] [AddCommGroup V] [Module K V] + [FiniteDimensional K V] + +/-- Mathlib's characteristic-not-two diagonalization theorem, exposed with +the ordinary hypothesis `2 ≠ 0` instead of the implementation typeclass +`Invertible 2`. This supplies the classical diagonalization join in the +supertropical ring-image proof. -/ +theorem classical_form_diagonalizes_of_two_ne_zero (h2 : (2 : K) ≠ 0) + (Q : QuadraticForm K V) : + ∃ w : Fin (Module.finrank K V) → K, + Equivalent Q (weightedSumSquares K w) := by + letI : Invertible (2 : K) := invertibleOfNonzero h2 + exact Q.equivalent_weightedSumSquares + +end ClassicalDiagonalization + +end Ogdoad.SemiringQuadratic diff --git a/formal/Ogdoad/WittRamification.lean b/formal/Ogdoad/WittRamification.lean new file mode 100644 index 00000000..c17f55f3 --- /dev/null +++ b/formal/Ogdoad/WittRamification.lean @@ -0,0 +1,360 @@ +import Mathlib + +/-! +# Finite triangular Artin--Schreier reduction in characteristic two + +Let `k` be a perfect coefficient field of characteristic two. A finite +principal part is represented by a finitely supported function `f : ℕ →₀ k`, +where `f n` is the coefficient of the pole of order `n`. For `m > 0`, adding + +```text +(c * pi^(-m))^2 + c * pi^(-m) +``` + +changes only pole orders `2*m` and `m`. Choosing `c^2 = f (2*m)` therefore +kills an even pole and introduces data only at the strictly smaller order +`m`. This is the triangular algebra behind ramified Artin--Schreier +normalization. + +The file proves a bounded descending normalization algorithm. Every positive +even coefficient through the chosen bound vanishes, coefficients above the +bound are unchanged, and the output at order `j` depends only on input +coefficients at orders at least `j`. Thus a bounded principal part reduces to +odd poles by finitely many exact Artin--Schreier moves. + +The square-root operation is supplied explicitly, together with its defining +identity. Finite residue fields provide such an operation because they are +perfect, but no finite-field or local-field API is needed for the triangular +argument itself. + +This file does not formalize substitution along a ramified extension, unit +power-series expansion, Witt residues, or the compatibility of the resulting +coordinates with restriction and Scharlau transfer. Those remain inputs to a +positive, unbounded local coordinate law. They cannot produce the literal +fixed finite query-independent arena originally proposed: the final section +proves abstractly that finitely many P/N observations cannot distinguish an +infinite family of source classes. Applying that obstruction to Witt classes +still requires the external mathematical proof that the selected rank-two +family is infinite. +-/ + +namespace Ogdoad.WittRamification + +/-- A finite principal part, indexed by pole order. Index zero is retained so +that we can state explicitly that positive-pole Artin--Schreier normalization +does not change it. In a ramified base change, any new constant contribution +comes from the unit power-series expansion before this reduction begins. -/ +abbrev PrincipalPart (k : Type*) [Zero k] := ℕ →₀ k + +section CharacteristicTwo + +variable {k : Type*} [Field k] + +/-- The elementary change in principal-part coefficients caused by adding +`(c*pi^(-m))^2 + c*pi^(-m)`. -/ +noncomputable def elementaryAS (m : ℕ) (c : k) : PrincipalPart k := + Finsupp.single (2 * m) (c ^ 2) + Finsupp.single m c + +@[simp] +theorem elementaryAS_apply_of_ne (m j : ℕ) (c : k) + (hjm : j ≠ m) (hj2m : j ≠ 2 * m) : + elementaryAS m c j = 0 := by + simp [elementaryAS, hjm, hj2m] + +/-- Cancel the coefficient at order `2*m` by one elementary +Artin--Schreier move. -/ +noncomputable def cancelPole (root : k → k) (f : PrincipalPart k) (m : ℕ) : + PrincipalPart k := + f + elementaryAS m (root (f (2 * m))) + +/-- A cancellation is literally addition of an elementary +Artin--Schreier term; no quotient relation is hidden in the algorithm. -/ +theorem cancelPole_eq_add_elementaryAS (root : k → k) + (f : PrincipalPart k) (m : ℕ) : + cancelPole root f m = f + elementaryAS m (root (f (2 * m))) := + rfl + +/-- The chosen square root kills the even top pole. The positivity hypothesis +keeps the newly introduced order `m` distinct from the killed order `2*m`. -/ +@[simp] +theorem cancelPole_top_eq_zero (root : k → k) + [CharP k 2] + (hsq : ∀ a : k, root a ^ 2 = a) + (f : PrincipalPart k) {m : ℕ} (hm : 0 < m) : + cancelPole root f m (2 * m) = 0 := by + have hmne : 2 * m ≠ m := by omega + have hmn : m ≠ 2 * m := hmne.symm + simp only [cancelPole, elementaryAS, Finsupp.add_apply, + Finsupp.single_eq_same, Finsupp.single_apply, if_neg hmn, + hsq, add_zero] + exact CharTwo.add_self_eq_zero (f (2 * m) : k) + +/-- One elementary cancellation is triangular: it changes nothing above its +top pole order. -/ +theorem cancelPole_apply_of_lt (root : k → k) + (f : PrincipalPart k) {m j : ℕ} (h : 2 * m < j) : + cancelPole root f m j = f j := by + have hj2m : j ≠ 2 * m := by omega + have hjm : j ≠ m := by omega + simp [cancelPole, elementaryAS, hj2m, hjm] + +/-- Cancelling a positive pole does not change the constant coefficient. -/ +theorem cancelPole_zero (root : k → k) + (f : PrincipalPart k) {m : ℕ} (hm : 0 < m) : + cancelPole root f m 0 = f 0 := by + have h0m : 0 ≠ m := by omega + have h02m : 0 ≠ 2 * m := by omega + simp [cancelPole, elementaryAS, h0m, h02m] + +/-- Perform the cancellation at order `n` when `n` is positive and even. +The witness is selected only from the arithmetic proposition, so the selected +half is independent of the coefficient vector. -/ +noncomputable def reduceAt (root : k → k) (n : ℕ) + (f : PrincipalPart k) : PrincipalPart k := by + classical + exact if h : ∃ m : ℕ, 0 < m ∧ n = 2 * m then + cancelPole root f h.choose + else + f + +/-- A positive even coefficient is zero after its reduction step. -/ +theorem reduceAt_self_eq_zero (root : k → k) + [CharP k 2] + (hsq : ∀ a : k, root a ^ 2 = a) + (f : PrincipalPart k) {n : ℕ} + (h : ∃ m : ℕ, 0 < m ∧ n = 2 * m) : + reduceAt root n f n = 0 := by + rw [reduceAt, dif_pos h] + calc + cancelPole root f h.choose n = + cancelPole root f h.choose (2 * h.choose) := + congrArg (cancelPole root f h.choose) h.choose_spec.2 + _ = 0 := cancelPole_top_eq_zero root hsq f + (m := h.choose) h.choose_spec.1 + +/-- Reducing order `n` changes no coefficient of strictly larger order. -/ +theorem reduceAt_apply_of_lt (root : k → k) + (f : PrincipalPart k) {n j : ℕ} (hnj : n < j) : + reduceAt root n f j = f j := by + rw [reduceAt] + split_ifs with h + · apply cancelPole_apply_of_lt + calc + 2 * h.choose = n := h.choose_spec.2.symm + _ < j := hnj + · rfl + +/-- Agreement of two inputs from order `j` upward survives one reduction. +This is the single-step dependency statement used in the global triangularity +theorem below. -/ +theorem reduceAt_congr_above (root : k → k) + {f g : PrincipalPart k} {j n : ℕ} + (hfg : ∀ q, j ≤ q → f q = g q) : + ∀ q, j ≤ q → reduceAt root n f q = reduceAt root n g q := by + intro q hjq + rw [reduceAt, reduceAt] + split_ifs with h + · have hn : n = 2 * h.choose := h.choose_spec.2 + by_cases hnj : n < j + · rw [cancelPole_apply_of_lt root f (hn ▸ lt_of_lt_of_le hnj hjq)] + rw [cancelPole_apply_of_lt root g (hn ▸ lt_of_lt_of_le hnj hjq)] + exact hfg q hjq + · have hjn : j ≤ n := Nat.le_of_not_gt hnj + have htop : f (2 * h.choose) = g (2 * h.choose) := by + rw [← hn] + exact hfg n hjn + simp only [cancelPole, Finsupp.add_apply] + rw [htop] + rw [hfg q hjq] + · exact hfg q hjq + +/-- A single allowed Artin--Schreier move on principal parts. -/ +def ASMove (f g : PrincipalPart k) : Prop := + ∃ (m : ℕ) (c : k), g = f + elementaryAS m c + +/-- Reachability by finitely many elementary Artin--Schreier moves. -/ +abbrev ASReachable (f g : PrincipalPart k) : Prop := + Relation.ReflTransGen ASMove f g + +/-- Every `reduceAt` step is either the identity or one exact elementary +Artin--Schreier move. -/ +theorem reduceAt_reachable (root : k → k) (n : ℕ) + (f : PrincipalPart k) : + ASReachable f (reduceAt root n f) := by + rw [reduceAt] + split_ifs with h + · apply Relation.ReflTransGen.single + exact ⟨h.choose, root (f (2 * h.choose)), rfl⟩ + · exact Relation.ReflTransGen.refl + +/-- Positive-order reduction preserves the constant coefficient. -/ +theorem reduceAt_zero (root : k → k) (n : ℕ) + (f : PrincipalPart k) : + reduceAt root n f 0 = f 0 := by + rw [reduceAt] + split_ifs with h + · exact cancelPole_zero root f h.choose_spec.1 + · rfl + +/-- Descending finite normalization. At stage `N+1` it first cancels that +order, then recursively treats all lower orders. -/ +noncomputable def normalizeUpTo (root : k → k) : + ℕ → PrincipalPart k → PrincipalPart k + | 0, f => f + | N + 1, f => normalizeUpTo root N (reduceAt root (N + 1) f) + +@[simp] +theorem normalizeUpTo_zero (root : k → k) (f : PrincipalPart k) : + normalizeUpTo root 0 f = f := + rfl + +@[simp] +theorem normalizeUpTo_succ (root : k → k) + (N : ℕ) (f : PrincipalPart k) : + normalizeUpTo root (N + 1) f = + normalizeUpTo root N (reduceAt root (N + 1) f) := + rfl + +/-- The bounded normalization changes no coefficient above its bound. -/ +theorem normalizeUpTo_apply_of_lt (root : k → k) + (f : PrincipalPart k) {N j : ℕ} (hNj : N < j) : + normalizeUpTo root N f j = f j := by + induction N generalizing f with + | zero => rfl + | succ N ih => + rw [normalizeUpTo_succ, ih] + · exact reduceAt_apply_of_lt root f hNj + · omega + +/-- Descending positive-pole normalization never changes the constant +coefficient. -/ +@[simp] +theorem normalizeUpTo_zero_apply (root : k → k) + (f : PrincipalPart k) (N : ℕ) : + normalizeUpTo root N f 0 = f 0 := by + induction N generalizing f with + | zero => rfl + | succ N ih => + rw [normalizeUpTo_succ, ih, reduceAt_zero] + +/-- Every positive even pole through the selected bound is eliminated. -/ +theorem normalizeUpTo_even_eq_zero (root : k → k) + [CharP k 2] + (hsq : ∀ a : k, root a ^ 2 = a) + (f : PrincipalPart k) {N n : ℕ} + (hnpos : 0 < n) (hnN : n ≤ N) (hneven : Even n) : + normalizeUpTo root N f n = 0 := by + induction N generalizing f with + | zero => omega + | succ N ih => + rw [normalizeUpTo_succ] + rcases lt_or_eq_of_le hnN with hnlt | rfl + · exact ih (reduceAt root (N + 1) f) (by omega) + · rw [normalizeUpTo_apply_of_lt root _ (by omega)] + apply reduceAt_self_eq_zero root hsq + rcases hneven with ⟨m, hm⟩ + refine ⟨m, ?_, ?_⟩ + · omega + · omega + +/-- Bounded inputs normalize completely to odd positive pole orders. The +first conclusion preserves the bound; the second is the odd-pole normal-form +statement expressed without choosing a parity witness. -/ +theorem normalizeUpTo_isOddNormalForm (root : k → k) + [CharP k 2] + (hsq : ∀ a : k, root a ^ 2 = a) + (f : PrincipalPart k) (N : ℕ) + (hbound : ∀ j, N < j → f j = 0) : + (∀ j, N < j → normalizeUpTo root N f j = 0) ∧ + (∀ j, 0 < j → Even j → normalizeUpTo root N f j = 0) := by + constructor + · intro j hNj + rw [normalizeUpTo_apply_of_lt root f hNj, hbound j hNj] + · intro j hjpos hjeven + by_cases hjN : j ≤ N + · exact normalizeUpTo_even_eq_zero root hsq f hjpos hjN hjeven + · have hNj : N < j := Nat.lt_of_not_ge hjN + rw [normalizeUpTo_apply_of_lt root f hNj, hbound j hNj] + +/-- The finite normalization is triangular: its output at order `j` depends +only on input coefficients at orders `q ≥ j`. -/ +theorem normalizeUpTo_congr_above (root : k → k) + {f g : PrincipalPart k} {j N : ℕ} + (hfg : ∀ q, j ≤ q → f q = g q) : + normalizeUpTo root N f j = normalizeUpTo root N g j := by + induction N generalizing f g with + | zero => exact hfg j le_rfl + | succ N ih => + rw [normalizeUpTo_succ, normalizeUpTo_succ] + apply ih + exact reduceAt_congr_above root hfg + +/-- The complete bounded normalization is connected to its input by a finite +chain of exact elementary Artin--Schreier moves. -/ +theorem normalizeUpTo_reachable (root : k → k) + (f : PrincipalPart k) (N : ℕ) : + ASReachable f (normalizeUpTo root N f) := by + induction N generalizing f with + | zero => exact Relation.ReflTransGen.refl + | succ N ih => + exact (reduceAt_reachable root (N + 1) f).trans + (ih (reduceAt root (N + 1) f)) + +section PerfectCoefficientField + +variable [CharP k 2] [PerfectField k] + +/-- The canonical inverse-Frobenius square root in a perfect characteristic-two +field. Finite residue fields acquire this operation automatically. -/ +noncomputable def perfectSquareRoot (a : k) : k := + (powMulEquiv k 2).symm a + +@[simp] +theorem perfectSquareRoot_sq (a : k) : perfectSquareRoot a ^ 2 = a := + powMulEquiv_symm_pow_p k 2 a + +/-- Specializing the bounded normalizer to the canonical inverse Frobenius +requires no auxiliary choice once the perfect-field structure is present. -/ +noncomputable def perfectNormalizeUpTo (N : ℕ) + (f : PrincipalPart k) : PrincipalPart k := + normalizeUpTo perfectSquareRoot N f + +theorem perfectNormalizeUpTo_isOddNormalForm + (f : PrincipalPart k) (N : ℕ) + (hbound : ∀ j, N < j → f j = 0) : + (∀ j, N < j → perfectNormalizeUpTo N f j = 0) ∧ + (∀ j, 0 < j → Even j → perfectNormalizeUpTo N f j = 0) := by + exact normalizeUpTo_isOddNormalForm perfectSquareRoot + perfectSquareRoot_sq f N hbound + +end PerfectCoefficientField + +end CharacteristicTwo + +section FixedFiniteLocalAccessObstruction + +variable {Class Query : Type*} + +/-- An infinite class family cannot be faithfully encoded by a fixed finite +vector of P/N outcomes. `Query → Bool` is finite when the query type is +finite, irrespective of how the observations are computed. -/ +theorem not_injective_finitePNOutcomeVector + [Infinite Class] [Fintype Query] + (observe : Class → Query → Bool) : + ¬Function.Injective observe := + not_injective_infinite_finite observe + +/-- Collision form of `not_injective_finitePNOutcomeVector`: two distinct +classes give exactly the same answer to every query in the fixed finite +family. -/ +theorem exists_finitePNOutcomeVector_collision + [Infinite Class] [Fintype Query] + (observe : Class → Query → Bool) : + ∃ x y : Class, x ≠ y ∧ ∀ q : Query, observe x q = observe y q := by + obtain ⟨x, y, hxy, hne⟩ := Function.not_injective_iff.mp + (not_injective_finitePNOutcomeVector observe) + exact ⟨x, y, hne, fun q ↦ congrFun hxy q⟩ + +end FixedFiniteLocalAccessObstruction + +end Ogdoad.WittRamification diff --git a/formal/Ogdoad/WittRamifiedRestriction.lean b/formal/Ogdoad/WittRamifiedRestriction.lean new file mode 100644 index 00000000..626adcfd --- /dev/null +++ b/formal/Ogdoad/WittRamifiedRestriction.lean @@ -0,0 +1,167 @@ +import Ogdoad.WittRamification +import Mathlib.FieldTheory.Finite.Trace + +/-! +# Ramified unit-jet substitution + +This file supplies the concrete substitution layer omitted from +`WittRamification`. For a ramified parameter change `pi = u * varpi^e`, the +coefficient `unitInv n r` represents the `r`th coefficient of `u⁻ⁿ`. A base +principal part is expanded into a finite target principal part, then passed +through the exact descending Artin--Schreier normalizer proved in +`WittRamification`. + +The construction kernel-checks the unit-jet coefficient formula, a sharp +finite support bound, preservation of the unit-jet constant term by +normalization, elimination of every positive even pole, and triangular +dependence. Identifying a supplied jet with the coefficients of an actual +local-field unit, and the separate unramified/ramified generator calculation, +remain the local quadratic-form bridge. +-/ + +namespace Ogdoad.WittRamifiedRestriction + +open scoped CharTwo + +noncomputable section + +abbrev PrincipalPart := WittRamification.PrincipalPart + +variable {k ell : Type*} [Field k] [Field ell] + +/-- Contribution of one source pole `c*pi⁻ⁿ` after substituting +`pi = u*varpi^e`. Index `j` is the target pole order, so the required unit-jet +coefficient is `e*n-j`. -/ +noncomputable def substitutedTerm (embed : k →+* ell) (e n : ℕ) + (unitInv : ℕ → ℕ → ell) (c : k) : PrincipalPart ell := by + classical + exact Finsupp.onFinset (Finset.range (e * n + 1)) + (fun j ↦ if j ≤ e * n then embed c * unitInv n (e * n - j) else 0) + (by + intro j hj + simp only [Finset.mem_range] + by_contra hnot + have hlt : e * n < j := by omega + simp [Nat.not_le.mpr hlt] at hj) + +@[simp] +theorem substitutedTerm_apply (embed : k →+* ell) (e n : ℕ) + (unitInv : ℕ → ℕ → ell) (c : k) (j : ℕ) : + substitutedTerm embed e n unitInv c j = + if j ≤ e * n then embed c * unitInv n (e * n - j) else 0 := by + classical + exact Finsupp.onFinset_apply + +/-- Finite unit-series substitution on a whole principal part. -/ +noncomputable def substitute (embed : k →+* ell) (e : ℕ) + (unitInv : ℕ → ℕ → ell) (psi : PrincipalPart k) : PrincipalPart ell := + psi.sum fun n c ↦ substitutedTerm embed e n unitInv c + +@[simp] +theorem substitute_apply (embed : k →+* ell) (e : ℕ) + (unitInv : ℕ → ℕ → ell) (psi : PrincipalPart k) (j : ℕ) : + substitute embed e unitInv psi j = + psi.sum fun n c ↦ + if j ≤ e * n then embed c * unitInv n (e * n - j) else 0 := by + classical + simp [substitute] + +/-- Largest possible target pole order after ramification. -/ +def substitutionBound (e : ℕ) (psi : PrincipalPart k) : ℕ := + e * psi.support.sup id + +/-- The substituted principal part has no coefficient above the sharp +ramification-scaled source bound. -/ +theorem substitute_apply_of_bound_lt (embed : k →+* ell) (e : ℕ) + (unitInv : ℕ → ℕ → ell) (psi : PrincipalPart k) {j : ℕ} + (hj : substitutionBound e psi < j) : + substitute embed e unitInv psi j = 0 := by + classical + rw [substitute_apply, Finsupp.sum] + apply Finset.sum_eq_zero + intro n hn + have hnle : n ≤ psi.support.sup id := + Finset.le_sup (f := fun m : ℕ ↦ m) hn + have hen : e * n ≤ substitutionBound e psi := by + exact Nat.mul_le_mul_left e hnle + simp only [if_neg (not_le.mpr (hen.trans_lt hj))] + +/-- The paper's unit-jet constant contribution before applying the residue +field trace. -/ +def unitConstant (embed : k →+* ell) (e : ℕ) + (unitInv : ℕ → ℕ → ell) (psi : PrincipalPart k) : ell := + substitute embed e unitInv psi 0 + +theorem unitConstant_eq_sum (embed : k →+* ell) (e : ℕ) + (unitInv : ℕ → ℕ → ell) (psi : PrincipalPart k) : + unitConstant embed e unitInv psi = + psi.sum fun n c ↦ embed c * unitInv n (e * n) := by + simp [unitConstant] + +section CharacteristicTwo + +variable [CharP ell 2] [PerfectField ell] + +/-- Concrete ramified wild-coordinate transformation: perform the unit-jet +substitution and then the exact descending Artin--Schreier normalization. -/ +noncomputable def transform (embed : k →+* ell) (e : ℕ) + (unitInv : ℕ → ℕ → ell) (psi : PrincipalPart k) : PrincipalPart ell := + WittRamification.perfectNormalizeUpTo (substitutionBound e psi) + (substitute embed e unitInv psi) + +/-- The transformation is a canonical odd-pole normal form. -/ +theorem transform_isOddNormalForm (embed : k →+* ell) (e : ℕ) + (unitInv : ℕ → ℕ → ell) (psi : PrincipalPart k) : + (∀ j, substitutionBound e psi < j → + transform embed e unitInv psi j = 0) ∧ + (∀ j, 0 < j → Even j → transform embed e unitInv psi j = 0) := by + exact WittRamification.perfectNormalizeUpTo_isOddNormalForm + (substitute embed e unitInv psi) (substitutionBound e psi) + (fun j hj ↦ substitute_apply_of_bound_lt embed e unitInv psi hj) + +/-- Artin--Schreier pole cancellation does not alter the new constant created +by the unit jet. -/ +@[simp] +theorem transform_zero (embed : k →+* ell) (e : ℕ) + (unitInv : ℕ → ℕ → ell) (psi : PrincipalPart k) : + transform embed e unitInv psi 0 = unitConstant embed e unitInv psi := by + change WittRamification.normalizeUpTo WittRamification.perfectSquareRoot + (substitutionBound e psi) (substitute embed e unitInv psi) 0 = + substitute embed e unitInv psi 0 + exact WittRamification.normalizeUpTo_zero_apply _ _ _ + +/-- The transformed coefficient at order `j` depends only on substituted +coefficients at orders at least `j`. -/ +theorem transform_congr_above (embed : k →+* ell) (e : ℕ) + (unitInv : ℕ → ℕ → ell) (psi chi : PrincipalPart k) (j N : ℕ) + (hpsi : ∀ q, j ≤ q → substitute embed e unitInv psi q = + substitute embed e unitInv chi q) : + WittRamification.perfectNormalizeUpTo N + (substitute embed e unitInv psi) j = + WittRamification.perfectNormalizeUpTo N + (substitute embed e unitInv chi) j := by + exact WittRamification.normalizeUpTo_congr_above + WittRamification.perfectSquareRoot hpsi + +section Trace + +variable [Algebra (ZMod 2) ell] + +/-- The exact unramified bit contributed by the wild unit jet. -/ +def eta (embed : k →+* ell) (e : ℕ) (unitInv : ℕ → ℕ → ell) + (psi : PrincipalPart k) : ZMod 2 := + Algebra.trace (ZMod 2) ell (transform embed e unitInv psi 0) + +theorem eta_eq_trace_unitConstant (embed : k →+* ell) (e : ℕ) + (unitInv : ℕ → ℕ → ell) (psi : PrincipalPart k) : + eta embed e unitInv psi = + Algebra.trace (ZMod 2) ell (unitConstant embed e unitInv psi) := by + simp [eta] + +end Trace + +end CharacteristicTwo + +end + +end Ogdoad.WittRamifiedRestriction diff --git a/formal/Ogdoad/WittRealization.lean b/formal/Ogdoad/WittRealization.lean new file mode 100644 index 00000000..073e6d3f --- /dev/null +++ b/formal/Ogdoad/WittRealization.lean @@ -0,0 +1,374 @@ +import Ogdoad.ImpartialRealizer +import Ogdoad.WittRamification +import Ogdoad.WittRealizationExact +import Mathlib.Data.DFinsupp.BigOperators +import Mathlib.Data.Finset.Sigma +import Mathlib.LinearAlgebra.DFinsupp + +/-! +# End-to-end realization of presented Witt coordinates + +The Aravire--Jacob classification presents `W_q(F_2(t))` by one constant +coordinate, finitely supported odd principal parts in the residue fields, and +finitely supported ramified Arf bits satisfying one parity relation. Mathlib +does not currently contain the characteristic-two quadratic Witt group or the +Aravire--Jacob residue sequence, so this file takes that cited classification +at exactly one boundary: a linear equivalence from the actual Witt group to +the concrete type `PresentedWitt` below. + +Everything after that boundary is constructed here. In particular this file + +* defines the parity-zero global coordinate space; +* defines every constant, ramified, and trace-character observation; +* proves that the observation family is additive, finitely supported on each + class, and faithful; +* realizes every observation by the literal empty-core impartial `0/*` tail; +* proves one end-to-end theorem saying that equality of all P/N outcomes is + equivalent to equality of presented Witt classes; and +* transports that theorem across any supplied Aravire--Jacob classification + equivalence. + +Thus no coordinate extraction, trace-separation, sparsity, game construction, +or faithfulness join is left as a theorem premise. The sole external bridge +is the classification equivalence itself. +-/ + +namespace Ogdoad.WittRealization + +open Ogdoad.Fifo + +noncomputable section + +set_option linter.unusedSectionVars false +set_option linter.unusedSimpArgs false +set_option linter.unnecessarySimpa false + +/-- Finite principal parts with no constant or positive even pole. -/ +def oddPrincipalPartSubmodule (k : Type*) [Field k] [Algebra F2 k] : + Submodule F2 (WittRamification.PrincipalPart k) where + carrier := {f | f 0 = 0 ∧ ∀ n, 0 < n → Even n → f n = 0} + zero_mem' := by simp + add_mem' := by + rintro f g ⟨hf0, hf⟩ ⟨hg0, hg⟩ + constructor + · simp [hf0, hg0] + · intro n hn he + simp [hf n hn he, hg n hn he] + smul_mem' := by + rintro a f ⟨hf0, hf⟩ + constructor + · simp [hf0] + · intro n hn he + simp [hf n hn he] + +/-- The canonical odd-pole local residue space at one place. -/ +abbrev OddPrincipalPart (k : Type*) [Field k] [Algebra F2 k] := + oddPrincipalPartSubmodule k + +/-- Sum of the finitely supported ramified Arf bits. -/ +def ramifiedParity (Place : Type*) : (Place →₀ F2) →ₗ[F2] F2 := + Finsupp.lsum F2 fun _ ↦ LinearMap.id + +@[simp] +theorem ramifiedParity_apply {Place : Type*} (r : Place →₀ F2) : + ramifiedParity Place r = r.sum fun _ b ↦ b := by + rfl + +/-- Ramified bits satisfying the unique global reciprocity relation. -/ +abbrev RamifiedData (Place : Type*) := LinearMap.ker (ramifiedParity Place) + +/-- Finitely supported odd-pole data in the varying residue fields. -/ +abbrev WildData (Place : Type*) (kappa : Place → Type*) + [∀ v, Field (kappa v)] [∀ v, Algebra F2 (kappa v)] := + Π₀ v : Place, OddPrincipalPart (kappa v) + +/-- The concrete global coordinate object in the Aravire--Jacob +classification: constant, wild residue, and parity-zero ramified residue. -/ +abbrev PresentedWitt (Place : Type*) (kappa : Place → Type*) + [∀ v, Field (kappa v)] [∀ v, Algebra F2 (kappa v)] := + F2 × WildData Place kappa × RamifiedData Place + +/-- Labels for every basis-free binary observation. -/ +inductive Observation (Place : Type*) (kappa : Place → Type*) where + | constant + | ramified (v : Place) + | wild (v : Place) (pole : ℕ) (lambda : kappa v) + +variable {Place : Type*} {kappa : Place → Type*} + [∀ v, Field (kappa v)] [∀ v, Finite (kappa v)] + [∀ v, Algebra F2 (kappa v)] + +/-- The bit attached to one canonical coordinate label. -/ +def bit (x : PresentedWitt Place kappa) : Observation Place kappa → F2 + | .constant => x.1 + | .ramified v => x.2.2.1 v + | .wild v n lambda => + Algebra.trace F2 (kappa v) (lambda * (x.2.1 v).1 n) + +@[simp] +theorem bit_constant (x : PresentedWitt Place kappa) : + bit x .constant = x.1 := + rfl + +@[simp] +theorem bit_ramified (x : PresentedWitt Place kappa) (v : Place) : + bit x (.ramified v) = x.2.2.1 v := + rfl + +@[simp] +theorem bit_wild (x : PresentedWitt Place kappa) + (v : Place) (n : ℕ) (lambda : kappa v) : + bit x (.wild v n lambda) = + Algebra.trace F2 (kappa v) (lambda * (x.2.1 v).1 n) := + rfl + +/-- Orthogonal sum becomes xor at every observation label. -/ +theorem bit_add (x y : PresentedWitt Place kappa) + (o : Observation Place kappa) : + bit (x + y) o = bit x o + bit y o := by + cases o with + | constant => rfl + | ramified v => rfl + | wild v n lambda => + simp only [bit, Prod.fst_add, Prod.snd_add, DFinsupp.add_apply, + Submodule.coe_add, Finsupp.add_apply, mul_add, map_add] + +@[simp] +theorem bit_zero (o : Observation Place kappa) : + bit (0 : PresentedWitt Place kappa) o = 0 := by + cases o <;> simp [bit] + +/-- Trace-character observations determine every presented coordinate. -/ +theorem bit_injective : + Function.Injective (bit (Place := Place) (kappa := kappa)) := by + intro x y hxy + apply Prod.ext + · exact congrFun hxy .constant + · apply Prod.ext + · apply DFinsupp.ext + intro v + apply Subtype.ext + apply Finsupp.ext + intro n + apply WittRealizationExact.traceBitPresentation_injective (kappa v) + apply LinearMap.ext + intro lambda + simpa [WittRealizationExact.traceBitPresentation_apply] using + congrFun hxy (.wild v n lambda) + · apply Subtype.ext + apply Finsupp.ext + intro v + exact congrFun hxy (.ramified v) + +/-- A finite directory containing every nonzero observation of one class. +For each nonzero wild coefficient it includes all trace labels in the finite +residue field; redundancy is deliberate and basis-free. -/ +noncomputable def activeLabels (x : PresentedWitt Place kappa) : + Finset (Observation Place kappa) := by + classical + letI (v : Place) : Fintype (kappa v) := Fintype.ofFinite (kappa v) + let ramified : Finset (Observation Place kappa) := + x.2.2.1.support.image Observation.ramified + let wildSigma : Finset (Σ v : Place, Σ _n : ℕ, kappa v) := + x.2.1.support.sigma fun v ↦ + (x.2.1 v).1.support.sigma fun _n ↦ Finset.univ + let wild : Finset (Observation Place kappa) := + wildSigma.image fun q ↦ Observation.wild q.1 q.2.1 q.2.2 + exact {.constant} ∪ ramified ∪ wild + +/-- Outside the finite active directory every observation is the zero game. -/ +theorem bit_eq_zero_of_not_mem_activeLabels + (x : PresentedWitt Place kappa) (o : Observation Place kappa) + (ho : o ∉ activeLabels x) : bit x o = 0 := by + classical + letI (v : Place) : Fintype (kappa v) := Fintype.ofFinite (kappa v) + cases o with + | constant => + exact False.elim (ho (by simp [activeLabels])) + | ramified v => + have hv : v ∉ x.2.2.1.support := by + intro hv + apply ho + simp only [activeLabels, Finset.mem_union, Finset.mem_singleton] + exact Or.inl (Or.inr (Finset.mem_image.mpr ⟨v, hv, rfl⟩)) + rw [bit_ramified, Finsupp.notMem_support_iff.mp hv] + | wild v n lambda => + by_cases hv : v ∈ x.2.1.support + · by_cases hn : n ∈ (x.2.1 v).1.support + · apply False.elim + apply ho + simp only [activeLabels, Finset.mem_union, Finset.mem_singleton] + apply Or.inr + apply Finset.mem_image.mpr + refine ⟨⟨v, ⟨n, lambda⟩⟩, ?_, rfl⟩ + simp only [Finset.mem_sigma] + exact ⟨hv, hn, Finset.mem_univ _⟩ + · rw [bit_wild, Finsupp.notMem_support_iff.mp hn, mul_zero, map_zero] + · have hv0 : x.2.1 v = 0 := DFinsupp.notMem_support_iff.mp hv + rw [bit_wild, hv0] + simp + +/-- In particular the complete labelled bit function has finite support. -/ +theorem bit_support_finite (x : PresentedWitt Place kappa) : + (Function.support (bit x)).Finite := by + refine (activeLabels x).finite_toSet.subset ?_ + intro o ho + by_contra hnot + exact ho (bit_eq_zero_of_not_mem_activeLabels x o hnot) + +/-- The actual finite sparse family emitted by the compiler. -/ +noncomputable def sparseCompiler (x : PresentedWitt Place kappa) : + Observation Place kappa →₀ F2 := + Finsupp.ofSupportFinite (bit x) (bit_support_finite x) + +@[simp] +theorem sparseCompiler_apply (x : PresentedWitt Place kappa) + (o : Observation Place kappa) : sparseCompiler x o = bit x o := by + simp [sparseCompiler, Finsupp.ofSupportFinite_coe] + +/-- The finite sparse compiler is itself faithful. -/ +theorem sparseCompiler_injective : + Function.Injective (sparseCompiler (Place := Place) (kappa := kappa)) := by + intro x y hxy + apply bit_injective + funext o + simpa only [sparseCompiler_apply] using DFunLike.congr_fun hxy o + +@[simp] +theorem sparseCompiler_zero : + sparseCompiler (0 : PresentedWitt Place kappa) = 0 := by + ext o + simp + +/-- Orthogonal sum is coordinatewise xor on the emitted finite families. -/ +theorem sparseCompiler_add (x y : PresentedWitt Place kappa) : + sparseCompiler (x + y) = sparseCompiler x + sparseCompiler y := by + ext o + simp [bit_add] + +/-- Boolean presentation of a binary charge for the impartial tail compiler. -/ +def bitBool (z : F2) : Bool := decide (z = 1) + +theorem bitBool_eq_false_iff (z : F2) : bitBool z = false ↔ z = 0 := by + by_cases hz : z = 1 + · simp [bitBool, hz] + · have : z = 0 := Ogdoad.zmod2_eq_zero_of_ne_one z hz + simp [bitBool, this] + +/-- The literal normal-play `0/*` arena for one observation. Its FIFO core +has no coins; the terminal one-move tail is present exactly when the bit is +one. -/ +def CoordinateIsP (x : PresentedWitt Place kappa) + (o : Observation Place kappa) : Prop := + ImpartialRealizer.TailWins (⊥ : SimpleGraph Empty) true + (ImpartialRealizer.initial (V := Empty) + (ImpartialRealizer.scoreBit (bitBool (bit x o)))) + +private theorem emptyGraph_isMatching : + IsMatchingGraph (⊥ : SimpleGraph Empty) := by + intro v + exact Empty.elim v + +/-- One labelled arena is a P-position exactly when its Witt coordinate bit +vanishes. -/ +theorem coordinateIsP_iff (x : PresentedWitt Place kappa) + (o : Observation Place kappa) : + CoordinateIsP x o ↔ bit x o = 0 := by + exact (ImpartialRealizer.root_isP_iff_charge_zero + emptyGraph_isMatching le_rfl (bitBool (bit x o))).trans + (bitBool_eq_false_iff (bit x o)) + +/-- Flagship theorem for the presented class: two classes have identical P/N +outcome families exactly when they are equal. -/ +theorem same_outcomes_iff (x y : PresentedWitt Place kappa) : + (∀ o, CoordinateIsP x o ↔ CoordinateIsP y o) ↔ x = y := by + constructor + · intro h + apply bit_injective + funext o + have hz : bit x o = 0 ↔ bit y o = 0 := by + simpa only [coordinateIsP_iff] using h o + by_cases hx0 : bit x o = 0 + · rw [hx0, hz.mp hx0] + · have hy0 : bit y o ≠ 0 := by + intro hy + exact hx0 (hz.mpr hy) + rw [Ogdoad.zmod2_eq_one_of_ne_zero _ hx0, + Ogdoad.zmod2_eq_one_of_ne_zero _ hy0] + · rintro rfl + exact fun _ ↦ Iff.rfl + +/-- The only bridge required from a quadratic Witt-group formalization: the +cited classification equivalence to the concrete coordinates above. -/ +structure ClassificationBridge (W : Type*) [AddCommGroup W] [Module F2 W] + (Place : Type*) (kappa : Place → Type*) + [∀ v, Field (kappa v)] [∀ v, Algebra F2 (kappa v)] where + coordinates : W ≃ₗ[F2] PresentedWitt Place kappa + +/-- End-to-end theorem transported to any Witt group equipped with the cited +classification bridge. No later construction is accepted as a premise. -/ +theorem witt_same_outcomes_iff + {W : Type*} [AddCommGroup W] [Module F2 W] + (bridge : ClassificationBridge W Place kappa) (x y : W) : + (∀ o, CoordinateIsP (bridge.coordinates x) o ↔ + CoordinateIsP (bridge.coordinates y) o) ↔ x = y := by + rw [same_outcomes_iff, bridge.coordinates.injective.eq_iff] + +section PlaceSpikes + +/-- The canonical simple odd pole with coefficient one. -/ +noncomputable def simplePole (K : Type*) [Field K] [Algebra F2 K] : + OddPrincipalPart K := by + classical + refine ⟨Finsupp.single 1 1, ?_⟩ + constructor + · simp + · intro n hn he + by_cases hn1 : n = 1 + · subst n + exact (Nat.not_even_one he).elim + · simp [Finsupp.single_apply, hn1] + +theorem simplePole_ne_zero (K : Type*) [Field K] [Algebra F2 K] : + simplePole K ≠ 0 := by + intro h + have hcoeff := congrArg (fun p : OddPrincipalPart K ↦ p.1 1) h + simpa [simplePole] using hcoeff + +/-- Coordinate class supported by one simple pole at one place. Under the +Aravire--Jacob bridge this is the residue pattern of the paper's binary plane +`[1,P⁻¹]`. -/ +noncomputable def placeSpike (v : Place) : PresentedWitt Place kappa := by + classical + exact (0, DFinsupp.single v (simplePole (kappa v)), 0) + +/-- Distinct places give distinct concrete coordinate classes. -/ +theorem placeSpike_injective : + Function.Injective (placeSpike (Place := Place) (kappa := kappa)) := by + classical + intro v w hvw + by_contra hvne + have hcomponent := congrArg (fun x : PresentedWitt Place kappa ↦ x.2.1 v) hvw + have hwv : v ≠ w := hvne + rw [show (placeSpike (Place := Place) (kappa := kappa) v).2.1 v = + simplePole (kappa v) by simp [placeSpike]] at hcomponent + rw [show (placeSpike (Place := Place) (kappa := kappa) w).2.1 v = 0 by + change (DFinsupp.single (β := fun u ↦ OddPrincipalPart (kappa u)) + w (simplePole (kappa w))) v = 0 + exact DFinsupp.single_eq_of_ne hwv] at hcomponent + exact simplePole_ne_zero (kappa v) hcomponent + +/-- Concrete fixed-finite-family obstruction on the presented Witt space. -/ +theorem not_injective_fixed_finite_outcomes [Infinite Place] + {Query : Type*} [Fintype Query] + (observe : PresentedWitt Place kappa → Query → Bool) : + ¬Function.Injective observe := by + letI : Infinite (PresentedWitt Place kappa) := + Infinite.of_injective placeSpike placeSpike_injective + exact WittRamification.not_injective_finitePNOutcomeVector observe + +end PlaceSpikes + +end + +end Ogdoad.WittRealization diff --git a/formal/Ogdoad/WittRealizationExact.lean b/formal/Ogdoad/WittRealizationExact.lean new file mode 100644 index 00000000..d99120ec --- /dev/null +++ b/formal/Ogdoad/WittRealizationExact.lean @@ -0,0 +1,372 @@ +import Ogdoad.Algebra.ZModTwo + +/-! +# The exact-sequence core of characteristic-two Witt realization + +For `K = F₂(t)`, the cited Milnor--Scharlau sequence has the shape + +```text +0 → C → W → R → C → 0, +``` + +where `C = W_q(F₂)`, `W = W_q(F₂(t))`, and `R` is the direct sum of +the placewise residue groups. All these groups are naturally `F₂`-vector +spaces. This file isolates what follows from exactness alone. + +The main theorem `coordinatesEquivOfProjection` proves that an explicit linear +constant detector `p : W → C` satisfying `p ∘ constant = id` gives faithful +additive coordinates + +```text +W ≃ C × ker(transfer). +``` + +For `F₂(t)`, the intended detector is the unramified `phi0` coordinate in the +Aravire--Jacob decomposition at infinity with canonical uniformizer `1/t`. +Once its additivity and identity on constant forms are proved, +`coordinatesEquivOfProjection` supplies the canonical algebraic coordinates +without a basis choice. The later `splitCoordinatesEquiv` is only a fallback: +Mathlib can extend a basis to manufacture some retraction, but that construction +is deliberately noncomputable and noncanonical and therefore does not settle +functoriality under scalar extension or Scharlau transfer. + +The second main ingredient is `traceBitPresentation`. For every finite +extension `E/F₂`, it embeds a wild coefficient `z : E` into the basis-free +family of bits `lambda ↦ Tr(lambda*z)`. Nondegeneracy of the finite-field +trace pairing proves that these observations distinguish coefficients. + +Finally, `additiveEncoding_eq_of_absorption` records the cancellation +obstruction to extending this nonsingular program faithfully to arbitrary +singular forms. The characteristic-two quasilinear relation +` ⊥ <0>` forces every additive encoding into a +cancellative game-value group to identify `` with `<0>`. + +No declaration below assumes or proves the Milnor--Scharlau sequence for +quadratic Witt groups. A future formalization of that cited theorem should +instantiate `ResidueSequence`; the results here then apply without change. +-/ + +namespace Ogdoad.WittRealizationExact + +section FiniteFieldTraceBits + +/-- The basis-free family of `F₂` observations of a coefficient in a finite +residue field: `z` is sent to the linear functional +`lambda ↦ Tr_{E/F₂}(lambda*z)`. Packaging this as a linear map proves +linearity in `z` by construction. -/ +noncomputable def traceBitPresentation + (E : Type*) [Field E] [Finite E] [Algebra F2 E] : + E →ₗ[F2] Module.Dual F2 E := + Algebra.traceForm F2 E + +@[simp] +theorem traceBitPresentation_apply + {E : Type*} [Field E] [Finite E] [Algebra F2 E] + (z lambda : E) : + traceBitPresentation E z lambda = + Algebra.trace F2 E (lambda * z) := by + rw [traceBitPresentation, Algebra.traceForm_apply, mul_comm] + +@[simp] +theorem traceBitPresentation_add + {E : Type*} [Field E] [Finite E] [Algebra F2 E] + (z w : E) : + traceBitPresentation E (z + w) = + traceBitPresentation E z + traceBitPresentation E w := + map_add (traceBitPresentation E) z w + +@[simp] +theorem traceBitPresentation_smul + {E : Type*} [Field E] [Finite E] [Algebra F2 E] + (a : F2) (z : E) : + traceBitPresentation E (a • z) = + a • traceBitPresentation E z := + map_smul (traceBitPresentation E) a z + +/-- The finite-field trace observations distinguish coefficients. -/ +theorem traceBitPresentation_injective + (E : Type*) [Field E] [Finite E] [Algebra F2 E] : + Function.Injective (traceBitPresentation E) := by + rw [← LinearMap.ker_eq_bot] + exact (traceForm_nondegenerate F2 E).ker_eq_bot + +/-- Point-separating form of trace nondegeneracy: every nonzero coefficient is +detected by at least one trace bit. -/ +theorem exists_traceBitPresentation_ne_zero + {E : Type*} [Field E] [Finite E] [Algebra F2 E] + {z : E} (hz : z ≠ 0) : + ∃ lambda : E, traceBitPresentation E z lambda ≠ 0 := by + by_contra h + apply hz + apply (traceForm_nondegenerate F2 E).1 z + intro lambda + by_contra hne + exact h ⟨lambda, hne⟩ + +end FiniteFieldTraceBits + +section SingularCancellationBoundary + +variable {Form Value : Type*} + [AddCommMonoid Form] [AddCancelCommMonoid Value] + +/-- Any additive encoding into a cancellative value monoid identifies `a` and +`z` as soon as the source has the absorption relation `a+a=a+z`. + +For quasilinear one-dimensional forms in characteristic two, the change of +variables `(u,v)=(x+y,y)` gives +` ⊥ <0>`. Thus an additive impartial-game-value +encoding cannot distinguish the nonzero radical coefficient `` from the +zero form while retaining this isometry relation. -/ +theorem additiveEncoding_eq_of_absorption + (encode : Form →+ Value) {a z : Form} + (habsorb : a + a = a + z) : encode a = encode z := by + have h := congrArg encode habsorb + have h' : encode a + encode a = encode a + encode z := by + simpa only [map_add] using h + exact add_left_cancel h' + +/-- Consequently, a genuine absorption pair obstructs injectivity of every +additive encoding into a cancellative value monoid. -/ +theorem not_injective_additiveEncoding_of_absorption + (encode : Form →+ Value) {a z : Form} + (hane : a ≠ z) (habsorb : a + a = a + z) : + ¬Function.Injective encode := by + intro hinjective + exact hane (hinjective (additiveEncoding_eq_of_absorption encode habsorb)) + +end SingularCancellationBoundary + +variable {C W R : Type*} + [AddCommGroup C] [AddCommGroup W] [AddCommGroup R] + [Module F2 C] [Module F2 W] [Module F2 R] + +/-- The linear-algebra data and exactness hypotheses in the +Milnor--Scharlau sequence. The endpoint hypotheses record the leading +injection and trailing surjection. -/ +structure ResidueSequence (C W R : Type*) + [AddCommGroup C] [AddCommGroup W] [AddCommGroup R] + [Module F2 C] [Module F2 W] [Module F2 R] where + constant : C →ₗ[F2] W + residue : W →ₗ[F2] R + transfer : R →ₗ[F2] C + constant_injective : Function.Injective constant + exact_at_witt : Function.Exact constant residue + exact_at_residue : Function.Exact residue transfer + transfer_surjective : Function.Surjective transfer + +namespace ResidueSequence + +variable (S : ResidueSequence C W R) + +/-- Constants have zero second residue. -/ +@[simp] +theorem residue_constant (c : C) : S.residue (S.constant c) = 0 := by + have hcomp := S.exact_at_witt.linearMap_comp_eq_zero + exact LinearMap.ext_iff.mp hcomp c + +/-- Every Witt residue tuple satisfies the transfer/reciprocity relation. -/ +@[simp] +theorem transfer_residue (w : W) : S.transfer (S.residue w) = 0 := by + have hcomp := S.exact_at_residue.linearMap_comp_eq_zero + exact LinearMap.ext_iff.mp hcomp w + +/-- The residue map with codomain restricted to the transfer kernel. -/ +def kernelResidue : W →ₗ[F2] LinearMap.ker S.transfer := + S.residue.codRestrict (LinearMap.ker S.transfer) fun w ↦ by + exact LinearMap.mem_ker.mpr (S.transfer_residue w) + +@[simp] +theorem kernelResidue_coe (w : W) : + (S.kernelResidue w : R) = S.residue w := + rfl + +/-- The transfer relation is the only relation on residue tuples. -/ +theorem transfer_eq_zero_iff_exists_residue (r : R) : + S.transfer r = 0 ↔ ∃ w : W, S.residue w = r := by + rw [← LinearMap.mem_ker, S.exact_at_residue.linearMap_ker_eq, + LinearMap.mem_range] + +/-- Two classes have the same residues exactly when their difference is a +constant-field class. Injectivity makes that constant unique. -/ +theorem residue_eq_iff_existsUnique_constant (x y : W) : + S.residue x = S.residue y ↔ + ∃! c : C, x - y = S.constant c := by + constructor + · intro hxy + have hker : x - y ∈ LinearMap.ker S.residue := by + rw [LinearMap.mem_ker, map_sub, hxy, sub_self] + rw [S.exact_at_witt.linearMap_ker_eq, LinearMap.mem_range] at hker + obtain ⟨c, hc⟩ := hker + refine ⟨c, hc.symm, ?_⟩ + intro d hd + apply S.constant_injective + exact hd.symm.trans hc.symm + · rintro ⟨c, hc, _⟩ + apply sub_eq_zero.mp + calc + S.residue x - S.residue y = S.residue (x - y) := by rw [map_sub] + _ = S.residue (S.constant c) := congrArg S.residue hc + _ = 0 := S.residue_constant c + +/-- Coordinates built from an explicitly supplied constant detector. For +`F₂(t)`, the intended detector is the unramified `phi0` coordinate in the +Aravire--Jacob decomposition at the canonical infinite uniformizer `1/t`. -/ +def coordinatesOfProjection (projection : W →ₗ[F2] C) : + W →ₗ[F2] C × LinearMap.ker S.transfer := + projection.prod S.kernelResidue + +@[simp] +theorem coordinatesOfProjection_apply (projection : W →ₗ[F2] C) (w : W) : + S.coordinatesOfProjection projection w = + (projection w, S.kernelResidue w) := + rfl + +theorem projection_constant_of_comp_eq_id + (projection : W →ₗ[F2] C) + (hprojection : projection ∘ₗ S.constant = LinearMap.id) + (c : C) : projection (S.constant c) = c := by + exact LinearMap.ext_iff.mp hprojection c + +theorem coordinatesOfProjection_injective + (projection : W →ₗ[F2] C) + (hprojection : projection ∘ₗ S.constant = LinearMap.id) : + Function.Injective (S.coordinatesOfProjection projection) := by + intro x y hxy + have hproj : projection x = projection y := congrArg Prod.fst hxy + have hkres : S.kernelResidue x = S.kernelResidue y := + congrArg Prod.snd hxy + have hres : S.residue x = S.residue y := + congrArg Subtype.val hkres + have hker : x - y ∈ LinearMap.ker S.residue := by + rw [LinearMap.mem_ker, map_sub, hres, sub_self] + rw [S.exact_at_witt.linearMap_ker_eq, LinearMap.mem_range] at hker + obtain ⟨c, hc⟩ := hker + have hprojzero : projection (x - y) = 0 := by + rw [map_sub, hproj, sub_self] + have hc0 : c = 0 := by + calc + c = projection (S.constant c) := + (S.projection_constant_of_comp_eq_id projection hprojection c).symm + _ = projection (x - y) := by rw [hc] + _ = 0 := hprojzero + apply sub_eq_zero.mp + calc + x - y = S.constant c := hc.symm + _ = 0 := by rw [hc0, map_zero] + +theorem coordinatesOfProjection_surjective + (projection : W →ₗ[F2] C) + (hprojection : projection ∘ₗ S.constant = LinearMap.id) : + Function.Surjective (S.coordinatesOfProjection projection) := by + rintro ⟨c, r, hr⟩ + have hrange : (r : R) ∈ LinearMap.range S.residue := by + rw [← S.exact_at_residue.linearMap_ker_eq] + exact hr + rw [LinearMap.mem_range] at hrange + obtain ⟨w, hw⟩ := hrange + let correction : C := c - projection w + let x : W := w + S.constant correction + refine ⟨x, ?_⟩ + apply Prod.ext + · change projection x = c + simp only [x, map_add, correction, + S.projection_constant_of_comp_eq_id projection hprojection] + abel + · apply Subtype.ext + change S.residue x = r + simp only [x, map_add, S.residue_constant, add_zero, hw] + +/-- An explicit constant detector splitting the constant inclusion turns the +Milnor--Scharlau residue sequence into faithful additive coordinates. Once +the canonical-infinity `phi0` detector is constructed and shown to be a +retraction, this theorem gives the desired basis-independent algebraic +isomorphism without any choice of vector-space basis. -/ +noncomputable def coordinatesEquivOfProjection + (projection : W →ₗ[F2] C) + (hprojection : projection ∘ₗ S.constant = LinearMap.id) : + W ≃ₗ[F2] C × LinearMap.ker S.transfer := + LinearEquiv.ofBijective (S.coordinatesOfProjection projection) + ⟨S.coordinatesOfProjection_injective projection hprojection, + S.coordinatesOfProjection_surjective projection hprojection⟩ + +/-- A linear retraction of the constant-field inclusion. Its construction +extends a basis and is consequently a choice, not canonical arithmetic data. -/ +noncomputable def constantProjection : W →ₗ[F2] C := + S.constant.leftInverse + +@[simp] +theorem constantProjection_constant (c : C) : + S.constantProjection (S.constant c) = c := by + exact LinearMap.leftInverse_apply_of_inj + (LinearMap.ker_eq_bot_of_injective S.constant_injective) c + +/-- Constant coordinate together with the transfer-zero residue tuple. -/ +noncomputable def splitCoordinates : + W →ₗ[F2] C × LinearMap.ker S.transfer := + S.coordinatesOfProjection S.constantProjection + +@[simp] +theorem splitCoordinates_apply (w : W) : + S.splitCoordinates w = + (S.constantProjection w, S.kernelResidue w) := + rfl + +theorem splitCoordinates_injective : + Function.Injective S.splitCoordinates := by + exact S.coordinatesOfProjection_injective S.constantProjection + (LinearMap.leftInverse_comp_of_inj + (LinearMap.ker_eq_bot_of_injective S.constant_injective)) + +theorem splitCoordinates_surjective : + Function.Surjective S.splitCoordinates := by + exact S.coordinatesOfProjection_surjective S.constantProjection + (LinearMap.leftInverse_comp_of_inj + (LinearMap.ker_eq_bot_of_injective S.constant_injective)) + +/-- Exactness over `F₂` gives a faithful additive coordinate system after a +choice of constant projection. This theorem proves the algebraic splitting; +it makes no claim that the choice is natural in field maps or is realized by +impartial arenas. -/ +noncomputable def splitCoordinatesEquiv : + W ≃ₗ[F2] C × LinearMap.ker S.transfer := + LinearEquiv.ofBijective S.splitCoordinates + ⟨S.splitCoordinates_injective, S.splitCoordinates_surjective⟩ + +/-- Modulo constants, the residue map is canonically an equivalence onto the +transfer kernel. Unlike `splitCoordinatesEquiv`, this quotient statement uses +no splitting choice. -/ +noncomputable def quotientConstantsEquivTransferKernel : + (W ⧸ LinearMap.range S.constant) ≃ₗ[F2] LinearMap.ker S.transfer := + (Submodule.quotEquivOfEq (LinearMap.range S.constant) + (LinearMap.ker S.residue) S.exact_at_witt.linearMap_ker_eq.symm).trans + (S.residue.quotKerEquivRange.trans + (LinearEquiv.ofEq (LinearMap.range S.residue) + (LinearMap.ker S.transfer) + S.exact_at_residue.linearMap_ker_eq.symm)) + +section FiniteSupport + +variable {Place : Type*} (T : ResidueSequence C W (Place →₀ F2)) + +/-- The nonzero place set of an individual Witt class is finite by +construction of the direct sum. -/ +noncomputable def relevantPlaces (w : W) : Finset Place := + (T.residue w).support + +theorem residue_eq_zero_of_not_mem_relevantPlaces + [DecidableEq Place] (w : W) {v : Place} + (hv : v ∉ T.relevantPlaces w) : T.residue w v = 0 := by + by_contra hne + exact hv (Finsupp.mem_support_iff.mpr hne) + +/-- The finite-support tuple still obeys the single global transfer relation. -/ +theorem residue_transfer_relation (w : W) : + T.transfer (T.residue w) = 0 := + T.transfer_residue w + +end FiniteSupport + +end ResidueSequence + +end Ogdoad.WittRealizationExact diff --git a/formal/Ogdoad/WittSingularBoundary.lean b/formal/Ogdoad/WittSingularBoundary.lean new file mode 100644 index 00000000..8e916e7e --- /dev/null +++ b/formal/Ogdoad/WittSingularBoundary.lean @@ -0,0 +1,80 @@ +import Ogdoad.WittRealizationExact +import Mathlib.LinearAlgebra.QuadraticForm.Prod + +/-! +# Concrete quasilinear absorption in characteristic two + +The abstract cancellation theorem in `WittRealizationExact` assumes an +absorption relation. This file proves the actual quadratic-form isometry +used by the paper: + +```text + orthogonal-sum isometric-to orthogonal-sum <0>. +``` + +The isometry is the involutive shear `(x,y) |-> (x+y,y)`. Consequently the +paper's obstruction is no longer resting on an unformalized coordinate +change; only passage to a chosen isometry-class monoid is external. +-/ + +namespace Ogdoad.WittSingularBoundary + +open scoped CharTwo + +noncomputable section + +set_option linter.unnecessarySeqFocus false +set_option linter.unusedSectionVars false + +variable {F : Type*} [Field F] [CharP F 2] + +/-- The one-dimensional quasilinear form ``. -/ +def quasilinearLine (c : F) : QuadraticForm F F := + c • QuadraticMap.sq + +@[simp] +theorem quasilinearLine_apply (c x : F) : + quasilinearLine c x = c * x ^ 2 := by + simp [quasilinearLine, QuadraticMap.sq_apply, pow_two] + +/-- The characteristic-two shear `(x,y) |-> (x+y,y)`, which is its own +inverse. -/ +def shear : (F × F) ≃ₗ[F] (F × F) where + toFun p := (p.1 + p.2, p.2) + invFun p := (p.1 + p.2, p.2) + left_inv := by + intro p + ext <;> simp + right_inv := by + intro p + ext <;> simp + map_add' := by + intro p q + ext <;> simp <;> abel + map_smul' := by + intro a p + ext <;> simp [mul_add] + +@[simp] +theorem shear_apply (p : F × F) : shear p = (p.1 + p.2, p.2) := + rfl + +/-- Concrete quasilinear absorption isometry. -/ +def absorptionIsometry (c : F) : + ((quasilinearLine c).prod (quasilinearLine c)).IsometryEquiv + ((quasilinearLine c).prod (quasilinearLine 0)) where + toLinearEquiv := shear + map_app' := by + intro p + simp [QuadraticMap.prod_apply, CharTwo.add_sq] + ring + +theorem absorption_value_identity (c x y : F) : + quasilinearLine c x + quasilinearLine c y = + quasilinearLine c (x + y) + quasilinearLine 0 y := by + simp [CharTwo.add_sq] + ring + +end + +end Ogdoad.WittSingularBoundary diff --git a/formal/Ogdoad/WittTransfer.lean b/formal/Ogdoad/WittTransfer.lean new file mode 100644 index 00000000..709303a9 --- /dev/null +++ b/formal/Ogdoad/WittTransfer.lean @@ -0,0 +1,173 @@ +import Ogdoad.Fifo +import Mathlib.FieldTheory.Finite.Trace +import Mathlib.RingTheory.Trace.Basic + +/-! +# Finite-field trace calculation for the ramified Arf generator + +This file formalizes the basis calculation behind the statement that every +nonzero Scharlau transfer from a finite extension of `F_2` preserves the +unique nonzero quadratic Witt class. In the trace-normalized case, the +transferred plane has Arf coordinate + +```text +sum_i Tr(e_i^2) * Tr(a * f_i^2), +``` + +where `(e_i)` is any basis and `(f_i)` its trace-dual basis. The main theorem +proves that this sum is exactly `Tr(a)`. Consequently it is one whenever the +source binary plane has Arf invariant one. + +Packaging this calculation as a theorem about Scharlau transfer on quadratic +Witt groups still awaits a quadratic Witt-group API; the arithmetic identity +itself is kernel-checked here. +-/ + +namespace Ogdoad.WittTransfer + +open Module +open scoped BigOperators CharTwo + +noncomputable section + +set_option linter.unusedSectionVars false + +variable {E : Type*} [Field E] [Finite E] [CharP E 2] [Algebra F2 E] + +/-- The unique trace-pairing coefficient representing an `F_2`-linear +functional. -/ +noncomputable def functionalCoefficient (s : E →ₗ[F2] F2) : E := + ((Algebra.traceForm F2 E).toDual + (traceForm_nondegenerate F2 E)).symm s + +theorem functionalCoefficient_spec (s : E →ₗ[F2] F2) (z : E) : + s z = Algebra.trace F2 E (functionalCoefficient s * z) := by + symm + exact (LinearMap.BilinForm.apply_toDual_symm_apply + (B := Algebra.traceForm F2 E) s z) + +theorem functionalCoefficient_ne_zero {s : E →ₗ[F2] F2} (hs : s ≠ 0) : + functionalCoefficient s ≠ 0 := by + intro hc + apply hs + apply LinearMap.ext + intro z + rw [functionalCoefficient_spec, hc] + simp + +/-- Every nonzero functional is trace against a nonzero square. -/ +theorem exists_square_trace_coefficient (s : E →ₗ[F2] F2) (hs : s ≠ 0) : + ∃ d : E, d ≠ 0 ∧ + ∀ z : E, s z = Algebra.trace F2 E (d ^ 2 * z) := by + let c := functionalCoefficient s + have hc : c ≠ 0 := functionalCoefficient_ne_zero hs + obtain ⟨d, hd⟩ := surjective_frobenius E 2 c + have hdsq : d ^ 2 = c := by + simpa only [frobenius_def] using hd + have hd0 : d ≠ 0 := by + intro hd0 + apply hc + rw [← hdsq, hd0] + simp + refine ⟨d, hd0, fun z ↦ ?_⟩ + rw [functionalCoefficient_spec, hdsq] + +/-- Simultaneous scaling by the square root turns the transfer along an +arbitrary nonzero functional into the trace-normalized plane used below. -/ +theorem scharlau_plane_change (s : E →ₗ[F2] F2) (d a x y : E) + (hs : ∀ z : E, s z = Algebra.trace F2 E (d ^ 2 * z)) : + s (x ^ 2 + x * y + a * y ^ 2) = + Algebra.trace F2 E + ((d * x) ^ 2 + (d * x) * (d * y) + a * (d * y) ^ 2) := by + rw [hs] + congr 1 + ring + +/-- Absolute trace is invariant under squaring over `F_2`. -/ +theorem trace_sq (x : E) : + Algebra.trace F2 E (x ^ 2) = Algebra.trace F2 E x := by + apply (algebraMap F2 E).injective + calc + algebraMap F2 E (Algebra.trace F2 E (x ^ 2)) = + ∑ i ∈ Finset.range (Module.finrank F2 E), + (x ^ 2) ^ (Nat.card F2 ^ i) := + FiniteField.algebraMap_trace_eq_sum_pow F2 E (x ^ 2) + _ = (∑ i ∈ Finset.range (Module.finrank F2 E), + x ^ (Nat.card F2 ^ i)) ^ 2 := by + rw [CharTwo.sum_sq] + apply Finset.sum_congr rfl + intro i _ + rw [← pow_mul, ← pow_mul] + congr 1 + omega + _ = (algebraMap F2 E (Algebra.trace F2 E x)) ^ 2 := by + rw [FiniteField.algebraMap_trace_eq_sum_pow] + _ = algebraMap F2 E ((Algebra.trace F2 E x) ^ 2) := by + rw [map_pow] + _ = algebraMap F2 E (Algebra.trace F2 E x) := by + rw [show (Algebra.trace F2 E x) ^ 2 = Algebra.trace F2 E x by + simpa [pow_two] using Ogdoad.zmod2_sq_eq_self + (Algebra.trace F2 E x)] + +variable {ι : Type*} [Fintype ι] [DecidableEq ι] + +/-- Squared trace-dual resolution of one. -/ +theorem sum_trace_smul_traceDual_sq (b : Basis ι F2 E) : + (∑ i, Algebra.trace F2 E (b i) • (b.traceDual i) ^ 2) = 1 := by + have hone := b.traceDual.sum_repr (1 : E) + have hrepr (i : ι) : + b.traceDual.repr (1 : E) i = Algebra.trace F2 E (b i) := by + rw [Module.Basis.traceDual_repr_apply] + simp [Algebra.traceForm_apply] + rw [show (1 : E) = (1 : E) ^ 2 by simp] + rw [← hone] + rw [CharTwo.sum_sq] + apply Finset.sum_congr rfl + intro i _ + rw [hrepr] + simp only [Algebra.smul_def, mul_pow] + rw [show (algebraMap F2 E (Algebra.trace F2 E (b i))) ^ 2 = + algebraMap F2 E (Algebra.trace F2 E (b i)) by + rw [← map_pow] + congr 1 + simpa [pow_two] using Ogdoad.zmod2_sq_eq_self + (Algebra.trace F2 E (b i))] + +/-- The transferred trace plane has the same Arf coordinate as its source +plane. -/ +theorem transfer_arf_sum (b : Basis ι F2 E) (a : E) : + (∑ i, Algebra.trace F2 E ((b i) ^ 2) * + Algebra.trace F2 E (a * (b.traceDual i) ^ 2)) = + Algebra.trace F2 E a := by + simp_rw [trace_sq] + have hterm (i : ι) : + Algebra.trace F2 E (b i) * + Algebra.trace F2 E (a * (b.traceDual i) ^ 2) = + Algebra.trace F2 E + (Algebra.trace F2 E (b i) • (a * (b.traceDual i) ^ 2)) := by + rw [map_smul] + rfl + simp_rw [hterm] + rw [← map_sum (Algebra.trace F2 E)] + congr 1 + calc + (∑ i, Algebra.trace F2 E (b i) • + (a * (b.traceDual i) ^ 2)) = + a * (∑ i, Algebra.trace F2 E (b i) • (b.traceDual i) ^ 2) := by + rw [Finset.mul_sum] + apply Finset.sum_congr rfl + intro i _ + simp only [Algebra.smul_def] + ring + _ = a := by rw [sum_trace_smul_traceDual_sq b, mul_one] + +/-- Arf-one specialization used by the global reciprocity map. -/ +theorem transfer_arf_one (b : Basis ι F2 E) {a : E} + (ha : Algebra.trace F2 E a = 1) : + (∑ i, Algebra.trace F2 E ((b i) ^ 2) * + Algebra.trace F2 E (a * (b.traceDual i) ^ 2)) = 1 := by + rw [transfer_arf_sum b a, ha] + +end + +end Ogdoad.WittTransfer diff --git a/formal/README.md b/formal/README.md index 3be6969e..16ddc351 100644 --- a/formal/README.md +++ b/formal/README.md @@ -15,15 +15,44 @@ compositions are separate evidence unless a theorem below explicitly connects them. In particular, a declaration ending in `Target` or `Theorem` may merely *define* a proposition; inspect whether it is followed by a proof. +## Module architecture + +The formal tree has one declaration owner for each theorem and thin, +import-only paper entry points: + +| layer | modules | contract | +| --- | --- | --- | +| shared algebra | `Algebra.ZModTwo`, `Algebra.ArtinSchreier` | Paper-independent binary-scalar facts, Artin--Schreier additivity and tower algebra, the finite-field trace exact sequence, and the trace-one irreducibility criterion. No FIFO, Gold, Witt, excess, or nim module is imported here. | +| certificate support | `Certificate.BinaryPolynomial` | One executable bit-polynomial multiplication, powering, remainder, and gcd evaluator shared by named finite-field certificates. It supplies computation only; each consumer separately owns its modulus, irreducibility check, constants, and mathematical claim. | +| reusable mathematical components | `Quadratic.CharTwo` (bundling `Off`, `SymplecticBasis`, `WittFrame`, and `CharTwoClassification`), `FifoMatching`, and the other theorem-bearing modules below | Each definition or theorem is proved once in the narrowest domain module that owns it. Domain bundles and paper modules import these components; they do not restate them. | +| open-frontier bundle | `FifoFrontier` | The complete arbitrary-graph FIFO research surface used by the linking paper. Solved consumers such as Gold--Arf import only `FifoMatching`, not the whole frontier. | +| paper entry points | `Papers.TransfiniteArf`, `Papers.GoldArf`, `Papers.WittRealization`, `Papers.LinkingAffine`, `Papers.Excess`, `Papers.NimFastMultiplication`, `Papers.SemiringStability`, `Papers.MisereNaturalRealization` | Import-only manifests matching the eight papers that currently have Lean content. These are the preferred focused build targets. | +| project root | `Ogdoad` | Imports the paper entry points, so the full build still covers every declaration while keeping paper composition explicit. | + +For example, both Gold--Arf and transfinite Arf import +`Quadratic.CharTwo`; neither paper entry point imports the other. Both +Gold--Arf and fast nim multiplication import +`Algebra.ArtinSchreier`; neither owns a private copy of the trace-kernel or +trace-one polynomial facts. Witt realization, Gold--Arf, and the FIFO +development share `Algebra.ZModTwo` rather than reaching through the FIFO +namespace for binary-scalar lemmas. The three exact binary-field certificates +inside `Excess` share `Certificate.BinaryPolynomial`; they do not carry private +copies of the evaluator. + ## Closed results | area | modules | kernel-checked result and boundary | | --- | --- | --- | +| shared characteristic-two algebra | `Algebra.ZModTwo`, `Algebra.ArtinSchreier` | The common `F2` coefficient type and binary scalar lemmas; Artin--Schreier additivity, companions, and tower lifting; the finite-field Artin--Schreier image/absolute-trace-kernel equality; and irreducibility of `X^2 + X + a` exactly at trace one. These modules are paper-independent dependencies. | | characteristic-two quadratic forms | `Off`, `SymplecticBasis`, `WittFrame`, `CharTwoClassification` | Frobenius and Artin--Schreier plane reductions, a recursive orthogonal symplectic decomposition, a deterministic adapted basis, explicit canonical-form isometries, and classification by ambient, polar-radical, and quadratic-radical dimensions. A set-sized algebraically closed field is the formal proxy for each finite piece of Conway's proper-class `On_2`. | | game-valued obstructions and Brown selectors | `GameExterior`, `BrownGame`, `BrownSelectorPGame` | Root-divisibility forces the stated exterior/Clifford coefficients to vanish; Brown refinements split as `lift(ell) + 2Q`; one explicit finite partizan tree realizes the four Brown outcome classes. Moews's structure theorem for short games is a cited external input, not a Lean axiom. | -| Gold sources and extensions | `GoldDiagonal`, `GoldExtraspecial`, `GoldExtraspecialTrace` | Quadratic-tower trace reconstruction, Artin--Schreier lifting and trace-kernel exactness, the universal cocycle group, the trace/Frobenius Gold specialization, and the order-eight quaternion cell over the canonical `GF(4)`. Lean's abstract finite fields are not definitionally identified with the Rust nimber backend. | +| Gold sources and extensions | `Algebra.ArtinSchreier`, `GoldDiagonal`, `GoldExtraspecial`, `GoldExtraspecialTrace` | Shared Artin--Schreier lifting and trace-kernel exactness, Gold-specific quadratic-tower trace reconstruction, the universal cocycle group, the trace/Frobenius Gold specialization, and the order-eight quaternion cell over the canonical `GF(4)`. Lean's abstract finite fields are not definitionally identified with the Rust nimber backend. | | Gold--Arf arena | `FifoMatching`, `ImpartialRealizer`, `PhysicalDeferred`, `GoldMatchingAlgebra`, `GoldArena` | Both seats force zero on matching-plus-isolates boards; the pass-free compiler has exact tempo and charge semantics; literal and deferred ledgers are conjugate with strategy trees transported both ways. `GoldArena.gold_literal_root_isP_iff` is the end-to-end literal-root theorem for every finite binary quadratic refinement, public basis, and input. It does not use arbitrary-graph FIFO linking. | | realization bounds and comparison surfaces | `GoldNoEvaluator`, `GoldBlockCompression`, `GoldForkPadding`, `GoldSemantics` | Transcript-span and observation-weight lower bounds, induced block refinements, an outcome-preserving fork-padding obstruction, and an independent payoff-to-normal-play compiler. These are auxiliary theorems, not alternate end-to-end arena constructions. | +| imperfect-field Witt realization | `WittRealizationExact`, `WittRealization`, `WittTransfer`, `WittRamification`, `WittRamifiedRestriction`, `WittSingularBoundary` | The concrete parity-zero presented coordinate space has a finite sparse trace-character compiler into literal `0/*` arenas, with equality of all outcomes equivalent to equality of presented classes. The development also checks the trace-dual Scharlau Arf calculation, finite unit-jet substitution and exact Artin--Schreier normalization, one-place and fixed-finite-family obstructions, and the quasilinear absorption isometry. The Aravire--Jacob exact sequence and local decomposition, their equivalence with the actual `W_q(F_2(t))`, and the local quadratic-form interpretation of ramified generators remain the cited bridge. | +| stable semiring quadratic pairs | `SemiringQuadratic` | `hessenbergPolynomial_regular_isometric_hyperbolic` is the end-to-end finite-polynomial Hessenberg proxy: from bijectivity of the actual companion adjoint it proves Gram invertibility, proves internally that every invertible `MvPolynomial (Fin d) Nat` matrix is a permutation matrix, constructs its fixed-point-free transposition decomposition, and returns even rank plus an actual linear isometry of quadratic pairs with canonical hyperbolic planes. The module also proves that finite basis coefficients determine the entire pair, exposes Mathlib's characteristic-not-two diagonalization, and gives an explicit additive equivalence from the exact cyclic/hyperbolic/parity presentation to `ZMod 2`. The ordinal CNF--polynomial identification, connected-graph stable quotient, concrete supertropical semiring and lift presentation, and thermograph facts remain cited or paper-level bridges. | +| canonical nim multiplication | `Algebra.ArtinSchreier`, `NimFastMultiplication` | The shared finite-field trace-one irreducibility criterion, then the nim-specific trace carry through a quadratic generator, three-variable-product quadratic split, affine Artin--Schreier generator shift, involutive two-block coordinate transform, preservation of the canonical tower equations under ring equivalence, and exact recovery of source-basis product coordinates. The concrete Conway tower, De Feo--Schost algorithms, and asymptotic bounds remain cited or paper-level inputs. | +| finite misere quotient realization | `MisereTransition`, `MisereOctalCertificate`, `MisereNaturalUniversality`, `MisereTraceLanguage`, `MisereGrundyObstruction` | A least-rank separator proves transition determinism. Every value has a `P`-reaching context, so no transition value is one of its own options; the whole-, one-, and two-remainder octal exclusions are checked explicitly. Rank plus table parity proves outcome correctness, and multiplicativity, surjectivity, and reducedness identify exact quotient fibers. An asserted ultimately periodic octal word has periodic complete option records past `2*N + p + d`, so a finite prefix through one tail period certifies all heap records. The typed finite-prefix/pad/unary-tail construction realizes every nontrivial finite valid table at the abstract heap level, while the later-terminal-pad theorem rules out its verbatim heapwise octal encoding for quotients with more than two elements. The unary Boolean-language module identifies exact option cells and the failure language coefficientwise. Mathlib's Hindman theorem supplies distinct positive `i < j` with equal finite colors at `i`, `j`, and `i+j`; for a finite Grundy trace this produces a square-valued option, which forces distinct consecutive powers and an eventual period of length at least two. The external unary-grammar hardness reduction, transport to numerical heap sizes and multisets, strict finite-octal synthesis, tame-family strategy, Grundy kernel argument, and quotients through heap 18 remain paper-level or open exactly as stated in `writeups/misere_natural_realization.tex`. | ## Arbitrary-graph FIFO linking: open @@ -82,6 +111,28 @@ four-arm `Z/O/C/D` reduction, including: - the corrected characteristic-two sparse norm; - trace, norm, Frobenius, Kummer, Dickson--Lucas, Singer, and cubical-support identities used by the selected-order reductions; +- weighted two-axis Frobenius-word normalization: all multiplicative + reciprocity expressions in this model formed from one Kummer class remain + rank one, regardless of Frobenius, character, and multiplicity indices; +- affine-Frobenius conjugacy and marked Jacobi-sum scaling: class functions on + one split fixed-multiplier affine coset erase its translation coordinate, while + additive convolution retains only the summed weight of the same Kummer + phase; a labeled Fourier-line pullback exposes a gauge-dependent one-step + coordinate, while the full affine-path holonomy is gauge invariant; a + separate identity identifies a closed Kummer ratio with the Euler phase. + Every homomorphic affine composition cocycle + is a coboundary with zero full-period holonomy, and every closing holonomy + remains possible after the proper path has been normalized; affine + intertwiners contribute only a scalar multiple plus a telescoping endpoint + term; +- the singleton-even auxiliary-cubic and abstract pairing no-gos: + positive-level Fermat torsion has trivial cubic Kummer class, a perfect + alternating pairing may vanish on a prescribed line, and coprime adjacent + annihilators kill a proposed ancestry homomorphism; the selected elliptic + curve, divisor, and Miller-function interpretation remain paper-level; +- oriented Frobenius-prefix evaluation and its block cocycle, including the + faithful unit-exponent-prefix/full-orbit-trivial dichotomy; these identities + only re-encode a marked power-residue phase and do not evaluate it; - transparent finite primality, factor, modular, and order screens for the rows explicitly encoded in the file. @@ -103,9 +154,28 @@ the ambient order contains a higher power of `p`. The selected Conway phase is still the missing datum in each universal arm. See `../docs/OPEN.md` and `../writeups/excess.tex`. +## Natural finite misere realization: open + +`MisereTransition`, `MisereOctalCertificate`, +`MisereNaturalUniversality`, `MisereTraceLanguage`, and +`MisereGrundyObstruction` check the finite-table determinism, exact periodic +trace certificate, abstract prefix/pad/unary-tail construction and its inert-pad +obstruction, unary option-language dictionary, and finite-Grundy power-period +obstruction listed in the closed-results table. + +They do not characterize exact quotients of finite-code octal games, prove +automatic periodicity for split traces, or determine whether the full misere +Grundy quotient is finite. The numerical transport, tame-family construction, +and exact Grundy prefixes through heap 18 remain paper proofs. See +`../docs/OPEN.md` and `../writeups/misere_natural_realization.tex`. + ## Review contract -- Keep `Ogdoad.lean` synchronized with the intended build surface. +- Put a reusable declaration in a paper-independent domain module; paper entry + points remain import-only manifests. +- Keep each `Papers.*` entry point synchronized with its live paper and keep + `Ogdoad.lean` synchronized with the complete set of Lean-backed paper entry + points. - Keep the project warning-clean and placeholder-free. - Prefer transparent definitions and ordinary `decide` for finite proofs when practical; document any stronger evaluator. diff --git a/grundy/src/parse.rs b/grundy/src/parse.rs index 2c10a945..2f2dcad1 100644 --- a/grundy/src/parse.rs +++ b/grundy/src/parse.rs @@ -248,7 +248,7 @@ impl Parser { } fn parse_lambda_or_expression(&mut self) -> GrundyResult { - if let Some(binders) = self.try_parse_binders()? { + if let Some(binders) = self.try_parse_binders() { self.expect(|k| matches!(k, TokenKind::Arrow), "`↦`")?; let body = self.parse_lambda_or_expression()?; return Ok(Expr::Lambda { @@ -259,7 +259,7 @@ impl Parser { self.parse_expression() } - fn try_parse_binders(&mut self) -> GrundyResult>> { + fn try_parse_binders(&mut self) -> Option> { let save = self.pos; let out = match self.peek_kind() { Some(TokenKind::Ident(_)) if matches!(self.peek_kind_at(1), Some(TokenKind::Arrow)) => { @@ -279,7 +279,7 @@ impl Parser { loop { let Some(binder) = self.parse_lambda_binder() else { self.pos = save; - return Ok(None); + return None; }; binders.push(binder); if !matches!(self.peek_kind(), Some(TokenKind::Comma)) { @@ -289,14 +289,14 @@ impl Parser { } if !matches!(self.peek_kind(), Some(TokenKind::RParen)) { self.pos = save; - return Ok(None); + return None; } self.bump(); if matches!(self.peek_kind(), Some(TokenKind::Arrow)) { Some(binders) } else { self.pos = save; - return Ok(None); + return None; } } _ => None, @@ -304,7 +304,7 @@ impl Parser { if out.is_none() { self.pos = save; } - Ok(out) + out } fn parse_lambda_binder(&mut self) -> Option { diff --git a/grundy/src/runtime/index.rs b/grundy/src/runtime/index.rs index 1c53827e..e3de7954 100644 --- a/grundy/src/runtime/index.rs +++ b/grundy/src/runtime/index.rs @@ -26,7 +26,11 @@ pub(crate) fn eval_index(runtime: &mut R, expr: &Expr) -> Grun match expr { Expr::Index(expr) => eval_index(runtime, expr), Expr::Int(n) => u128_to_i128(*n), - Expr::Bool(_) => Err(bool_sort_error()), + Expr::Bool(_) + | Expr::Unary { + op: UnaryOp::Not, .. + } + | Expr::Relation { .. } => Err(bool_sort_error()), Expr::Lambda { .. } => Err(fn_sort_error()), Expr::Block { bindings, body } => match runtime.eval_block(bindings, body)? { Value::Index(value) => Ok(value), @@ -49,10 +53,16 @@ pub(crate) fn eval_index(runtime: &mut R, expr: &Expr) -> Grun .ok_or_else(|| overflow("index negation overflowed i128")), Expr::Unary { op: UnaryOp::Inv, .. - } => Err(index_sort_error()), - Expr::Unary { - op: UnaryOp::Not, .. - } => Err(bool_sort_error()), + } + | Expr::Star(_) + | Expr::Omega + | Expr::Blade(_) + | Expr::Container(_) + | Expr::Up + | Expr::Down + | Expr::Dim + | Expr::GameForm { .. } + | Expr::Call { .. } => Err(index_sort_error()), Expr::Apply { .. } => match runtime.eval_value(expr)? { Value::Index(value) => Ok(value), Value::Element(_) => Err(index_sort_error()), @@ -75,16 +85,6 @@ pub(crate) fn eval_index(runtime: &mut R, expr: &Expr) -> Grun let rhs = eval_index(runtime, rhs)?; eval_index_binary(*op, lhs, rhs) } - Expr::Relation { .. } => Err(bool_sort_error()), - Expr::Star(_) - | Expr::Omega - | Expr::Blade(_) - | Expr::Container(_) - | Expr::Up - | Expr::Down - | Expr::Dim - | Expr::GameForm { .. } - | Expr::Call { .. } => Err(index_sort_error()), } } @@ -97,7 +97,7 @@ pub(crate) fn parse_display_expr(src: &str) -> GrundyResult { } } -pub(crate) fn index_literal_expr(value: i128) -> GrundyResult { +pub(crate) fn index_literal_expr(value: i128) -> Expr { let inner = if value >= 0 { Expr::Int(value as u128) } else { @@ -106,7 +106,7 @@ pub(crate) fn index_literal_expr(value: i128) -> GrundyResult { expr: Box::new(Expr::Int(value.unsigned_abs())), } }; - Ok(Expr::Index(Box::new(inner))) + Expr::Index(Box::new(inner)) } pub(crate) fn wrap_index_expr(inner: Expr) -> Expr { @@ -134,15 +134,6 @@ pub(crate) fn value_sort(value: &Value) -> DataSort { } } -pub(crate) fn env_sort(value: &Value) -> GrundyResult { - match value { - Value::Element(_) => Ok(DataSort::Element), - Value::Index(_) => Ok(DataSort::Index), - Value::Bool(_) => Ok(DataSort::Bool), - Value::Function(_) => Err(fn_sort_error()), - } -} - pub(crate) fn ensure_value_sort(value: &Value, expected: DataSort) -> GrundyResult<()> { match value { Value::Function(_) => Err(fn_sort_error()), diff --git a/grundy/src/runtime/transform.rs b/grundy/src/runtime/transform.rs index 16f5686b..54af582a 100644 --- a/grundy/src/runtime/transform.rs +++ b/grundy/src/runtime/transform.rs @@ -5,7 +5,7 @@ use super::*; pub(crate) fn value_to_expr(value: &Value) -> GrundyResult { match value { Value::Element(value) => parse_display_expr(&value.to_string()), - Value::Index(value) => Ok(index_literal_expr(*value)?), + Value::Index(value) => Ok(index_literal_expr(*value)), Value::Bool(value) => Ok(Expr::Bool(*value)), Value::Function(function) => Ok(function.to_expr()), } diff --git a/grundy/src/runtime/validate.rs b/grundy/src/runtime/validate.rs index e148c117..38c883de 100644 --- a/grundy/src/runtime/validate.rs +++ b/grundy/src/runtime/validate.rs @@ -8,6 +8,12 @@ pub(crate) enum ExpectedSort { Known(DataSort), } +#[derive(Clone, Copy)] +enum StaticBindingSort { + Data(DataSort), + Function(DataSort), +} + pub(crate) fn check_binders( binders: &[String], is_world_shadow: impl Fn(&str) -> bool, @@ -93,22 +99,17 @@ pub(crate) fn infer_expr_sort( } } Expr::Call { name, args } => match name.as_str() { - "nleft" | "nright" | "birthday" => { + "nleft" | "nright" | "birthday" | "deg" => { expect_arity(name, args, 1)?; infer_expr_sort(&args[0], ExpectedSort::Known(DataSort::Element), binders)?; expect_sort(DataSort::Index, expected) } - "left" | "right" => { + "left" | "right" | "coef" | "grade" => { expect_arity(name, args, 2)?; infer_expr_sort(&args[0], ExpectedSort::Known(DataSort::Element), binders)?; infer_expr_sort(&args[1], ExpectedSort::Known(DataSort::Index), binders)?; expect_sort(DataSort::Element, expected) } - "canon" => { - expect_arity(name, args, 1)?; - infer_expr_sort(&args[0], ExpectedSort::Known(DataSort::Element), binders)?; - expect_sort(DataSort::Element, expected) - } "up" | "down" | "dim" => Err(literal_call_error(name)), "hasdraw" | "stopper" | "integral" => { expect_arity(name, args, 1)?; @@ -117,24 +118,7 @@ pub(crate) fn infer_expr_sort( } "drawn" => Err(function_replacement_error("drawn", "hasdraw")), "outcome" | "winner" | "who" => Err(outcome_name_error(name)), - "coef" => { - expect_arity(name, args, 2)?; - infer_expr_sort(&args[0], ExpectedSort::Known(DataSort::Element), binders)?; - infer_expr_sort(&args[1], ExpectedSort::Known(DataSort::Index), binders)?; - expect_sort(DataSort::Element, expected) - } - "deg" => { - expect_arity(name, args, 1)?; - infer_expr_sort(&args[0], ExpectedSort::Known(DataSort::Element), binders)?; - expect_sort(DataSort::Index, expected) - } - "grade" => { - expect_arity(name, args, 2)?; - infer_expr_sort(&args[0], ExpectedSort::Known(DataSort::Element), binders)?; - infer_expr_sort(&args[1], ExpectedSort::Known(DataSort::Index), binders)?; - expect_sort(DataSort::Element, expected) - } - "rev" | "even" | "dual" | "frob" => { + "canon" | "rev" | "even" | "dual" | "frob" => { expect_arity(name, args, 1)?; infer_expr_sort(&args[0], ExpectedSort::Known(DataSort::Element), binders)?; expect_sort(DataSort::Element, expected) @@ -387,27 +371,50 @@ pub(crate) fn static_sort( expr: &Expr, env: &BTreeMap>, deg_is_index: bool, +) -> GrundyResult { + let bindings = env + .iter() + .map(|(name, value)| { + let sort = match value { + Value::Element(_) => StaticBindingSort::Data(DataSort::Element), + Value::Index(_) => StaticBindingSort::Data(DataSort::Index), + Value::Bool(_) => StaticBindingSort::Data(DataSort::Bool), + Value::Function(function) => StaticBindingSort::Function(function.ret), + }; + (name.clone(), sort) + }) + .collect(); + static_sort_with_bindings(expr, &bindings, deg_is_index) +} + +fn static_sort_with_bindings( + expr: &Expr, + env: &BTreeMap, + deg_is_index: bool, ) -> GrundyResult { match expr { - Expr::Bool(_) | Expr::Relation { .. } => Ok(DataSort::Bool), + Expr::Bool(_) + | Expr::Relation { .. } + | Expr::Unary { + op: UnaryOp::Not, .. + } + | Expr::Binary { + op: BinaryOp::And | BinaryOp::Or, + .. + } => Ok(DataSort::Bool), Expr::Index(_) | Expr::Dim => Ok(DataSort::Index), Expr::Lambda { .. } => Err(fn_sort_error()), Expr::Block { bindings, body } => { - let mut local_sorts = env - .iter() - .map(|(name, value)| env_sort(value).map(|sort| (name.clone(), sort))) - .collect::>>()?; + let mut local = env.clone(); for binding in bindings { - let sort = static_sort_with_sorts(&binding.expr, &local_sorts, deg_is_index)?; - local_sorts.insert(binding.name.clone(), sort); + let sort = static_sort_with_bindings(&binding.expr, &local, deg_is_index)?; + local.insert(binding.name.clone(), StaticBindingSort::Data(sort)); } - static_sort_with_sorts(body, &local_sorts, deg_is_index) + static_sort_with_bindings(body, &local, deg_is_index) } Expr::Ident(name) => match env.get(name) { - Some(Value::Element(_)) => Ok(DataSort::Element), - Some(Value::Index(_)) => Ok(DataSort::Index), - Some(Value::Bool(_)) => Ok(DataSort::Bool), - Some(Value::Function(_)) => Err(fn_sort_error()), + Some(StaticBindingSort::Data(sort)) => Ok(*sort), + Some(StaticBindingSort::Function(_)) => Err(fn_sort_error()), None => Ok(DataSort::Element), }, Expr::Call { name, .. } @@ -419,95 +426,23 @@ pub(crate) fn static_sort( Expr::Call { name, .. } if matches!(name.as_str(), "hasdraw" | "stopper" | "integral") => { Ok(DataSort::Bool) } - Expr::Unary { - op: UnaryOp::Not, .. - } => Ok(DataSort::Bool), - Expr::Unary { expr, .. } => static_sort(expr, env, deg_is_index), + Expr::Unary { expr, .. } => static_sort_with_bindings(expr, env, deg_is_index), Expr::Apply { callee, .. } => match &**callee { Expr::Ident(name) => match env.get(name) { - Some(Value::Function(function)) => Ok(function.ret), + Some(StaticBindingSort::Function(ret)) => Ok(*ret), _ => Ok(DataSort::Element), }, _ => Ok(DataSort::Element), }, - Expr::Binary { - op: BinaryOp::And | BinaryOp::Or, - .. - } => Ok(DataSort::Bool), - Expr::Binary { - op: BinaryOp::Add | BinaryOp::Sub | BinaryOp::Mul | BinaryOp::Pow, - lhs, - rhs, - } => { - let lhs = static_sort(lhs, env, deg_is_index).unwrap_or(DataSort::Element); - let rhs = static_sort(rhs, env, deg_is_index).unwrap_or(DataSort::Element); - if lhs == DataSort::Bool || rhs == DataSort::Bool { - Ok(DataSort::Bool) - } else if lhs == DataSort::Index || rhs == DataSort::Index { - Ok(DataSort::Index) - } else { - Ok(DataSort::Element) - } - } - Expr::If { - then_expr, - else_expr, - .. - } => { - let then_sort = static_sort(then_expr, env, deg_is_index)?; - let else_sort = static_sort(else_expr, env, deg_is_index)?; - if then_sort == else_sort { - Ok(then_sort) - } else { - Err(sort_mismatch(then_sort, else_sort)) - } - } - _ => Ok(DataSort::Element), - } -} - -pub(crate) fn static_sort_with_sorts( - expr: &Expr, - env: &BTreeMap, - deg_is_index: bool, -) -> GrundyResult { - match expr { - Expr::Bool(_) | Expr::Relation { .. } => Ok(DataSort::Bool), - Expr::Index(_) | Expr::Dim => Ok(DataSort::Index), - Expr::Lambda { .. } => Err(fn_sort_error()), - Expr::Block { bindings, body } => { - let mut local = env.clone(); - for binding in bindings { - let sort = static_sort_with_sorts(&binding.expr, &local, deg_is_index)?; - local.insert(binding.name.clone(), sort); - } - static_sort_with_sorts(body, &local, deg_is_index) - } - Expr::Ident(name) => Ok(env.get(name).copied().unwrap_or(DataSort::Element)), - Expr::Call { name, .. } - if matches!(name.as_str(), "dim" | "nleft" | "nright" | "birthday") - || (deg_is_index && name == "deg") => - { - Ok(DataSort::Index) - } - Expr::Call { name, .. } if matches!(name.as_str(), "hasdraw" | "stopper" | "integral") => { - Ok(DataSort::Bool) - } - Expr::Unary { - op: UnaryOp::Not, .. - } => Ok(DataSort::Bool), - Expr::Unary { expr, .. } => static_sort_with_sorts(expr, env, deg_is_index), - Expr::Binary { - op: BinaryOp::And | BinaryOp::Or, - .. - } => Ok(DataSort::Bool), Expr::Binary { op: BinaryOp::Add | BinaryOp::Sub | BinaryOp::Mul | BinaryOp::Pow, lhs, rhs, } => { - let lhs = static_sort_with_sorts(lhs, env, deg_is_index).unwrap_or(DataSort::Element); - let rhs = static_sort_with_sorts(rhs, env, deg_is_index).unwrap_or(DataSort::Element); + let lhs = + static_sort_with_bindings(lhs, env, deg_is_index).unwrap_or(DataSort::Element); + let rhs = + static_sort_with_bindings(rhs, env, deg_is_index).unwrap_or(DataSort::Element); if lhs == DataSort::Bool || rhs == DataSort::Bool { Ok(DataSort::Bool) } else if lhs == DataSort::Index || rhs == DataSort::Index { @@ -521,8 +456,8 @@ pub(crate) fn static_sort_with_sorts( else_expr, .. } => { - let then_sort = static_sort_with_sorts(then_expr, env, deg_is_index)?; - let else_sort = static_sort_with_sorts(else_expr, env, deg_is_index)?; + let then_sort = static_sort_with_bindings(then_expr, env, deg_is_index)?; + let else_sort = static_sort_with_bindings(else_expr, env, deg_is_index)?; if then_sort == else_sort { Ok(then_sort) } else { diff --git a/grundy/src/runtime/value.rs b/grundy/src/runtime/value.rs index 11a07ff0..edb61427 100644 --- a/grundy/src/runtime/value.rs +++ b/grundy/src/runtime/value.rs @@ -65,6 +65,15 @@ pub(crate) fn validation_sample_function(function: &FunctionValue, body: Expr) - } } +pub(crate) fn into_element(value: Value) -> GrundyResult { + match value { + Value::Element(value) => Ok(value), + Value::Index(_) => Err(index_sort_error()), + Value::Bool(_) => Err(bool_sort_error()), + Value::Function(_) => Err(fn_sort_error()), + } +} + pub(crate) fn display_value(value: &Value) -> String { match value { Value::Element(value) => value.to_string(), diff --git a/grundy/src/unparse.rs b/grundy/src/unparse.rs index 99b7f17f..34eb0c48 100644 --- a/grundy/src/unparse.rs +++ b/grundy/src/unparse.rs @@ -297,7 +297,6 @@ fn unparse_binding(binding: &Binding) -> String { fn precedence(expr: &Expr) -> u8 { match expr { Expr::Lambda { .. } => 0, - Expr::Block { .. } => 13, Expr::If { .. } => 1, Expr::Binary { op: BinaryOp::Or, .. diff --git a/grundy/src/worlds/clifford.rs b/grundy/src/worlds/clifford.rs index a457e825..5669e12f 100644 --- a/grundy/src/worlds/clifford.rs +++ b/grundy/src/worlds/clifford.rs @@ -138,7 +138,7 @@ impl CliffordRuntime { fn eval_expr(&mut self, expr: &Expr) -> GrundyResult> { match expr { Expr::Bool(_) => Err(bool_sort_error()), - Expr::Index(_) => Err(index_sort_error()), + Expr::Index(_) | Expr::Dim => Err(index_sort_error()), Expr::GameForm { .. } => Err(game_only_error("game forms")), Expr::Int(n) => Ok(self.alg.scalar(S::bare_int(*n, Span::point(0))?)), Expr::Star(star) => Ok(self.alg.scalar(S::star(star, Span::point(0))?)), @@ -157,14 +157,8 @@ impl CliffordRuntime { Expr::Container(items) => self.eval_container(items), Expr::Up => Err(game_only_error("`up`")), Expr::Down => Err(game_only_error("`down`")), - Expr::Dim => Err(index_sort_error()), Expr::Lambda { .. } => Err(fn_sort_error()), - Expr::Block { bindings, body } => match self.eval_block(bindings, body)? { - Value::Element(value) => Ok(value), - Value::Index(_) => Err(index_sort_error()), - Value::Bool(_) => Err(bool_sort_error()), - Value::Function(_) => Err(fn_sort_error()), - }, + Expr::Block { bindings, body } => into_element(self.eval_block(bindings, body)?), Expr::Ident(name) => { if let Some(value) = self.state.env.get(name) { match value { @@ -188,19 +182,8 @@ impl CliffordRuntime { UnaryOp::Not => Err(bool_sort_error()), } } - Expr::Apply { .. } => match self.eval_value(expr)? { - Value::Element(value) => Ok(value), - Value::Index(_) => Err(index_sort_error()), - Value::Bool(_) => Err(bool_sort_error()), - Value::Function(_) => Err(fn_sort_error()), - }, + Expr::Apply { .. } | Expr::If { .. } => into_element(self.eval_value(expr)?), Expr::Binary { op, lhs, rhs } => self.eval_binary(*op, lhs, rhs), - Expr::If { .. } => match self.eval_value(expr)? { - Value::Element(value) => Ok(value), - Value::Index(_) => Err(index_sort_error()), - Value::Bool(_) => Err(bool_sort_error()), - Value::Function(_) => Err(fn_sort_error()), - }, Expr::Relation { .. } => Err(GrundyError::new( GrundyErrorKind::BoolSort, Span::point(0), diff --git a/grundy/src/worlds/game/mod.rs b/grundy/src/worlds/game/mod.rs index cb5d1e64..7cf0d84f 100644 --- a/grundy/src/worlds/game/mod.rs +++ b/grundy/src/worlds/game/mod.rs @@ -369,7 +369,7 @@ impl GameRuntime { fn eval_element(&mut self, expr: &Expr) -> GrundyResult { match expr { - Expr::Bool(_) => Err(bool_sort_error()), + Expr::Bool(_) | Expr::Relation { .. } => Err(bool_sort_error()), Expr::Index(_) => Err(index_sort_error()), Expr::Int(n) => { let n = i128::try_from(*n).map_err(|_| overflow("game integer exceeds i128"))?; @@ -412,12 +412,7 @@ impl GameRuntime { .collect::>>()?, self.state.graph_budget, ), - Expr::Block { bindings, body } => match self.eval_block(bindings, body)? { - Value::Element(value) => Ok(value), - Value::Index(_) => Err(index_sort_error()), - Value::Bool(_) => Err(bool_sort_error()), - Value::Function(_) => Err(fn_sort_error()), - }, + Expr::Block { bindings, body } => into_element(self.eval_block(bindings, body)?), Expr::Ident(name) => match self.state.env.get(name) { Some(Value::Element(value)) => Ok(value.clone()), Some(Value::Index(_)) => Err(index_sort_error()), @@ -436,20 +431,8 @@ impl GameRuntime { )), UnaryOp::Not => Err(bool_sort_error()), }, - Expr::Apply { .. } => match self.eval_value(expr)? { - Value::Element(value) => Ok(value), - Value::Index(_) => Err(index_sort_error()), - Value::Bool(_) => Err(bool_sort_error()), - Value::Function(_) => Err(fn_sort_error()), - }, + Expr::Apply { .. } | Expr::If { .. } => into_element(self.eval_value(expr)?), Expr::Binary { op, lhs, rhs } => self.eval_binary(*op, lhs, rhs), - Expr::If { .. } => match self.eval_value(expr)? { - Value::Element(value) => Ok(value), - Value::Index(_) => Err(index_sort_error()), - Value::Bool(_) => Err(bool_sort_error()), - Value::Function(_) => Err(fn_sort_error()), - }, - Expr::Relation { .. } => Err(bool_sort_error()), } } diff --git a/grundy/src/worlds/polynomial.rs b/grundy/src/worlds/polynomial.rs index 6dcef911..31e4b4ee 100644 --- a/grundy/src/worlds/polynomial.rs +++ b/grundy/src/worlds/polynomial.rs @@ -191,12 +191,7 @@ impl PolyRuntime { } } Expr::Lambda { .. } => Err(fn_sort_error()), - Expr::Block { bindings, body } => match self.eval_block(bindings, body)? { - Value::Element(value) => Ok(value), - Value::Index(_) => Err(index_sort_error()), - Value::Bool(_) => Err(bool_sort_error()), - Value::Function(_) => Err(fn_sort_error()), - }, + Expr::Block { bindings, body } => into_element(self.eval_block(bindings, body)?), Expr::Call { name, args } => self.eval_call(name, args), Expr::Unary { op, expr } => { let value = self.eval_element(expr)?; @@ -206,19 +201,8 @@ impl PolyRuntime { UnaryOp::Not => Err(bool_sort_error()), } } - Expr::Apply { .. } => match self.eval_value(expr)? { - Value::Element(value) => Ok(value), - Value::Index(_) => Err(index_sort_error()), - Value::Bool(_) => Err(bool_sort_error()), - Value::Function(_) => Err(fn_sort_error()), - }, + Expr::Apply { .. } | Expr::If { .. } => into_element(self.eval_value(expr)?), Expr::Binary { op, lhs, rhs } => self.eval_binary(*op, lhs, rhs), - Expr::If { .. } => match self.eval_value(expr)? { - Value::Element(value) => Ok(value), - Value::Index(_) => Err(index_sort_error()), - Value::Bool(_) => Err(bool_sort_error()), - Value::Function(_) => Err(fn_sort_error()), - }, Expr::Relation { .. } => Err(GrundyError::new( GrundyErrorKind::BoolSort, Span::point(0), diff --git a/grundy/src/worlds/rational_function.rs b/grundy/src/worlds/rational_function.rs index 4c04d319..0086cf47 100644 --- a/grundy/src/worlds/rational_function.rs +++ b/grundy/src/worlds/rational_function.rs @@ -180,12 +180,7 @@ impl RatFuncRuntime { } } Expr::Lambda { .. } => Err(fn_sort_error()), - Expr::Block { bindings, body } => match self.eval_block(bindings, body)? { - Value::Element(value) => Ok(value), - Value::Index(_) => Err(index_sort_error()), - Value::Bool(_) => Err(bool_sort_error()), - Value::Function(_) => Err(fn_sort_error()), - }, + Expr::Block { bindings, body } => into_element(self.eval_block(bindings, body)?), Expr::Call { name, args } => self.eval_call(name, args), Expr::Unary { op, expr } => { let value = self.eval_element(expr)?; @@ -195,19 +190,8 @@ impl RatFuncRuntime { UnaryOp::Not => Err(bool_sort_error()), } } - Expr::Apply { .. } => match self.eval_value(expr)? { - Value::Element(value) => Ok(value), - Value::Index(_) => Err(index_sort_error()), - Value::Bool(_) => Err(bool_sort_error()), - Value::Function(_) => Err(fn_sort_error()), - }, + Expr::Apply { .. } | Expr::If { .. } => into_element(self.eval_value(expr)?), Expr::Binary { op, lhs, rhs } => self.eval_binary(*op, lhs, rhs), - Expr::If { .. } => match self.eval_value(expr)? { - Value::Element(value) => Ok(value), - Value::Index(_) => Err(index_sort_error()), - Value::Bool(_) => Err(bool_sort_error()), - Value::Function(_) => Err(fn_sort_error()), - }, Expr::Relation { .. } => Err(GrundyError::new( GrundyErrorKind::BoolSort, Span::point(0), diff --git a/grundy/tests/conformance.rs b/grundy/tests/conformance.rs index a4ec4df0..5450cca9 100644 --- a/grundy/tests/conformance.rs +++ b/grundy/tests/conformance.rs @@ -70,7 +70,7 @@ fn word_conditionals_have_minimal_unambiguous_parentheses() { } for input in ["1 ? 2 : 3", ":", "1 + ?", "1 + :"] { - let err = parse_statement(input).expect_err("punctuation ternary is retired"); + let err = parse_statement(input).expect_err("punctuation ternary is not accepted"); assert_eq!(err.kind, GrundyErrorKind::Parse); assert_eq!( err.hint.as_deref(), @@ -79,6 +79,16 @@ fn word_conditionals_have_minimal_unambiguous_parentheses() { } } +#[test] +fn block_sorting_ignores_unreferenced_ambient_functions() { + let output = eval_to_string( + "integer 0", + "f := x ↦ x + 1\nif true then (a := 1; f@a) else 0", + ) + .expect("the block and conditional have element sort"); + assert_eq!(output, "2"); +} + #[test] fn trailing_nonterminal_tokens_drive_file_continuation() { let integer = eval_to_string( diff --git a/ogdoad.pyi b/ogdoad.pyi index 3960f197..9c6335ae 100644 --- a/ogdoad.pyi +++ b/ogdoad.pyi @@ -770,6 +770,22 @@ class Char2PsiTerm: def pole_order(self) -> Any: ... def __repr__(self) -> builtins.str: ... +class Char2SymmetryFactorization: + @property + def clifford_verified(self) -> builtins.bool: ... + @property + def degree(self) -> builtins.int: ... + @property + def dickson(self) -> builtins.int: ... + @property + def factor_count(self) -> builtins.int: ... + @property + def factors(self) -> list[list[builtins.int]]: ... + @property + def matrix(self) -> list[list[builtins.int]]: ... + def display(self) -> builtins.str: ... + def __repr__(self) -> builtins.str: ... + class Char2WittDecomp: @property def arf(self) -> Any: ... @@ -3500,6 +3516,22 @@ class FiniteFieldInvariants: def display(self, *args: Any, **kwargs: Any) -> Any: ... def __repr__(self) -> builtins.str: ... +class FiniteFieldNumericInvariants: + @property + def absolute_degree(self) -> builtins.int: ... + @property + def characteristic(self) -> builtins.int: ... + @property + def field_order(self) -> builtins.int: ... + @property + def level(self) -> builtins.int: ... + @property + def pythagoras_number(self) -> builtins.int: ... + @property + def u_invariant(self) -> builtins.int: ... + def display(self) -> builtins.str: ... + def __repr__(self) -> builtins.str: ... + class FiniteHermitianForm: def __init__(self, *args: Any, **kwargs: Any) -> None: ... @property @@ -3514,6 +3546,11 @@ class FiniteHermitianForm: @staticmethod def diagonal(*args: Any, **kwargs: Any) -> Any: ... def direct_sum(self, *args: Any, **kwargs: Any) -> Any: ... + def restrict_scalars(self, *args: Any, **kwargs: Any) -> Any: + """Restrict this Hermitian form to an ordinary quadratic algebra over its + fixed field. The concrete return type is `Fp2Algebra`, `F4Algebra`, + `Fp3Algebra`, or `Fp5Algebra`, preserving backend separation. + """ def __repr__(self) -> builtins.str: ... class FiniteHermitianInvariants: @@ -13588,6 +13625,10 @@ class HermitianForm: def from_gram(*args: Any, **kwargs: Any) -> Any: ... @staticmethod def from_skew(*args: Any, **kwargs: Any) -> Any: ... + def restrict_scalars(self, *args: Any, **kwargs: Any) -> Any: + """Restrict `h` over `Surcomplex/Surreal` to the ordinary quadratic form + `q(v)=h(v,v)` over `Surreal`, doubling the dimension. + """ def signature(self, *args: Any, **kwargs: Any) -> Any: ... def __repr__(self) -> builtins.str: ... @@ -37776,6 +37817,9 @@ def char2_monic_irreducible_factors(*args: Any, **kwargs: Any) -> Any: finite-field element indices, low degree first. """ +def char2_spinor_norm(q: Sequence[builtins.int], b: Mapping[tuple[builtins.int, builtins.int], builtins.int], matrix: Sequence[Sequence[builtins.int]], degree: builtins.int = 1) -> builtins.int: + """The additive characteristic-two spinor norm of an isometry of `F_{2^degree}`.""" + def classify_complex(n: builtins.int, r: builtins.int = 0) -> CliffordInvariants: """Classify a complex Clifford algebra directly from `(n, r)` (`n` nondegenerate dimensions, `r` null/radical) — the 2-fold table. Complement to @@ -37895,6 +37939,12 @@ def extraspecial_group_f2(qd: Sequence[builtins.bool], bmat: Sequence[builtins.i def extraspecial_group_nimber(alg: NimberAlgebra) -> Extraspecial2Group: ... +def factor_char2_isometry(q: Sequence[builtins.int], b: Mapping[tuple[builtins.int, builtins.int], builtins.int], matrix: Sequence[Sequence[builtins.int]], degree: builtins.int = 1) -> Char2SymmetryFactorization: + """Certify a vector-symmetry factorization of an isometry of `F_{2^degree}`.""" + +def finite_field_numeric_invariants(p: builtins.int, degree: builtins.int = 1) -> FiniteFieldNumericInvariants: + """Numeric finite-field invariants for `F_{p^degree}`.""" + def fit_f2_quadratic(*args: Any, **kwargs: Any) -> Any: """Fit an F₂ quadratic form to a subset of `F_2^k`, returning the recovered coefficients and Arf data if the set is a quadric. @@ -38058,10 +38108,9 @@ def leech_aut_order(*args: Any, **kwargs: Any) -> Any: ... def left_stop(*args: Any, **kwargs: Any) -> Any: ... -def level(*args: Any, **kwargs: Any) -> Any: - """The level/Stufe of the prime field `F_p`: the least `n` for which `-1` is a - sum of `n` squares. Supported primes are `2, 3, 5, 7, 11, 13`; other inputs - raise `ValueError`. +def level(p: builtins.int, degree: builtins.int = 1) -> builtins.int: + """The level/Stufe of `F_{p^degree}`: the least `n` for which `-1` is a sum of + `n` squares. """ def lexicode(*args: Any, **kwargs: Any) -> Any: ... @@ -38287,10 +38336,8 @@ def p_positions(*args: Any, **kwargs: Any) -> Any: Raises `ValueError` if any successor index is out of range. """ -def pythagoras_number(*args: Any, **kwargs: Any) -> Any: - """The Pythagoras number of the prime field `F_p`: least `n` such that every sum - of squares is already a sum of `n` squares. - """ +def pythagoras_number(p: builtins.int, degree: builtins.int = 1) -> builtins.int: + """The Pythagoras number of `F_{p^degree}`.""" def qexp_from_int(*args: Any, **kwargs: Any) -> Any: ... @@ -38466,10 +38513,9 @@ def type_ii_e8_sum_code(*args: Any, **kwargs: Any) -> Any: ... def type_ii_len16_code(*args: Any, **kwargs: Any) -> Any: ... -def u_invariant(*args: Any, **kwargs: Any) -> Any: - """The u-invariant of the prime field `F_p`: largest dimension of an anisotropic - quadratic form. In characteristic 2 this returns `None` because the diagonal - odd-characteristic model is not the right form theory. +def u_invariant(p: builtins.int, degree: builtins.int = 1) -> builtins.int: + """The quadratic u-invariant of `F_{p^degree}`. Characteristic two uses regular + quadratic forms. """ def verify_milgram(*args: Any, **kwargs: Any) -> Any: ... diff --git a/scripts/check_writeups.py b/scripts/check_writeups.py index a3e00d27..4e62ab47 100644 --- a/scripts/check_writeups.py +++ b/scripts/check_writeups.py @@ -12,10 +12,14 @@ PAPERS = ( "goldarf", + "witt_realization", "excess", + "nim_fast_multiplication", "linking_affine", "thermo_newton", "transfinite_arf", + "semiring_stability", + "misere_natural_realization", ) CITATION = re.compile( diff --git a/scripts/generate_stubs.py b/scripts/generate_stubs.py index ff4d7e94..b8d69eff 100644 --- a/scripts/generate_stubs.py +++ b/scripts/generate_stubs.py @@ -55,6 +55,12 @@ "is_isotropic_q": "(entries: Sequence[builtins.int]) -> builtins.bool", "hilbert_product": "(a: tuple[builtins.int, builtins.int], b: tuple[builtins.int, builtins.int]) -> builtins.int", "hilbert_symbol": "(p: builtins.int, a: builtins.int, b: builtins.int) -> builtins.int", + "finite_field_numeric_invariants": "(p: builtins.int, degree: builtins.int = 1) -> FiniteFieldNumericInvariants", + "level": "(p: builtins.int, degree: builtins.int = 1) -> builtins.int", + "pythagoras_number": "(p: builtins.int, degree: builtins.int = 1) -> builtins.int", + "u_invariant": "(p: builtins.int, degree: builtins.int = 1) -> builtins.int", + "char2_spinor_norm": "(q: Sequence[builtins.int], b: Mapping[tuple[builtins.int, builtins.int], builtins.int], matrix: Sequence[Sequence[builtins.int]], degree: builtins.int = 1) -> builtins.int", + "factor_char2_isometry": "(q: Sequence[builtins.int], b: Mapping[tuple[builtins.int, builtins.int], builtins.int], matrix: Sequence[Sequence[builtins.int]], degree: builtins.int = 1) -> Char2SymmetryFactorization", # nim field / finite-field toolkit (src/py/scalars.rs, all u128-based) "nim_add": "(a: builtins.int, b: builtins.int) -> builtins.int", "nim_mul": "(a: builtins.int, b: builtins.int) -> builtins.int", @@ -107,6 +113,20 @@ # "Class.method" -> signature. A leading "@staticmethod " marks a staticmethod # (the `self` is then omitted from the signature string). MEMBER_OVERRIDES: dict[str, str] = { + "FiniteFieldNumericInvariants.characteristic": "(self) -> builtins.int", + "FiniteFieldNumericInvariants.absolute_degree": "(self) -> builtins.int", + "FiniteFieldNumericInvariants.field_order": "(self) -> builtins.int", + "FiniteFieldNumericInvariants.level": "(self) -> builtins.int", + "FiniteFieldNumericInvariants.pythagoras_number": "(self) -> builtins.int", + "FiniteFieldNumericInvariants.u_invariant": "(self) -> builtins.int", + "FiniteFieldNumericInvariants.display": "(self) -> builtins.str", + "Char2SymmetryFactorization.degree": "(self) -> builtins.int", + "Char2SymmetryFactorization.matrix": "(self) -> list[list[builtins.int]]", + "Char2SymmetryFactorization.factors": "(self) -> list[list[builtins.int]]", + "Char2SymmetryFactorization.factor_count": "(self) -> builtins.int", + "Char2SymmetryFactorization.dickson": "(self) -> builtins.int", + "Char2SymmetryFactorization.clifford_verified": "(self) -> builtins.bool", + "Char2SymmetryFactorization.display": "(self) -> builtins.str", # scalar constructors "Nimber.__init__": "(self, value: builtins.int) -> None", "Rational.__init__": "(self, num: builtins.int, den: builtins.int = 1) -> None", diff --git a/src/clifford/blade.rs b/src/clifford/blade.rs index 8afb42c2..b0d67606 100644 --- a/src/clifford/blade.rs +++ b/src/clifford/blade.rs @@ -159,10 +159,9 @@ pub fn blade_subspace( /// mixed-grade multivectors are not blades. pub fn is_blade(alg: &CliffordAlgebra, a: &Multivector) -> bool { match homogeneous_grade(a) { - None => false, Some(0) => true, Some(k) if k <= alg.dim() => plucker_relations_hold(alg, a, k), - Some(_) => false, + None | Some(_) => false, } } diff --git a/src/clifford/engine/product.rs b/src/clifford/engine/product.rs index 7f944ba6..753cfab6 100644 --- a/src/clifford/engine/product.rs +++ b/src/clifford/engine/product.rs @@ -39,7 +39,7 @@ impl Metric { let c = self.bil(i, j); if !c.is_zero() { let coeff = if k & 1 == 0 { c } else { c.neg() }; - add_term(&mut out, t ^ (1 << j), coeff); + add_term(&mut out, t ^ (1 << j), &coeff); } k += 1; } @@ -51,7 +51,7 @@ impl Metric { let mut out = self.contract_vec_blade(i, t); if t & (1 << i) == 0 { let sign = wedge_sign::(1 << i, t); - add_term(&mut out, t | (1 << i), sign); + add_term(&mut out, t | (1 << i), &sign); } out } diff --git a/src/clifford/engine/terms.rs b/src/clifford/engine/terms.rs index e197e4d4..600712b4 100644 --- a/src/clifford/engine/terms.rs +++ b/src/clifford/engine/terms.rs @@ -25,16 +25,16 @@ pub(super) fn scale(mut terms: BTreeMap, s: &S) -> BTreeMap< /// Folds `other` into `into`, preserving the no-stored-zero invariant. pub(crate) fn merge(into: &mut BTreeMap, other: BTreeMap) { for (blade, coeff) in other { - add_term(into, blade, coeff); + add_term(into, blade, &coeff); } } /// Insert `coeff` for `blade` into `out`, adding to any existing coefficient. /// If the result is zero it is removed, preserving the "zeros never stored" /// invariant. -pub(crate) fn add_term(out: &mut BTreeMap, blade: u128, coeff: S) { +pub(crate) fn add_term(out: &mut BTreeMap, blade: u128, coeff: &S) { let e = out.entry(blade).or_insert_with(S::zero); - *e = e.add(&coeff); + *e = e.add(coeff); if e.is_zero() { out.remove(&blade); } @@ -55,7 +55,7 @@ pub(super) fn wedge_terms( } let coeff = ca.mul(cb).mul(&wedge_sign::(ba, bb)); if !coeff.is_zero() { - add_term(&mut out, ba | bb, coeff); + add_term(&mut out, ba | bb, &coeff); } } } diff --git a/src/clifford/hopf.rs b/src/clifford/hopf.rs index d60f42dc..64cdecfb 100644 --- a/src/clifford/hopf.rs +++ b/src/clifford/hopf.rs @@ -63,7 +63,7 @@ pub fn coproduct(alg: &CliffordAlgebra, mv: &Multivector) -> Mu let sign = w.terms.get(&mask_s).cloned().unwrap_or_else(S::zero); if !sign.is_zero() { let tens = t | (u << dim); - add_term(&mut out, tens, coeff.mul(&sign)); + add_term(&mut out, tens, &coeff.mul(&sign)); } if t == 0 { break; diff --git a/src/clifford/spinor.rs b/src/clifford/spinor.rs index 855f0974..cdc990a2 100644 --- a/src/clifford/spinor.rs +++ b/src/clifford/spinor.rs @@ -626,7 +626,7 @@ pub fn lazy_spinor_rep(alg: &CliffordAlgebra) -> Option) -> fmt::Result { + let message = match self { + Self::WrongCharacteristic => "the scalar field must have characteristic two", + Self::UnsupportedField => { + "automatic factorization needs a supported enumerable characteristic-two field" + } + Self::GeneralMetric => "ordered-contraction metrics are not quadratic forms", + Self::DimensionMismatch => { + "the transformation must be a square matrix of the metric dimension" + } + Self::SingularPolarForm => "the quadratic form must have nonsingular polar form", + Self::NotIsometry => "the matrix does not preserve the quadratic form", + Self::IsotropicFactor => "every vector-symmetry factor must be anisotropic", + Self::FactorizationFailed => "Wall-form elimination did not produce a factorization", + Self::ProductMismatch => "the vector symmetries do not recompose to the matrix", + Self::DicksonMismatch => "factor parity does not equal the Dickson invariant", + }; + f.write_str(message) + } +} + +impl Error for Char2FactorizationError {} + +/// An exact certificate that a regular characteristic-two isometry is a +/// product of vector symmetries. +/// +/// The fields are private: instances can only be constructed after exact +/// isometry, recomposition, and Dickson-parity checks. Matrices act on column +/// vectors, and the listed factors multiply from left to right. +#[derive(Clone, Debug, PartialEq)] +pub struct Char2SymmetryFactorization { + metric: Metric, + matrix: Vec>, + factors: Vec>, +} + +impl Char2SymmetryFactorization { + /// The quadratic metric certified by this factorization. + pub fn metric(&self) -> &Metric { + &self.metric + } + + /// The certified isometry, as a row-major matrix acting on columns. + pub fn matrix(&self) -> &[Vec] { + &self.matrix + } + + /// Anisotropic roots of the vector symmetries, in multiplication order. + pub fn factors(&self) -> &[Vec] { + &self.factors + } + + /// Number of vector-symmetry factors. + pub fn factor_count(&self) -> usize { + self.factors.len() + } + + /// Dickson invariant, equal to both `rank(I + g) mod 2` and factor parity. + pub fn dickson(&self) -> u128 { + (self.factors.len() % 2) as u128 + } + + /// The product of the factor vectors in the corresponding Clifford algebra. + pub fn versor(&self) -> Multivector { + let alg = CliffordAlgebra::new(self.metric.dim(), self.metric.clone()); + self.factors.iter().fold(alg.scalar(S::one()), |acc, root| { + alg.mul(&acc, &vector_multivector(&alg, root)) + }) + } + + /// Whether the certificate's Clifford versor has the certified grade parity + /// and its twisted adjoint agrees with the matrix on every basis vector. + pub fn verifies_clifford_action(&self) -> bool { + let alg = CliffordAlgebra::new(self.metric.dim(), self.metric.clone()); + let versor = self.versor(); + if crate::clifford::versor_grade_parity(&versor) != Some(self.dickson()) { + return false; + } + (0..self.metric.dim()).all(|j| { + alg.twisted_sandwich(&versor, &alg.e(j)) + == Some(vector_multivector( + &alg, + &(0..self.metric.dim()) + .map(|i| self.matrix[i][j]) + .collect::>(), + )) + }) + } + + /// Returns the canonical compact display representation. + pub fn display(&self) -> String { + self.to_string() + } +} + +impl fmt::Display for Char2SymmetryFactorization { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Char2SymmetryFactorization(dim={}, factor_count={}, dickson={})", + self.metric.dim(), + self.factor_count(), + self.dickson() + ) + } +} + +fn identity(n: usize) -> Vec> { + (0..n) + .map(|i| { + (0..n) + .map(|j| if i == j { S::one() } else { S::zero() }) + .collect() + }) + .collect() +} + +fn matrix_shape(matrix: &[Vec], n: usize) -> bool { + matrix.len() == n && matrix.iter().all(|row| row.len() == n) +} + +fn matrix_mul(left: &[Vec], right: &[Vec]) -> Vec> { + let n = left.len(); + (0..n) + .map(|i| { + (0..n) + .map(|j| (0..n).fold(S::zero(), |acc, k| acc.add(&left[i][k].mul(&right[k][j])))) + .collect() + }) + .collect() +} + +fn add_vectors(left: &[S], right: &[S]) -> Vec { + left.iter().zip(right).map(|(a, b)| a.add(b)).collect() +} + +fn scale_add_vectors(left: &[S], scalar: &S, right: &[S]) -> Vec { + left.iter() + .zip(right) + .map(|(a, b)| a.add(&scalar.mul(b))) + .collect() +} + +fn polar_matrix(metric: &Metric) -> Vec> { + let n = metric.dim(); + let mut polar = vec![vec![S::zero(); n]; n]; + for (&(i, j), value) in metric.b() { + polar[i][j] = *value; + polar[j][i] = *value; + } + polar +} + +fn bilinear(polar: &[Vec], left: &[S], right: &[S]) -> S { + left.iter().enumerate().fold(S::zero(), |outer, (i, a)| { + let row = right + .iter() + .enumerate() + .fold(S::zero(), |inner, (j, b)| inner.add(&polar[i][j].mul(b))); + outer.add(&a.mul(&row)) + }) +} + +fn quadratic(metric: &Metric, vector: &[S]) -> S { + let diagonal = metric + .q() + .iter() + .zip(vector) + .fold(S::zero(), |acc, (q, x)| acc.add(&q.mul(&x.mul(x)))); + metric.b().iter().fold(diagonal, |acc, (&(i, j), b)| { + acc.add(&b.mul(&vector[i].mul(&vector[j]))) + }) +} + +fn vector_symmetry_unchecked( + metric: &Metric, + polar: &[Vec], + root: &[S], +) -> Option>> { + let n = metric.dim(); + let qinv = quadratic(metric, root).inv()?; + let mut symmetry = identity::(n); + for j in 0..n { + let mut ej = vec![S::zero(); n]; + ej[j] = S::one(); + let coefficient = bilinear(polar, &ej, root).mul(&qinv); + for i in 0..n { + symmetry[i][j] = symmetry[i][j].add(&root[i].mul(&coefficient)); + } + } + Some(symmetry) +} + +/// Matrix of the vector symmetry `x ↦ x + B(x,v)Q(v)⁻¹v`. +pub fn char2_vector_symmetry( + metric: &Metric, + root: &[S], +) -> Result>, Char2FactorizationError> { + if S::characteristic() != 2 { + return Err(Char2FactorizationError::WrongCharacteristic); + } + if metric.has_upper() { + return Err(Char2FactorizationError::GeneralMetric); + } + if root.len() != metric.dim() { + return Err(Char2FactorizationError::DimensionMismatch); + } + vector_symmetry_unchecked(metric, &polar_matrix(metric), root) + .ok_or(Char2FactorizationError::IsotropicFactor) +} + +fn is_isometry(metric: &Metric, polar: &[Vec], matrix: &[Vec]) -> bool { + let n = metric.dim(); + let columns: Vec> = (0..n) + .map(|j| (0..n).map(|i| matrix[i][j]).collect()) + .collect(); + if (0..n).any(|j| quadratic(metric, &columns[j]) != metric.q()[j]) { + return false; + } + (0..n).all(|i| (0..n).all(|j| bilinear(polar, &columns[i], &columns[j]) == polar[i][j])) +} + +fn residual_matrix(matrix: &[Vec]) -> Vec> { + let mut residual = matrix.to_vec(); + for (i, row) in residual.iter_mut().enumerate() { + row[i] = row[i].add(&S::one()); + } + residual +} + +fn columns_as_rows(columns: &[Vec], nrows: usize) -> Vec> { + (0..nrows) + .map(|i| columns.iter().map(|column| column[i]).collect()) + .collect() +} + +fn residual_basis(residual: &[Vec]) -> Option<(Vec>, Vec)> { + let n = residual.len(); + let mut columns: Vec> = Vec::new(); + let mut preimages = Vec::new(); + let mut rank = 0; + for j in 0..n { + let column: Vec = (0..n).map(|i| residual[i][j]).collect(); + let mut candidate = columns.clone(); + candidate.push(column.clone()); + let candidate_rank = field::unit_pivot_rank(columns_as_rows(&candidate, n))?; + if candidate_rank > rank { + columns.push(column); + preimages.push(j); + rank = candidate_rank; + } + } + Some((columns, preimages)) +} + +fn coordinate_bilinear(gram: &[Vec], left: &[S], right: &[S]) -> S { + left.iter().enumerate().fold(S::zero(), |outer, (i, a)| { + let row = right + .iter() + .enumerate() + .fold(S::zero(), |inner, (j, b)| inner.add(&gram[i][j].mul(b))); + outer.add(&a.mul(&row)) + }) +} + +#[derive(Clone)] +struct WallVector { + ambient: Vec, + coordinates: Vec, +} + +fn continue_semi_orthogonal_basis( + gram: &[Vec], + basis: &[WallVector], + pivot_index: usize, + pivot: WallVector, + clear_first_argument: bool, +) -> Option>> { + let mut candidate = basis.to_vec(); + candidate[pivot_index] = pivot; + candidate.swap(0, pivot_index); + let diagonal = coordinate_bilinear(gram, &candidate[0].coordinates, &candidate[0].coordinates); + let diagonal_inv = diagonal.inv()?; + for j in 1..candidate.len() { + let coefficient = if clear_first_argument { + coordinate_bilinear(gram, &candidate[0].coordinates, &candidate[j].coordinates) + } else { + coordinate_bilinear(gram, &candidate[j].coordinates, &candidate[0].coordinates) + } + .mul(&diagonal_inv); + candidate[j].ambient = + scale_add_vectors(&candidate[j].ambient, &coefficient, &candidate[0].ambient); + candidate[j].coordinates = scale_add_vectors( + &candidate[j].coordinates, + &coefficient, + &candidate[0].coordinates, + ); + } + let mut rest = semi_orthogonal_basis(gram, &candidate[1..], clear_first_argument)?; + let mut result = vec![candidate[0].clone()]; + result.append(&mut rest); + Some(result) +} + +fn semi_orthogonal_basis( + gram: &[Vec], + basis: &[WallVector], + clear_first_argument: bool, +) -> Option>> { + if basis.is_empty() { + return Some(Vec::new()); + } + for i in 0..basis.len() { + if !coordinate_bilinear(gram, &basis[i].coordinates, &basis[i].coordinates).is_zero() { + if let Some(result) = continue_semi_orthogonal_basis( + gram, + basis, + i, + basis[i].clone(), + clear_first_argument, + ) { + return Some(result); + } + } + } + for i in 0..basis.len() { + for j in (i + 1)..basis.len() { + for scalar_index in 1..S::field_order() { + let scalar = S::from_index(scalar_index); + let pivot = WallVector { + ambient: scale_add_vectors(&basis[i].ambient, &scalar, &basis[j].ambient), + coordinates: scale_add_vectors( + &basis[i].coordinates, + &scalar, + &basis[j].coordinates, + ), + }; + if coordinate_bilinear(gram, &pivot.coordinates, &pivot.coordinates).is_zero() { + continue; + } + if let Some(result) = + continue_semi_orthogonal_basis(gram, basis, i, pivot, clear_first_argument) + { + return Some(result); + } + } + } + } + // Taylor's F2 repair mixes an anisotropic line with a hyperbolic pair. + if S::field_order() == 2 { + for i in 0..basis.len() { + for j in (i + 1)..basis.len() { + for k in (j + 1)..basis.len() { + let pivot = WallVector { + ambient: add_vectors( + &add_vectors(&basis[i].ambient, &basis[j].ambient), + &basis[k].ambient, + ), + coordinates: add_vectors( + &add_vectors(&basis[i].coordinates, &basis[j].coordinates), + &basis[k].coordinates, + ), + }; + if coordinate_bilinear(gram, &pivot.coordinates, &pivot.coordinates).is_zero() { + continue; + } + if let Some(result) = + continue_semi_orthogonal_basis(gram, basis, i, pivot, clear_first_argument) + { + return Some(result); + } + } + } + } + } + None +} + +fn factor_nonalternating_wall( + polar: &[Vec], + matrix: &[Vec], +) -> Option>> { + let residual = residual_matrix(matrix); + let (basis, preimages) = residual_basis(&residual)?; + let rank = basis.len(); + if rank == 0 { + return Some(Vec::new()); + } + let mut gram = vec![vec![S::zero(); rank]; rank]; + for i in 0..rank { + let mut preimage = vec![S::zero(); polar.len()]; + preimage[preimages[i]] = S::one(); + for j in 0..rank { + gram[i][j] = bilinear(polar, &preimage, &basis[j]); + } + } + let wall_basis: Vec> = basis + .into_iter() + .enumerate() + .map(|(i, ambient)| WallVector { + ambient, + coordinates: (0..rank) + .map(|j| if i == j { S::one() } else { S::zero() }) + .collect(), + }) + .collect(); + semi_orthogonal_basis(&gram, &wall_basis, true) + .or_else(|| semi_orthogonal_basis(&gram, &wall_basis, false)) + .map(|vectors| vectors.into_iter().map(|vector| vector.ambient).collect()) +} + +fn with_anisotropic_coordinate_vector( + metric: &Metric, + mut visit: impl FnMut(Vec) -> Option, +) -> Option { + let n = metric.dim(); + for i in 0..n { + let mut vector = vec![S::zero(); n]; + vector[i] = S::one(); + if !quadratic(metric, &vector).is_zero() { + if let Some(result) = visit(vector) { + return Some(result); + } + } + } + for i in 0..n { + for j in (i + 1)..n { + for scalar_index in 1..S::field_order() { + let mut vector = vec![S::zero(); n]; + vector[i] = S::one(); + vector[j] = S::from_index(scalar_index); + if !quadratic(metric, &vector).is_zero() { + if let Some(result) = visit(vector) { + return Some(result); + } + } + } + } + } + None +} + +fn product_of_symmetries( + metric: &Metric, + factors: &[Vec], +) -> Result>, Char2FactorizationError> { + let polar = polar_matrix(metric); + factors + .iter() + .try_fold(identity(metric.dim()), |product, root| { + if root.len() != metric.dim() { + return Err(Char2FactorizationError::DimensionMismatch); + } + let symmetry = vector_symmetry_unchecked(metric, &polar, root) + .ok_or(Char2FactorizationError::IsotropicFactor)?; + Ok(matrix_mul(&product, &symmetry)) + }) +} + +/// Certify a proposed characteristic-two vector-symmetry factorization. +pub fn certify_char2_symmetry_factorization( + metric: &Metric, + matrix: &[Vec], + factors: Vec>, +) -> Result, Char2FactorizationError> { + if S::characteristic() != 2 { + return Err(Char2FactorizationError::WrongCharacteristic); + } + if metric.has_upper() { + return Err(Char2FactorizationError::GeneralMetric); + } + let n = metric.dim(); + if !matrix_shape(matrix, n) { + return Err(Char2FactorizationError::DimensionMismatch); + } + let polar = polar_matrix(metric); + if field::unit_pivot_rank(polar.clone()) != Some(n) { + return Err(Char2FactorizationError::SingularPolarForm); + } + if !is_isometry(metric, &polar, matrix) { + return Err(Char2FactorizationError::NotIsometry); + } + if product_of_symmetries(metric, &factors)? != matrix { + return Err(Char2FactorizationError::ProductMismatch); + } + let dickson = dickson_matrix_char2(matrix).ok_or(Char2FactorizationError::DimensionMismatch)?; + if (factors.len() % 2) as u128 != dickson { + return Err(Char2FactorizationError::DicksonMismatch); + } + Ok(Char2SymmetryFactorization { + metric: metric.clone(), + matrix: matrix.to_vec(), + factors, + }) +} + +fn certify_with_wall_orders( + metric: &Metric, + matrix: &[Vec], + prefix: &[Vec], + wall_factors: Vec>, +) -> Option> { + for wall_order in [ + wall_factors.clone(), + wall_factors.into_iter().rev().collect(), + ] { + let mut factors = prefix.to_vec(); + factors.extend(wall_order); + if let Ok(certificate) = certify_char2_symmetry_factorization(metric, matrix, factors) { + return Some(certificate); + } + } + None +} + +/// Factor an isometry of a regular characteristic-two quadratic space into +/// vector symmetries and return an exact certificate. +/// +/// Automatic root selection uses [`FiniteChar2Field`]'s finite enumeration. +/// The invariant and certificate checker remain generic through +/// [`char2_spinor_norm`] and [`certify_char2_symmetry_factorization`]. +/// +/// The algorithm uses Wall's residual space and Wall form. A nonalternating +/// Wall form is semi-orthogonalized directly. In the alternating case, one or +/// two auxiliary anisotropic symmetries convert it to a decomposable +/// nonalternating case; those symmetries remain in the final exact product. +pub fn factor_char2_isometry( + metric: &Metric, + matrix: &[Vec], +) -> Result, Char2FactorizationError> { + if !S::is_supported_char2_field() { + return Err(Char2FactorizationError::UnsupportedField); + } + if S::characteristic() != 2 { + return Err(Char2FactorizationError::WrongCharacteristic); + } + if metric.has_upper() { + return Err(Char2FactorizationError::GeneralMetric); + } + let n = metric.dim(); + if !matrix_shape(matrix, n) { + return Err(Char2FactorizationError::DimensionMismatch); + } + let polar = polar_matrix(metric); + if field::unit_pivot_rank(polar.clone()) != Some(n) { + return Err(Char2FactorizationError::SingularPolarForm); + } + if !is_isometry(metric, &polar, matrix) { + return Err(Char2FactorizationError::NotIsometry); + } + + if let Some(factors) = factor_nonalternating_wall(&polar, matrix) { + if let Some(certificate) = certify_with_wall_orders(metric, matrix, &[], factors) { + return Ok(certificate); + } + } + + if let Some(certificate) = with_anisotropic_coordinate_vector(metric, |auxiliary| { + let symmetry = vector_symmetry_unchecked(metric, &polar, &auxiliary) + .expect("visitor receives only anisotropic vectors"); + let modified = matrix_mul(&symmetry, matrix); + let factors = factor_nonalternating_wall(&polar, &modified)?; + certify_with_wall_orders(metric, matrix, &[auxiliary], factors) + }) { + return Ok(certificate); + } + + if let Some(certificate) = with_anisotropic_coordinate_vector(metric, |first| { + let first_symmetry = vector_symmetry_unchecked(metric, &polar, &first) + .expect("visitor receives only anisotropic vectors"); + with_anisotropic_coordinate_vector(metric, |second| { + let second_symmetry = vector_symmetry_unchecked(metric, &polar, &second) + .expect("visitor receives only anisotropic vectors"); + let modified = matrix_mul(&second_symmetry, &matrix_mul(&first_symmetry, matrix)); + let factors = factor_nonalternating_wall(&polar, &modified)?; + certify_with_wall_orders(metric, matrix, &[first.clone(), second], factors) + }) + }) { + return Ok(certificate); + } + Err(Char2FactorizationError::FactorizationFailed) +} + +/// The Dickson invariant `D(g) = rank(I + g) mod 2` of a square matrix over an +/// exact characteristic-two field. +pub fn dickson_matrix_char2(g: &[Vec]) -> Option { + let n = g.len(); + if S::characteristic() != 2 || !matrix_shape(g, n) { + return None; + } + field::unit_pivot_rank(residual_matrix(g)).map(|rank| (rank % 2) as u128) +} + +/// The additive characteristic-two spinor norm of an isometry, equivalently +/// its Dickson invariant `rank(I + g) mod 2`. +/// +/// Unlike [`dickson_matrix_char2`], this entry point verifies that the metric is +/// a regular quadratic form and that `g` is an isometry. The returned bit +/// agrees with the parity of every certified vector-symmetry factorization, +/// including when the factorization is not minimal. It remains defined on the +/// exceptional split four-space over `F_2`, where not every isometry is +/// generated by vector symmetries. +pub fn char2_spinor_norm( + metric: &Metric, + matrix: &[Vec], +) -> Result { + if S::characteristic() != 2 { + return Err(Char2FactorizationError::WrongCharacteristic); + } + if metric.has_upper() { + return Err(Char2FactorizationError::GeneralMetric); + } + let n = metric.dim(); + if !matrix_shape(matrix, n) { + return Err(Char2FactorizationError::DimensionMismatch); + } + let polar = polar_matrix(metric); + if field::unit_pivot_rank(polar.clone()) != Some(n) { + return Err(Char2FactorizationError::SingularPolarForm); + } + if !is_isometry(metric, &polar, matrix) { + return Err(Char2FactorizationError::NotIsometry); + } + dickson_matrix_char2(matrix).ok_or(Char2FactorizationError::DimensionMismatch) +} /// The **Dickson invariant** `D(g) ∈ F₂` of an orthogonal transformation `g`, /// given as an n×n matrix over a nim-field: `D(g) = dim Im(g − I) mod 2` @@ -18,7 +675,7 @@ pub fn dickson_matrix(g: &[Vec]) -> u128 { let n = g.len(); let mut m: Vec> = g.to_vec(); for i in 0..n { - m[i][i] = nim_add(m[i][i], 1); // g − I (= g + I in char 2) + m[i][i] = nim_add(m[i][i], 1); } (f2::nim_rank(m) % 2) as u128 } @@ -29,31 +686,50 @@ pub fn dickson_matrix(g: &[Vec]) -> u128 { /// a reflection) has `D = 1`. Returns `None` if the multivector is not of /// homogeneous grade parity (hence not a versor) or is zero. pub fn dickson_of_versor(alg: &CliffordAlgebra, v: &Multivector) -> Option { - // The Dickson invariant of a versor is its grade parity, a fact independent of - // the scalar field — so this is the char-2 specialisation of the generic - // `clifford::versor_grade_parity`. let dickson = crate::clifford::versor_grade_parity(v)?; alg.versor_inverse(v)?; Some(dickson) } +fn vector_multivector( + alg: &CliffordAlgebra, + coordinates: &[S], +) -> Multivector { + coordinates + .iter() + .enumerate() + .fold(alg.zero(), |acc, (i, coefficient)| { + alg.add(&acc, &alg.scalar_mul(coefficient, &alg.e(i))) + }) +} + #[cfg(test)] mod tests { use super::*; - use crate::clifford::{CliffordAlgebra, Metric}; - use crate::scalar::nim_mul; + use crate::scalar::{nim_mul, Fp, Fpn}; + use std::collections::BTreeMap; + + type F2 = Fp<2>; + type F4 = Fpn<2, 2>; + type F8 = Fpn<2, 3>; + + fn hyperbolic(planes: usize) -> Metric { + let mut b = BTreeMap::new(); + for plane in 0..planes { + b.insert((2 * plane, 2 * plane + 1), S::one()); + } + Metric::new(vec![S::zero(); 2 * planes], b) + } + + fn product(metric: &Metric, roots: &[Vec]) -> Vec> { + product_of_symmetries(metric, roots).unwrap() + } #[test] fn dickson_separates_rotations_from_reflections() { - // identity is a rotation: D = 0. assert_eq!(dickson_matrix(&[vec![1, 0], vec![0, 1]]), 0); - // the swap (0 1; 1 0) preserves the hyperbolic form x0 x1 and is a - // reflection (odd): D = 1. assert_eq!(dickson_matrix(&[vec![0, 1], vec![1, 0]]), 1); - // a hyperbolic "rotation" diag(t, t⁻¹) preserves x0 x1; for t=*2 in F₄, - // t⁻¹ = *3, so g = diag(*2,*3): D = 0 (in SO). assert_eq!(dickson_matrix(&[vec![2, 0], vec![0, 3]]), 0); - // composing two reflections (here swap∘swap = identity) gives D = 0. let swap = [[0u128, 1], [1, 0]]; let mut comp = vec![vec![0u128; 2]; 2]; for i in 0..2 { @@ -74,14 +750,216 @@ mod tests { let scalar_one = alg.scalar(Nimber(1)); let e0 = alg.e(0); let e0e1 = alg.mul(&alg.e(0), &alg.e(1)); - assert_eq!(dickson_of_versor(&alg, &scalar_one), Some(0)); // identity rotor - assert_eq!(dickson_of_versor(&alg, &e0), Some(1)); // a vector = a reflection - assert_eq!(dickson_of_versor(&alg, &e0e1), Some(0)); // a bivector = a rotor - // mixed parity ⇒ not a versor + assert_eq!(dickson_of_versor(&alg, &scalar_one), Some(0)); + assert_eq!(dickson_of_versor(&alg, &e0), Some(1)); + assert_eq!(dickson_of_versor(&alg, &e0e1), Some(0)); let mixed = alg.add(&e0, &e0e1); assert_eq!(dickson_of_versor(&alg, &mixed), None); let null_alg = CliffordAlgebra::new(1, Metric::grassmann(1)); assert_eq!(dickson_of_versor(&null_alg, &null_alg.e(0)), None); } + + #[test] + fn identity_and_single_symmetry_are_certified_over_f2() { + let metric = hyperbolic::(1); + let identity_certificate = factor_char2_isometry(&metric, &identity(2)).unwrap(); + assert!(identity_certificate.factors().is_empty()); + assert_eq!(identity_certificate.dickson(), 0); + assert!(identity_certificate.verifies_clifford_action()); + + let root = vec![F2::one(), F2::one()]; + let symmetry = char2_vector_symmetry(&metric, &root).unwrap(); + let certificate = factor_char2_isometry(&metric, &symmetry).unwrap(); + assert_eq!(certificate.factor_count(), 1); + assert_eq!(certificate.dickson(), 1); + assert!(certificate.verifies_clifford_action()); + } + + #[test] + fn extension_field_products_recompose_and_match_clifford_parity() { + let metric4 = hyperbolic::(1); + let a = F4::generator(); + let roots4 = vec![vec![F4::one(), F4::one()], vec![a, F4::one()]]; + let target4 = product(&metric4, &roots4); + let certificate4 = factor_char2_isometry(&metric4, &target4).unwrap(); + assert_eq!(certificate4.matrix(), target4); + assert_eq!(certificate4.dickson(), 0); + assert!(certificate4.verifies_clifford_action()); + + let metric8 = hyperbolic::(2); + let b = F8::generator(); + let roots8 = vec![ + vec![F8::one(), F8::one(), F8::zero(), F8::zero()], + vec![b, F8::one(), F8::one(), F8::one()], + vec![F8::one(), b, b, b], + ]; + let target8 = product(&metric8, &roots8); + let certificate8 = factor_char2_isometry(&metric8, &target8).unwrap(); + assert_eq!(certificate8.matrix(), target8); + assert_eq!(certificate8.dickson(), 1); + assert!(certificate8.verifies_clifford_action()); + } + + #[test] + fn split_four_space_over_f2_exposes_the_reflection_generation_exception() { + let metric = hyperbolic::(2); + let polar = polar_matrix(&metric); + let target = (0u128..(1 << 16)) + .map(|bits| { + (0..4) + .map(|i| { + (0..4) + .map(|j| F2::from_u128((bits >> (4 * i + j)) & 1)) + .collect::>() + }) + .collect::>() + }) + .find(|matrix| { + let residual = residual_matrix(matrix); + let Some((basis, _)) = residual_basis(&residual) else { + return false; + }; + !basis.is_empty() + && basis + .iter() + .all(|vector| quadratic(&metric, vector).is_zero()) + && is_isometry(&metric, &polar, matrix) + }) + .expect("O+(4,2) contains a nontrivial alternating-Wall isometry"); + + assert!(factor_nonalternating_wall(&polar, &target).is_none()); + assert_eq!( + factor_char2_isometry(&metric, &target), + Err(Char2FactorizationError::FactorizationFailed) + ); + assert_eq!(char2_spinor_norm(&metric, &target), Ok(0)); + } + + #[test] + fn totally_singular_f4_residual_gets_the_rank_plus_two_factorization() { + let metric = hyperbolic::(2); + let target = vec![ + vec![F4::zero(), F4::zero(), F4::zero(), F4::one()], + vec![F4::zero(), F4::zero(), F4::one(), F4::zero()], + vec![F4::zero(), F4::one(), F4::zero(), F4::zero()], + vec![F4::one(), F4::zero(), F4::zero(), F4::zero()], + ]; + let residual = residual_matrix(&target); + let (basis, _) = residual_basis(&residual).unwrap(); + assert_eq!(basis.len(), 2); + assert!(basis + .iter() + .all(|vector| quadratic(&metric, vector).is_zero())); + + let certificate = factor_char2_isometry(&metric, &target).unwrap(); + assert_eq!(certificate.factor_count(), 4); + assert_eq!(certificate.matrix(), target); + assert_eq!(certificate.dickson(), 0); + assert!(certificate.verifies_clifford_action()); + } + + #[test] + fn every_element_of_the_f2_split_four_symmetry_subgroup_is_certified() { + use std::collections::BTreeSet; + + let metric = hyperbolic::(2); + let roots: Vec> = (1u128..16) + .map(|bits| { + (0..4) + .map(|i| F2::from_u128((bits >> i) & 1)) + .collect::>() + }) + .filter(|root| !quadratic(&metric, root).is_zero()) + .collect(); + let generators: Vec>> = roots + .iter() + .map(|root| char2_vector_symmetry(&metric, root).unwrap()) + .collect(); + let key = |matrix: &[Vec]| { + matrix + .iter() + .flatten() + .enumerate() + .fold(0u128, |bits, (i, value)| { + bits | ((!value.is_zero()) as u128) << i + }) + }; + let mut seen = BTreeSet::from([key(&identity::(4))]); + let mut subgroup = vec![identity::(4)]; + let mut cursor = 0; + while cursor < subgroup.len() { + let current = subgroup[cursor].clone(); + for generator in &generators { + let next = matrix_mul(¤t, generator); + if seen.insert(key(&next)) { + subgroup.push(next); + } + } + cursor += 1; + } + assert_eq!(subgroup.len(), 36); + for matrix in subgroup { + let certificate = factor_char2_isometry(&metric, &matrix).unwrap(); + assert_eq!(certificate.matrix(), matrix); + assert_eq!( + char2_spinor_norm(&metric, &matrix), + Ok(certificate.dickson()) + ); + assert!(certificate.verifies_clifford_action()); + } + } + + #[test] + fn nimber_factorization_certificates_remain_generic() { + let metric = hyperbolic::(1); + let roots = vec![vec![Nimber(1), Nimber(1)], vec![Nimber(2), Nimber(1)]]; + let target = product(&metric, &roots); + let certificate = certify_char2_symmetry_factorization(&metric, &target, roots).unwrap(); + assert_eq!(certificate.matrix(), target); + assert!(certificate.verifies_clifford_action()); + } + + #[test] + fn invalid_inputs_are_rejected_explicitly() { + let odd = hyperbolic::>(1); + assert_eq!( + char2_spinor_norm(&odd, &identity(2)), + Err(Char2FactorizationError::WrongCharacteristic) + ); + let singular = Metric::diagonal(vec![F2::one(), F2::one()]); + assert_eq!( + factor_char2_isometry(&singular, &identity(2)), + Err(Char2FactorizationError::SingularPolarForm) + ); + let regular = hyperbolic::(1); + assert_eq!( + factor_char2_isometry(®ular, &[vec![F2::one()]]), + Err(Char2FactorizationError::DimensionMismatch) + ); + assert_eq!( + factor_char2_isometry( + ®ular, + &[vec![F2::one(), F2::one()], vec![F2::zero(), F2::one()]], + ), + Err(Char2FactorizationError::NotIsometry) + ); + assert_eq!( + char2_vector_symmetry(®ular, &[F2::one(), F2::zero()]), + Err(Char2FactorizationError::IsotropicFactor) + ); + } + + #[test] + fn certificate_constructor_rejects_wrong_products() { + let metric = hyperbolic::(1); + assert_eq!( + certify_char2_symmetry_factorization( + &metric, + &identity(2), + vec![vec![F2::one(), F2::one()]], + ), + Err(Char2FactorizationError::ProductMismatch) + ); + } } diff --git a/src/forms/char2/mod.rs b/src/forms/char2/mod.rs index 5d142d09..aa42f203 100644 --- a/src/forms/char2/mod.rs +++ b/src/forms/char2/mod.rs @@ -1,11 +1,11 @@ //! Characteristic-2 quadratic-form invariants. //! -//! The module contains five related constructions: +//! The module contains five related shelves: //! //! * `arf` classifies the quadratic form / Clifford algebra through the Arf //! invariant. -//! * `dickson` classifies orthogonal transformations by the Dickson invariant, -//! the determinant replacement in characteristic 2. +//! * `dickson` classifies orthogonal transformations by the additive spinor +//! norm / Dickson invariant and certifies vector-symmetry factorizations. //! * `brown` lifts the `ℤ/2` Arf bit to the `ℤ/8` Brown invariant of a //! `Z/4`-valued quadratic refinement, with `β(2q') = 4 Arf(q')`. //! * `extraspecial` turns a nonsingular `F_2` quadratic form into the @@ -16,8 +16,8 @@ //! that the char-2 local–global layer is generic over. //! //! The public exports stay flat (`forms::arf_invariant`, -//! `forms::dickson_matrix`, `forms::FiniteChar2Field`, …), matching the rest of the -//! forms pillar. +//! `forms::char2_spinor_norm`, `forms::FiniteChar2Field`, …), matching the rest +//! of the forms pillar. mod arf; mod brown; diff --git a/src/forms/classify.rs b/src/forms/classify.rs index 74393c87..2a3aeb0d 100644 --- a/src/forms/classify.rs +++ b/src/forms/classify.rs @@ -14,18 +14,34 @@ //! `Rational`'s Witt invariant is the full Hasse–Minkowski datum and surcomplex's //! is `W(ℂ) = ℤ/2`; neither is a `WittClassG`, so those two backends implement //! [`ClassifyForm`] but not [`ClassifyWitt`]. +//! +//! [`ClassifyMilnor`] dispatches the strict characteristic-not-two maps +//! `e_n : I^n/I^(n+1) -> K^M_n/2` for `n <= 2`. Unlike a compact invariant +//! report, these methods reject forms outside the requested fundamental-ideal +//! power. +//! +//! [`ClassifyCliffordCenters`] constructs `Z(Cl(q))` and `Z(Cl^0(q))` as +//! discriminant quadratic etale algebras, including the characteristic-two +//! Artin--Schreier presentation, and checks their class against the existing +//! Brauer--Wall coordinates. use crate::clifford::{CliffordAlgebra, Metric}; use crate::forms::{ arf_fpn_char2, arf_invariant, arf_ordinal_finite, bw_class_complex, bw_class_finite_odd, - bw_class_function_field, bw_class_nimber, bw_class_rational, bw_class_real, - classify_finite_odd, classify_rational, classify_surcomplex, classify_surreal, finite_odd_witt, - isometric_finite_odd, isometric_fpn_char2, isometric_nimber, isometric_ordinal_finite, - isometric_rational, isometric_real, isometric_surcomplex, - ordinal_metric_finite_subfield_degree, witt_decompose_finite_odd, witt_decompose_real, - ArfInvariants, BrauerWallClass, CliffordInvariants, FiniteOddField, - FunctionFieldBrauerWallClass, OddCharInvariants, OddWittDecomp, RationalBrauerWallClass, - RationalCliffordInvariants, RealWittDecomp, WittClassG, + bw_class_function_field, bw_class_nimber, bw_class_rational, bw_class_real, centers_complex, + centers_fp, centers_fpn, centers_function_field, centers_nimber, centers_ordinal, + centers_rational, centers_real, classify_finite_odd, classify_rational, classify_surcomplex, + classify_surreal, finite_milnor_e1, finite_milnor_e2, finite_odd_witt, + function_field_milnor_e1, function_field_milnor_e2, isometric_finite_odd, isometric_fpn_char2, + isometric_nimber, isometric_ordinal_finite, isometric_rational, isometric_real, + isometric_surcomplex, ordinal_metric_finite_subfield_degree, rational_milnor_e1, + rational_milnor_e2, strict_milnor_e0, witt_decompose_finite_odd, witt_decompose_real, + ArfInvariants, ArtinSchreierCenterDiscriminant, BrauerWallClass, CliffordCenterError, + CliffordCenterInvariants, CliffordInvariants, ComplexCenterDiscriminant, + FiniteFieldCenterDiscriminant, FiniteOddField, FunctionFieldBrauerWallClass, + FunctionFieldMilnorK1Class, MilnorInvariantError, MilnorK0Class, Mod2MilnorField, + OddCharInvariants, OddWittDecomp, RationalBrauerWallClass, RationalCliffordInvariants, + RationalMilnorK1Class, RealCenterDiscriminant, RealWittDecomp, WittClassG, }; use crate::scalar::{ Fp, Fpn, Nimber, Ordinal, Rational, RationalFunction, Scalar, Surcomplex, Surreal, @@ -354,6 +370,39 @@ pub trait ClassifyBrauerWall: Scalar { fn bw_class(metric: &Metric) -> Result; } +/// Strict degree-at-most-two mod-two Milnor invariants of a nonsingular form. +/// The associated `K1Class` and `K2Class` come from [`Mod2MilnorField`], so each +/// coefficient field keeps its exact square-class and Brauer carrier. +pub trait ClassifyMilnor: Mod2MilnorField { + /// `e_0(q) = dim(q) mod 2`. + fn milnor_e0(metric: &Metric) -> Result { + strict_milnor_e0(metric) + } + + /// The strict `e_1 : I/I^2 -> K^M_1/2` map. + fn milnor_e1(metric: &Metric) -> Result; + + /// The strict `e_2 : I^2/I^3 -> K^M_2/2` map. + fn milnor_e2(metric: &Metric) -> Result; +} + +/// Full and even Clifford centers on a scalar-dispatched exact field domain. +pub trait ClassifyCliffordCenters: Scalar { + /// Square or Artin--Schreier class controlling the quadratic center. + type CenterDiscriminant; + /// The field-specific Brauer--Wall carrier used for the coherence check. + type CenterBrauerWallClass; + + /// Construct `Z(Cl(q))` and `Z(Cl^0(q))` and compare their discriminant + /// class with the existing Brauer--Wall coordinates. + fn clifford_centers( + metric: &Metric, + ) -> Result< + CliffordCenterInvariants, + CliffordCenterError, + >; +} + impl ClassifyForm for Surreal { type Class = CliffordInvariants; fn classify(metric: &Metric) -> Result { @@ -633,6 +682,158 @@ impl ClassifyBrauerWall for Ordinal { } } +impl ClassifyMilnor for Rational { + fn milnor_e1(metric: &Metric) -> Result { + rational_milnor_e1(metric) + } + + fn milnor_e2(metric: &Metric) -> Result { + rational_milnor_e2(metric) + } +} + +impl ClassifyMilnor for Fp

{ + fn milnor_e1(metric: &Metric) -> Result { + finite_milnor_e1(metric) + } + + fn milnor_e2(metric: &Metric) -> Result { + finite_milnor_e2(metric) + } +} + +impl ClassifyMilnor for Fpn { + fn milnor_e1(metric: &Metric) -> Result { + finite_milnor_e1(metric) + } + + fn milnor_e2(metric: &Metric) -> Result { + finite_milnor_e2(metric) + } +} + +impl ClassifyMilnor for RationalFunction { + fn milnor_e1(metric: &Metric) -> Result { + function_field_milnor_e1(metric) + } + + fn milnor_e2(metric: &Metric) -> Result { + function_field_milnor_e2(metric) + } +} + +impl ClassifyCliffordCenters for Surreal { + type CenterDiscriminant = RealCenterDiscriminant; + type CenterBrauerWallClass = BrauerWallClass; + + fn clifford_centers( + metric: &Metric, + ) -> Result< + CliffordCenterInvariants, + CliffordCenterError, + > { + centers_real(metric) + } +} + +impl ClassifyCliffordCenters for Surcomplex { + type CenterDiscriminant = ComplexCenterDiscriminant; + type CenterBrauerWallClass = BrauerWallClass; + + fn clifford_centers( + metric: &Metric, + ) -> Result< + CliffordCenterInvariants, + CliffordCenterError, + > { + centers_complex(metric) + } +} + +impl ClassifyCliffordCenters for Rational { + type CenterDiscriminant = RationalMilnorK1Class; + type CenterBrauerWallClass = RationalBrauerWallClass; + + fn clifford_centers( + metric: &Metric, + ) -> Result< + CliffordCenterInvariants, + CliffordCenterError, + > { + centers_rational(metric) + } +} + +impl ClassifyCliffordCenters for Fp

{ + type CenterDiscriminant = FiniteFieldCenterDiscriminant; + type CenterBrauerWallClass = BrauerWallClass; + + fn clifford_centers( + metric: &Metric, + ) -> Result< + CliffordCenterInvariants, + CliffordCenterError, + > { + centers_fp(metric) + } +} + +impl ClassifyCliffordCenters for Fpn { + type CenterDiscriminant = FiniteFieldCenterDiscriminant; + type CenterBrauerWallClass = BrauerWallClass; + + fn clifford_centers( + metric: &Metric, + ) -> Result< + CliffordCenterInvariants, + CliffordCenterError, + > { + centers_fpn(metric) + } +} + +impl ClassifyCliffordCenters for RationalFunction { + type CenterDiscriminant = FunctionFieldMilnorK1Class; + type CenterBrauerWallClass = FunctionFieldBrauerWallClass; + + fn clifford_centers( + metric: &Metric, + ) -> Result< + CliffordCenterInvariants, + CliffordCenterError, + > { + centers_function_field(metric) + } +} + +impl ClassifyCliffordCenters for Nimber { + type CenterDiscriminant = ArtinSchreierCenterDiscriminant; + type CenterBrauerWallClass = BrauerWallClass; + + fn clifford_centers( + metric: &Metric, + ) -> Result< + CliffordCenterInvariants, + CliffordCenterError, + > { + centers_nimber(metric) + } +} + +impl ClassifyCliffordCenters for Ordinal { + type CenterDiscriminant = ArtinSchreierCenterDiscriminant; + type CenterBrauerWallClass = BrauerWallClass; + + fn clifford_centers( + metric: &Metric, + ) -> Result< + CliffordCenterInvariants, + CliffordCenterError, + > { + centers_ordinal(metric) + } +} + fn ordinal_char2_field_degree(metric: &Metric) -> Option { ordinal_metric_finite_subfield_degree(metric) } @@ -679,6 +880,36 @@ impl Metric { } } +impl Metric { + /// The strict degree-zero mod-two Milnor invariant. + pub fn milnor_e0(&self) -> Result { + S::milnor_e0(self) + } + + /// The strict `e_1 : I/I^2 -> K^M_1/2` invariant. + pub fn milnor_e1(&self) -> Result { + S::milnor_e1(self) + } + + /// The strict `e_2 : I^2/I^3 -> K^M_2/2` invariant. + pub fn milnor_e2(&self) -> Result { + S::milnor_e2(self) + } +} + +impl Metric { + /// Construct the full and even Clifford centers and compare them with the + /// Brauer--Wall coordinates. + pub fn clifford_centers( + &self, + ) -> Result< + CliffordCenterInvariants, + CliffordCenterError, + > { + S::clifford_centers(self) + } +} + impl CliffordAlgebra { /// Classify the algebra's underlying form (see [`ClassifyForm`]). pub fn classify(&self) -> Result { @@ -714,6 +945,36 @@ impl CliffordAlgebra { } } +impl CliffordAlgebra { + /// The strict degree-zero mod-two Milnor invariant of the underlying form. + pub fn milnor_e0(&self) -> Result { + S::milnor_e0(&self.metric) + } + + /// The strict degree-one mod-two Milnor invariant of the underlying form. + pub fn milnor_e1(&self) -> Result { + S::milnor_e1(&self.metric) + } + + /// The strict degree-two mod-two Milnor invariant of the underlying form. + pub fn milnor_e2(&self) -> Result { + S::milnor_e2(&self.metric) + } +} + +impl CliffordAlgebra { + /// Construct the full and even centers of this Clifford algebra and compare + /// them with its Brauer--Wall coordinates. + pub fn centers( + &self, + ) -> Result< + CliffordCenterInvariants, + CliffordCenterError, + > { + S::clifford_centers(&self.metric) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/forms/field_invariants.rs b/src/forms/field_invariants.rs index ac15131d..28d32a18 100644 --- a/src/forms/field_invariants.rs +++ b/src/forms/field_invariants.rs @@ -1,17 +1,146 @@ -//! Numeric field invariants: level, Pythagoras number, and u-invariant. +//! Numeric finite-field invariants: level, Pythagoras number, and u-invariant. //! -//! The functions compute these invariants over finite prime fields by exact -//! finite searches. +//! [`finite_field_numeric_invariants`] reports the three invariants uniformly +//! for supported prime fields and extension fields. The production path uses +//! the finite-field theorems, so its cost is independent of the field order; +//! the small exhaustive searches below remain independent test oracles and +//! support [`is_sum_of_n_squares`]'s prime-field membership query. //! -//! Reference values to check against: every finite field has level `1` or `2` -//! and Pythagoras number `≤ 2`; `u(F_q) = 2` (odd `q`). For comparison, +//! Every finite field has level `1` or `2`, Pythagoras number at most `2`, and +//! quadratic u-invariant `2`. In characteristic two, the u-invariant uses +//! regular quadratic forms (not a diagonal symmetric-bilinear proxy). For comparison, //! formally-real ℝ has level `∞` (no finite `n`), `u(ℝ) = ∞`, Pythagoras number //! `1`; and `u(Q_p) = 4`. //! -//! Public entry points return `None` when `P` is not prime. +//! Public entry points return `None` for unsupported field parameters. -use crate::scalar::Fp; +use crate::scalar::{ExactFieldScalar, Fp, Fpn}; use std::collections::BTreeSet; +use std::fmt; + +/// The scalar metadata needed by the numeric finite-field invariants. +/// +/// This deliberately lives in the forms pillar rather than widening +/// [`crate::scalar::FiniteField`]: prime fields and extension fields both need +/// the report, while the scalar analysis trait has a different Galois-oriented +/// contract. +pub trait FiniteFieldInvariantField: ExactFieldScalar + Copy { + /// Characteristic prime. + fn characteristic_prime() -> u128; + + /// Absolute degree over the prime field. + fn absolute_degree() -> usize; + + /// Field order, or `None` when the parameters are unsupported. + fn field_order_checked() -> Option; +} + +impl FiniteFieldInvariantField for Fp

{ + fn characteristic_prime() -> u128 { + P + } + + fn absolute_degree() -> usize { + 1 + } + + fn field_order_checked() -> Option { + Fp::

::modulus_is_prime().then_some(P) + } +} + +impl FiniteFieldInvariantField for Fpn { + fn characteristic_prime() -> u128 { + P + } + + fn absolute_degree() -> usize { + N + } + + fn field_order_checked() -> Option { + Fpn::::field_order_checked() + } +} + +/// Numeric invariants of a supported finite field. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct FiniteFieldNumericInvariants { + /// Characteristic prime `p`. + pub characteristic: u128, + /// Absolute degree `n` in `F_{p^n}`. + pub absolute_degree: usize, + /// Field order `q = p^n`. + pub field_order: u128, + /// Level `s(F_q)`, the least number of squares summing to `-1`. + pub level: usize, + /// Pythagoras number `p(F_q)`. + pub pythagoras_number: usize, + /// Quadratic u-invariant. In characteristic two this means regular + /// quadratic forms. + pub u_invariant: usize, +} + +impl FiniteFieldNumericInvariants { + /// Returns the canonical display representation. + pub fn display(&self) -> String { + self.to_string() + } +} + +impl fmt::Display for FiniteFieldNumericInvariants { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "FiniteFieldNumericInvariants(characteristic={}, absolute_degree={}, field_order={}, level={}, pythagoras_number={}, u_invariant={})", + self.characteristic, + self.absolute_degree, + self.field_order, + self.level, + self.pythagoras_number, + self.u_invariant, + ) + } +} + +/// Level, Pythagoras number, and quadratic u-invariant of a supported finite +/// field. +/// +/// For odd `q`, the level is `1` exactly when `q = 1 (mod 4)` and otherwise +/// `2`; the Pythagoras number and u-invariant are both `2`. For even `q`, every +/// element is a square, so the first two invariants are `1`, while the regular +/// quadratic u-invariant is `2`. +pub fn finite_field_numeric_invariants( +) -> Option { + let characteristic = F::characteristic_prime(); + let absolute_degree = F::absolute_degree(); + let field_order = F::field_order_checked()?; + let even = characteristic == 2; + Some(FiniteFieldNumericInvariants { + characteristic, + absolute_degree, + field_order, + level: if even || field_order % 4 == 1 { 1 } else { 2 }, + pythagoras_number: if even { 1 } else { 2 }, + u_invariant: 2, + }) +} + +/// The level of a supported finite field. +pub fn level_finite() -> Option { + finite_field_numeric_invariants::().map(|report| report.level) +} + +/// The Pythagoras number of a supported finite field. +pub fn pythagoras_number_finite() -> Option { + finite_field_numeric_invariants::().map(|report| report.pythagoras_number) +} + +/// The quadratic u-invariant of a supported finite field. In characteristic +/// two this means regular quadratic forms. +pub fn u_invariant_finite() -> Option { + finite_field_numeric_invariants::().map(|report| report.u_invariant) +} /// The squares of `F_P` (as residues in `[0, P)`). fn squares_mod() -> Vec { @@ -47,34 +176,19 @@ pub fn is_sum_of_n_squares(x: Fp

, n: usize) -> bool { /// or `None` if `P` is not prime. A finite field has level `1` (iff `−1` is a /// square: char 2, or `p ≡ 1 mod 4`) or `2`. (ℝ has level `∞` — no finite `n`.) pub fn level() -> Option { - if !Fp::

::modulus_is_prime() { - return None; - } - // −1 in F_P. Level ≤ 2 for any finite field; the search to 4 is a margin. - let minus_one = (P - 1) % P; - (1..=4).find(|&n| sums_of_n_squares::

(n).contains(&minus_one)) + level_finite::>() } /// The **Pythagoras number** `p(F_P)`: the least `n` such that every sum of /// squares is already a sum of `n` squares (the sum-of-squares set stabilizes). /// `None` if `P` is not prime. `≤ 2` for finite fields. pub fn pythagoras_number() -> Option { - if !Fp::

::modulus_is_prime() { - return None; - } - let mut prev = sums_of_n_squares::

(1); - for n in 1..=(P as usize + 1) { - let next = sums_of_n_squares::

(n + 1); - if next == prev { - return Some(n); - } - prev = next; - } - Some(P as usize) + pythagoras_number_finite::>() } /// Whether some `code`-indexed nonzero vector isotropes the diagonal form `qs` /// over `F_P` (brute force over `F_P^dim`). +#[cfg(test)] fn is_anisotropic(qs: &[u128]) -> bool { let dim = qs.len(); let mut total = 1u128; @@ -99,6 +213,7 @@ fn is_anisotropic(qs: &[u128]) -> bool { /// Does some diagonal form of dimension `dim` with entries in `F_P*` stay /// anisotropic? +#[cfg(test)] fn exists_anisotropic_form(dim: usize) -> bool { let mut total = 1u128; for _ in 0..dim { @@ -118,23 +233,12 @@ fn exists_anisotropic_form(dim: usize) -> bool { false } -/// The **u-invariant** `u(F_P)`: the largest dimension of an anisotropic form -/// (computed by exhausting diagonal forms up to dim 4). `Some(2)` for every odd -/// prime field (`None` for `P = 2` — char-2 forms are not diagonal — or non-prime -/// `P`). For comparison `u(ℝ) = ∞`, `u(Q_p) = 4`. +/// The **u-invariant** `u(F_P)`: the largest dimension of an anisotropic form. +/// `Some(2)` for every prime field, using regular quadratic forms in +/// characteristic two. Returns `None` for non-prime `P`. For comparison +/// `u(ℝ) = ∞`, `u(Q_p) = 4`. pub fn u_invariant() -> Option { - if P == 2 || !Fp::

::modulus_is_prime() { - return None; - } - let mut u = 0; - for dim in 1..=4 { - if exists_anisotropic_form::

(dim) { - u = dim; - } else { - break; // finite-field anisotropic dimensions are an initial segment - } - } - Some(u) + u_invariant_finite::>() } #[cfg(test)] @@ -165,7 +269,7 @@ mod tests { assert_eq!(u_invariant::<3>(), Some(2)); assert_eq!(u_invariant::<5>(), Some(2)); assert_eq!(u_invariant::<7>(), Some(2)); - assert_eq!(u_invariant::<2>(), None); // char 2 out of scope + assert_eq!(u_invariant::<2>(), Some(2)); } #[test] @@ -174,4 +278,100 @@ mod tests { assert!(!is_sum_of_n_squares::<3>(Fp::<3>::from_u128(2), 1)); // 2 is not a square mod 3 assert!(is_sum_of_n_squares::<5>(Fp::<5>::from_u128(4), 1)); // 4 = 2² } + + #[test] + fn extension_field_reports_use_field_order_not_characteristic_alone() { + let f4 = finite_field_numeric_invariants::>().unwrap(); + assert_eq!((f4.level, f4.pythagoras_number, f4.u_invariant), (1, 1, 2)); + assert_eq!((f4.absolute_degree, f4.field_order), (2, 4)); + + let f9 = finite_field_numeric_invariants::>().unwrap(); + assert_eq!((f9.level, f9.pythagoras_number, f9.u_invariant), (1, 2, 2)); + let f27 = finite_field_numeric_invariants::>().unwrap(); + assert_eq!( + (f27.level, f27.pythagoras_number, f27.u_invariant), + (2, 2, 2) + ); + } + + #[test] + fn degree_one_extension_and_prime_reports_agree() { + assert_eq!( + finite_field_numeric_invariants::>(), + finite_field_numeric_invariants::>() + ); + } + + #[test] + fn formula_reports_agree_with_small_prime_field_searches() { + for (p, expected_level, expected_pythagoras) in [(2, 1, 1), (3, 2, 2), (5, 1, 2), (7, 2, 2)] + { + let searched_level = (1..=4) + .find(|&n| match p { + 2 => sums_of_n_squares::<2>(n).contains(&1), + 3 => sums_of_n_squares::<3>(n).contains(&2), + 5 => sums_of_n_squares::<5>(n).contains(&4), + 7 => sums_of_n_squares::<7>(n).contains(&6), + _ => unreachable!(), + }) + .unwrap(); + assert_eq!(searched_level, expected_level); + assert_eq!(level_runtime_for_test(p), Some(expected_level)); + assert_eq!(pythagoras_runtime_for_test(p), Some(expected_pythagoras)); + } + assert!(exists_anisotropic_form::<3>(2)); + assert!(!exists_anisotropic_form::<3>(3)); + } + + fn level_runtime_for_test(p: u128) -> Option { + match p { + 2 => level::<2>(), + 3 => level::<3>(), + 5 => level::<5>(), + 7 => level::<7>(), + _ => None, + } + } + + fn pythagoras_runtime_for_test(p: u128) -> Option { + match p { + 2 => searched_pythagoras::<2>(), + 3 => searched_pythagoras::<3>(), + 5 => searched_pythagoras::<5>(), + 7 => searched_pythagoras::<7>(), + _ => None, + } + } + + fn searched_pythagoras() -> Option { + if !Fp::

::modulus_is_prime() { + return None; + } + let mut previous = sums_of_n_squares::

(1); + for n in 1..=(P as usize + 1) { + let next = sums_of_n_squares::

(n + 1); + if next == previous { + return Some(n); + } + previous = next; + } + None + } + + #[test] + fn unsupported_field_parameters_return_none() { + assert_eq!(finite_field_numeric_invariants::>(), None); + assert_eq!(finite_field_numeric_invariants::>(), None); + assert_eq!(finite_field_numeric_invariants::>(), None); + } + + #[test] + fn numeric_report_has_canonical_display() { + let report = finite_field_numeric_invariants::>().unwrap(); + assert_eq!( + report.to_string(), + "FiniteFieldNumericInvariants(characteristic=3, absolute_degree=2, field_order=9, level=1, pythagoras_number=2, u_invariant=2)" + ); + assert_eq!(report.display(), report.to_string()); + } } diff --git a/src/forms/hermitian.rs b/src/forms/hermitian.rs index 4ac397a0..ead99352 100644 --- a/src/forms/hermitian.rs +++ b/src/forms/hermitian.rs @@ -19,9 +19,18 @@ //! onto the fixed field is surjective; degenerate forms split off their radical. //! The finite classifier therefore records exactly `(rank, radical_dim)`, plus //! finite-field metadata identifying the quadratic extension. - -use crate::scalar::{FiniteField, Scalar, Surcomplex}; +//! +//! [`HermitianForm::restrict_scalars`] and +//! [`FiniteHermitianForm::restrict_scalars`] expose the associated ordinary +//! quadratic form `q(v) = h(v,v)` over the involution-fixed scalar world. Its +//! polar form is `Tr(h(v,w))`. Keeping those two formulas distinct is essential +//! in characteristic two, where `Tr(h(v,v)) = 0` but the norm quadratic form is +//! generally nonsingular. + +use crate::clifford::{Metric, MAX_BASIS_DIM}; +use crate::scalar::{FiniteField, Ordered, QuadraticInvolution, Scalar, Surcomplex}; use std::cmp::Ordering; +use std::collections::BTreeMap; /// A Hermitian form, carried by its conjugate-symmetric Gram matrix over /// `Surcomplex`. @@ -43,6 +52,104 @@ pub struct HermitianSignature { pub radical: usize, } +/// Failure to restrict a Hermitian form to an ordinary quadratic metric over +/// the involution-fixed scalar world. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum HermitianRestrictionError { + /// Restriction doubles the represented dimension and would exceed the + /// Clifford engine's fixed-width generator boundary. + DimensionTooLarge { + /// Dimension over the Hermitian coefficient world. + hermitian_dim: usize, + /// Largest Hermitian dimension whose restriction fits the engine. + max_hermitian_dim: usize, + }, + /// A coefficient mathematically fixed by the involution could not be + /// represented in the selected fixed-field backend. + FixedFieldProjection, +} + +impl std::fmt::Display for HermitianRestrictionError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + HermitianRestrictionError::DimensionTooLarge { + hermitian_dim, + max_hermitian_dim, + } => write!( + f, + "Hermitian dimension {hermitian_dim} restricts past the supported maximum {max_hermitian_dim}" + ), + HermitianRestrictionError::FixedFieldProjection => { + f.write_str("Hermitian restriction coefficient left the represented fixed field") + } + } + } +} + +impl std::error::Error for HermitianRestrictionError {} + +/// Restrict the Hermitian Gram matrix `H` over `E/F` to the ordinary quadratic +/// map `q(v) = h(v,v)` on the underlying `F`-space. +/// +/// In the coordinate basis `beta_a e_i`, the quadratic and polar entries are +/// +/// ```text +/// q_(i,a) = project(conj(beta_a) H_ii beta_a) +/// b_((i,a),(j,c)) = Tr(conj(beta_a) H_ij beta_c). +/// ``` +/// +/// The second line is formed as `z + involute(z)`. The first deliberately is +/// **not** `Tr(h(v,v))`: that would be `2 h(v,v)` and would erase every +/// quadratic value in characteristic two. +fn restrict_hermitian_gram( + gram: &[Vec], +) -> Result, HermitianRestrictionError> +where + E: QuadraticInvolution, +{ + let n = gram.len(); + let Some(dim) = n.checked_mul(2) else { + return Err(HermitianRestrictionError::DimensionTooLarge { + hermitian_dim: n, + max_hermitian_dim: MAX_BASIS_DIM / 2, + }); + }; + if dim > MAX_BASIS_DIM { + return Err(HermitianRestrictionError::DimensionTooLarge { + hermitian_dim: n, + max_hermitian_dim: MAX_BASIS_DIM / 2, + }); + } + + let basis = E::fixed_basis(); + let mut q = Vec::with_capacity(dim); + for (i, row) in gram.iter().enumerate() { + for beta in &basis { + let value = beta.involute().mul(&row[i]).mul(beta); + q.push( + E::project_fixed(&value).ok_or(HermitianRestrictionError::FixedFieldProjection)?, + ); + } + } + + let mut b = BTreeMap::new(); + for left in 0..dim { + let (i, a) = (left / 2, left % 2); + for right in (left + 1)..dim { + let (j, c) = (right / 2, right % 2); + let value = basis[a].involute().mul(&gram[i][j]).mul(&basis[c]); + let polar = value.add(&value.involute()); + let fixed = + E::project_fixed(&polar).ok_or(HermitianRestrictionError::FixedFieldProjection)?; + if !fixed.is_zero() { + b.insert((left, right), fixed); + } + } + } + + Ok(Metric::new(q, b)) +} + impl HermitianSignature { /// Return the canonical display representation. pub fn display(&self) -> String { @@ -231,6 +338,21 @@ impl FiniteHermitianForm { extension_field_order: checked_pow_u128(F::characteristic(), extension_degree), } } + + /// Restrict this `F_{p^{2k}}/F_{p^k}`-Hermitian form to the ordinary + /// quadratic form `q(v) = h(v,v)` over its represented fixed field. + /// + /// The method is available when the coefficient backend supplies an exact + /// [`QuadraticInvolution`] bridge. It currently covers every finite field + /// exposed by Python: `F_4/F_2`, `F_16/F_4`, `F_9/F_3`, and `F_25/F_5`. + pub fn restrict_scalars( + &self, + ) -> Result::Fixed>, HermitianRestrictionError> + where + F: QuadraticInvolution, + { + restrict_hermitian_gram(&self.gram) + } } /// Congruence by the elementary unit `E = I + λ·E_{source,target}`: `H ↦ E* H E`, @@ -365,6 +487,16 @@ impl HermitianForm { &self.gram } + /// Restrict this Hermitian form over `Surcomplex/S` to its ordinary + /// quadratic form over `S`. A Hermitian dimension `n` becomes quadratic + /// dimension `2n` in the coordinate order `(e_0, i e_0, e_1, i e_1, ...)`. + pub fn restrict_scalars(&self) -> Result, HermitianRestrictionError> + where + S: Ordered, + { + restrict_hermitian_gram(&self.gram) + } + /// The orthogonal direct sum (block-diagonal Gram). pub fn direct_sum(&self, other: &HermitianForm) -> HermitianForm { let (n, m) = (self.dim(), other.dim()); @@ -427,7 +559,7 @@ impl HermitianForm { #[cfg(test)] mod tests { use super::*; - use crate::scalar::{Fpn, Nimber, Rational, Surreal}; + use crate::scalar::{Fpn, Nimber, QuadraticInvolution, Rational, Surreal}; type GC = Surcomplex; @@ -438,6 +570,71 @@ mod tests { x.sign() } + fn evaluate_metric(metric: &Metric, coordinates: &[S]) -> S { + assert_eq!(metric.dim(), coordinates.len()); + let mut value = S::zero(); + for (i, x) in coordinates.iter().enumerate() { + value = value.add(&metric.q()[i].mul(&x.mul(x))); + } + for (&(i, j), bij) in metric.b() { + value = value.add(&bij.mul(&coordinates[i].mul(&coordinates[j]))); + } + value + } + + #[test] + fn surcomplex_restriction_keeps_quadratic_and_trace_polar_data() { + let h = HermitianForm::from_gram(vec![vec![gc(2, 0), gc(0, 1)], vec![gc(0, -1), gc(2, 0)]]) + .unwrap(); + let restricted = h.restrict_scalars().unwrap(); + + assert_eq!(restricted.q(), vec![rsign_value(2); 4]); + assert_eq!(restricted.b().len(), 2); + assert_eq!(restricted.b().get(&(0, 3)), Some(&rsign_value(-2))); + assert_eq!(restricted.b().get(&(1, 2)), Some(&rsign_value(2))); + assert!(restricted.a().is_empty()); + + let diagonal = HermitianForm::::diagonal(h.diagonalize()) + .restrict_scalars() + .unwrap(); + assert_eq!( + crate::forms::classify_rational(&restricted), + crate::forms::classify_rational(&diagonal), + "unitary diagonalization must preserve the restricted quadratic class" + ); + } + + fn rsign_value(n: i128) -> Rational { + Rational::from_int(n) + } + + #[test] + fn surcomplex_restriction_doubles_signature_and_radical() { + let h = HermitianForm::::diagonal(vec![ + Surreal::from_int(1), + Surreal::from_int(-1), + Surreal::zero(), + ]); + let restricted = h.restrict_scalars().unwrap(); + assert_eq!(restricted.dim(), 6); + assert_eq!( + crate::forms::surreal_signature(&restricted), + Some((2, 2, 2)) + ); + } + + #[test] + fn restriction_refuses_a_doubled_dimension_past_the_engine_boundary() { + let h = HermitianForm::::diagonal(vec![Rational::one(); 65]); + assert_eq!( + h.restrict_scalars(), + Err(HermitianRestrictionError::DimensionTooLarge { + hermitian_dim: 65, + max_hermitian_dim: 64, + }) + ); + } + #[test] fn finite_hermitian_forms_over_f9_are_rank_classified() { type F9 = Fpn<3, 2>; @@ -473,6 +670,59 @@ mod tests { FiniteHermitianForm::::diagonal(vec![x]).is_none(), "diagonal entries must be fixed by conjugation" ); + + let norm_plane = FiniteHermitianForm::::diagonal(vec![one]) + .unwrap() + .restrict_scalars() + .unwrap(); + let ordinary = crate::forms::classify_finite_odd(&norm_plane).unwrap(); + assert_eq!(ordinary.dim, 2); + assert_eq!(ordinary.radical_dim, 0); + assert!(ordinary.disc_is_square); + } + + #[test] + fn finite_char2_restriction_keeps_the_norm_quadratic_form() { + type F4 = Fpn<2, 2>; + type F16 = Fpn<2, 4>; + + let over_f2 = FiniteHermitianForm::::diagonal(vec![F4::one()]) + .unwrap() + .restrict_scalars() + .unwrap(); + let arf_f2 = crate::forms::arf_char2(&over_f2).unwrap(); + assert_eq!((arf_f2.arf, arf_f2.rank, arf_f2.radical_dim), (1, 2, 0)); + assert!(!arf_f2.radical_anisotropic); + + let fixed_coefficient = F4::generator(); + let coefficient = ::embed_fixed(&fixed_coefficient); + let h = FiniteHermitianForm::::diagonal(vec![coefficient]).unwrap(); + let over_f4 = h.restrict_scalars().unwrap(); + let arf_f4 = crate::forms::arf_char2(&over_f4).unwrap(); + assert_eq!((arf_f4.arf, arf_f4.rank, arf_f4.radical_dim), (1, 2, 0)); + assert!(!arf_f4.radical_anisotropic); + + let basis = ::fixed_basis(); + for left in 0..4u128 { + for right in 0..4u128 { + let coords = [ + F4::from_coeffs(&[left & 1, (left >> 1) & 1]), + F4::from_coeffs(&[right & 1, (right >> 1) & 1]), + ]; + let vector = ::embed_fixed(&coords[0]) + .mul(&basis[0]) + .add(&::embed_fixed(&coords[1]).mul(&basis[1])); + let hermitian_value = vector.involute().mul(&coefficient).mul(&vector); + let projected = ::project_fixed(&hermitian_value) + .expect("h(v,v) lies in F_4"); + assert_eq!(evaluate_metric(&over_f4, &coords), projected); + } + } + + let doubled = h.direct_sum(&h).restrict_scalars().unwrap(); + let doubled_arf = crate::forms::arf_char2(&doubled).unwrap(); + assert_eq!((doubled_arf.arf, doubled_arf.rank), (0, 4)); + assert_eq!(doubled, over_f4.direct_sum(&over_f4)); } #[test] diff --git a/src/forms/mod.rs b/src/forms/mod.rs index 5df2f4ad..84147e31 100644 --- a/src/forms/mod.rs +++ b/src/forms/mod.rs @@ -9,11 +9,12 @@ //! * [`char2`] provides Arf, Brown, Dickson, and extraspecial-group invariants. //! //! [`classify`] dispatches to these implementations from the scalar type. -//! [`witt`] contains Witt and Brauer--Wall classes, [`springer`] contains valued- -//! field residue decompositions, [`local_global`] contains reciprocity and -//! isotropy criteria, and [`integral`] contains lattices and finite quadratic -//! modules. [`symplectic`] and [`hermitian`] cover alternating and Hermitian forms; -//! [`trace_form`] constructs trace and transfer forms from field extensions. +//! [`witt`] contains Clifford centers and Witt/Brauer--Wall classes, [`springer`] +//! contains valued-field residue decompositions, [`local_global`] contains +//! reciprocity and isotropy criteria, and [`integral`] contains lattices and +//! finite quadratic modules. [`symplectic`] and [`hermitian`] cover alternating +//! and Hermitian forms; [`trace_form`] constructs trace and transfer forms from +//! field extensions. //! //! Public items from each child module are re-exported here. diff --git a/src/forms/witt/centers.rs b/src/forms/witt/centers.rs new file mode 100644 index 00000000..9a0b0d68 --- /dev/null +++ b/src/forms/witt/centers.rs @@ -0,0 +1,1256 @@ +//! Centers of Clifford algebras and their even subalgebras. +//! +//! For a nonsingular quadratic space of positive dimension, one of +//! `Z(Cl(q))` and `Z(Cl^0(q))` is the ground field and the other is the +//! discriminant quadratic etale algebra. In characteristic not two its +//! generator is the volume element `omega` with +//! +//! ```text +//! omega^2 = (-1)^(n(n-1)/2) det(q). +//! ``` +//! +//! The quadratic center is the full center when `n` is odd and the even center +//! when `n` is even. In characteristic two a nonsingular quadratic space has +//! even dimension. For a symplectic basis `(a_i,b_i)`, the element +//! `z = sum_i a_i b_i` is central in `Cl^0(q)` and satisfies +//! +//! ```text +//! z^2 + z = sum_i q(a_i) q(b_i). +//! ``` +//! +//! The Kummer or Artin--Schreier class of that relation is compared here with +//! the independently computed discriminant/Arf coordinate of the existing +//! Brauer--Wall classifier. These are the standard center descriptions from +//! the Clifford-algebra chapters of Knus--Merkurjev--Rost--Tignol, *The Book of +//! Involutions*; this module's contribution is their exact realization and the +//! cross-surface check on Ogdoad's represented scalar domains. + +use crate::clifford::{CliffordAlgebra, Metric, Multivector}; +use crate::forms::{ + arf_f2, arf_fpn_char2, arf_ordinal_finite, as_diagonal, bw_class_complex, bw_class_finite_odd, + bw_class_function_field, bw_class_nimber, bw_class_rational, bw_class_real, + ordinal_metric_finite_subfield_degree, BrauerWallClass, FiniteFieldMilnorK1Class, + FiniteOddField, FunctionFieldBrauerWallClass, FunctionFieldMilnorK1Class, Mod2MilnorField, + RationalBrauerWallClass, RationalMilnorK1Class, +}; +use crate::scalar::{ + nim_degree, nim_trace, FieldExtension, Fp, Fpn, Nimber, Ordinal, Rational, RationalFunction, + Scalar, Surcomplex, Surreal, +}; +use std::cmp::Ordering; +use std::collections::BTreeMap; + +/// The defining relation of a quadratic etale center. +#[derive(Debug, Clone, PartialEq)] +pub enum QuadraticEtaleRelation { + /// `F[omega]/(omega^2 - d)` in characteristic not two. + Kummer { + /// The represented signed discriminant `d = omega^2`. + generator_square: S, + }, + /// `F[z]/(z^2 + z + c)` in characteristic two. + ArtinSchreier { + /// The represented Artin--Schreier discriminant `c = z^2 + z`. + representative: S, + }, +} + +impl QuadraticEtaleRelation { + /// Return the canonical display representation. + pub fn display(&self) -> String { + self.to_string() + } +} + +impl std::fmt::Display for QuadraticEtaleRelation { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + QuadraticEtaleRelation::Kummer { generator_square } => { + write!(f, "omega^2={generator_square}") + } + QuadraticEtaleRelation::ArtinSchreier { representative } => { + write!(f, "z^2+z={representative}") + } + } + } +} + +/// A full or even Clifford center, materialized by its central generator when +/// it is quadratic over the ground field. +#[derive(Debug, Clone, PartialEq)] +pub enum DiscriminantEtaleAlgebra { + /// The center is exactly the coefficient field. + GroundField, + /// The discriminant quadratic etale algebra. + QuadraticEtale { + /// A concrete central generator in the represented Clifford algebra. + generator: Multivector, + /// Its Kummer or Artin--Schreier defining relation. + relation: QuadraticEtaleRelation, + /// The corresponding square or Artin--Schreier class. + discriminant_class: D, + /// Whether the quadratic etale algebra is split as `F x F`. + split: bool, + }, +} + +impl DiscriminantEtaleAlgebra { + /// Degree of the center over the coefficient field. + pub fn degree(&self) -> usize { + match self { + DiscriminantEtaleAlgebra::GroundField => 1, + DiscriminantEtaleAlgebra::QuadraticEtale { .. } => 2, + } + } + + /// Whether this is the coefficient field rather than a quadratic center. + pub fn is_ground_field(&self) -> bool { + matches!(self, DiscriminantEtaleAlgebra::GroundField) + } + + /// The concrete quadratic generator, when the center has degree two. + pub fn generator(&self) -> Option<&Multivector> { + match self { + DiscriminantEtaleAlgebra::GroundField => None, + DiscriminantEtaleAlgebra::QuadraticEtale { generator, .. } => Some(generator), + } + } + + /// The defining relation, when the center has degree two. + pub fn relation(&self) -> Option<&QuadraticEtaleRelation> { + match self { + DiscriminantEtaleAlgebra::GroundField => None, + DiscriminantEtaleAlgebra::QuadraticEtale { relation, .. } => Some(relation), + } + } + + /// The discriminant class, when the center has degree two. + pub fn discriminant_class(&self) -> Option<&D> { + match self { + DiscriminantEtaleAlgebra::GroundField => None, + DiscriminantEtaleAlgebra::QuadraticEtale { + discriminant_class, .. + } => Some(discriminant_class), + } + } + + /// `Some(true)` for a split quadratic center, `Some(false)` for a quadratic + /// field center, and `None` for the degree-one ground-field center. + pub fn quadratic_is_split(&self) -> Option { + match self { + DiscriminantEtaleAlgebra::GroundField => None, + DiscriminantEtaleAlgebra::QuadraticEtale { split, .. } => Some(*split), + } + } +} + +impl std::fmt::Display for DiscriminantEtaleAlgebra { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + DiscriminantEtaleAlgebra::GroundField => f.write_str("GroundField"), + DiscriminantEtaleAlgebra::QuadraticEtale { + relation, + discriminant_class, + split, + .. + } => match relation { + QuadraticEtaleRelation::Kummer { generator_square } => write!( + f, + "QuadraticEtale(omega^2={generator_square}, class={discriminant_class}, split={split})" + ), + QuadraticEtaleRelation::ArtinSchreier { representative } => write!( + f, + "QuadraticEtale(z^2+z={representative}, class={discriminant_class}, split={split})" + ), + }, + } + } +} + +impl DiscriminantEtaleAlgebra { + /// Return the canonical display representation. + pub fn display(&self) -> String { + self.to_string() + } +} + +/// Full-center/even-center data joined to the existing Brauer--Wall class. +#[derive(Debug, Clone, PartialEq)] +pub struct CliffordCenterInvariants { + dimension: usize, + center: DiscriminantEtaleAlgebra, + even_center: DiscriminantEtaleAlgebra, + brauer_wall_class: B, + brauer_wall_agrees: bool, +} + +impl CliffordCenterInvariants { + /// Dimension of the nonsingular quadratic space. + pub fn dimension(&self) -> usize { + self.dimension + } + + /// `Z(Cl(q))`. + pub fn center(&self) -> &DiscriminantEtaleAlgebra { + &self.center + } + + /// `Z(Cl^0(q))`. + pub fn even_center(&self) -> &DiscriminantEtaleAlgebra { + &self.even_center + } + + /// The independently computed Brauer--Wall class used for the comparison. + pub fn brauer_wall_class(&self) -> &B { + &self.brauer_wall_class + } + + /// Whether parity and discriminant/Arf class agree with the corresponding + /// Brauer--Wall coordinates. + pub fn brauer_wall_agrees(&self) -> bool { + self.brauer_wall_agrees + } + + /// The unique quadratic center, except in dimension zero where both centers + /// are the ground field. + pub fn quadratic_center(&self) -> Option<&DiscriminantEtaleAlgebra> { + if self.center.degree() == 2 { + Some(&self.center) + } else if self.even_center.degree() == 2 { + Some(&self.even_center) + } else { + None + } + } +} + +impl std::fmt::Display + for CliffordCenterInvariants +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "CliffordCenters(dim={}, center={}, even_center={}, brauer_wall={}, agrees={})", + self.dimension, + self.center, + self.even_center, + self.brauer_wall_class, + self.brauer_wall_agrees + ) + } +} + +impl CliffordCenterInvariants { + /// Return the canonical display representation. + pub fn display(&self) -> String { + self.to_string() + } +} + +/// Why a Clifford center could not be constructed on the represented domain. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[non_exhaustive] +pub enum CliffordCenterError { + /// Characteristic-not-two diagonalization failed. + DiagonalizerFailure, + /// The form is singular; degenerate Clifford centers have a larger + /// nilpotent structure and are not discriminant etale algebras. + SingularForm { + /// Dimension of the polar radical. + radical_dim: usize, + }, + /// The metric has a nonzero general-bilinear contraction in characteristic + /// two, outside the quadratic Clifford surface. + GeneralBilinearMetric, + /// A constructed volume/Artin--Schreier generator failed its scalar + /// quadratic relation. + GeneratorRelationFailure, + /// Field parameters, exact roots, finite windows, or bounded arithmetic are + /// outside the implemented classifier domain. + UnsupportedFieldOrArithmetic, +} + +impl std::fmt::Display for CliffordCenterError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + CliffordCenterError::DiagonalizerFailure => { + f.write_str("metric could not be diagonalized for its Clifford center") + } + CliffordCenterError::SingularForm { radical_dim } => write!( + f, + "Clifford discriminant centers require a nonsingular form (radical_dim={radical_dim})" + ), + CliffordCenterError::GeneralBilinearMetric => f.write_str( + "characteristic-two Clifford centers require a pure quadratic (q,b) metric", + ), + CliffordCenterError::GeneratorRelationFailure => { + f.write_str("constructed center generator failed its quadratic relation") + } + CliffordCenterError::UnsupportedFieldOrArithmetic => { + f.write_str("field or exact arithmetic is outside the Clifford-center surface") + } + } + } +} + +impl std::error::Error for CliffordCenterError {} + +/// Real square class of the signed discriminant. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct RealCenterDiscriminant { + negative: bool, +} + +impl RealCenterDiscriminant { + /// Whether the signed discriminant is negative. + pub fn is_negative(self) -> bool { + self.negative + } + + /// The real quadratic etale algebra is split exactly for positive signed + /// discriminant. + pub fn is_split(self) -> bool { + !self.negative + } + + /// Return the canonical display representation. + pub fn display(&self) -> String { + self.to_string() + } +} + +impl std::fmt::Display for RealCenterDiscriminant { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(if self.negative { + "negative" + } else { + "positive" + }) + } +} + +/// The unique square class over the represented algebraically closed complex +/// domain. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ComplexCenterDiscriminant; + +impl ComplexCenterDiscriminant { + /// Every nonzero complex square class is trivial on this classifier domain. + pub fn is_split(self) -> bool { + true + } + + /// Return the canonical display representation. + pub fn display(&self) -> String { + self.to_string() + } +} + +impl std::fmt::Display for ComplexCenterDiscriminant { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("trivial") + } +} + +/// An Artin--Schreier discriminant class over a represented finite +/// characteristic-two field. +#[derive(Debug, Clone)] +pub struct ArtinSchreierCenterDiscriminant { + representative: S, + field_degree: u128, + class: u128, +} + +impl ArtinSchreierCenterDiscriminant { + fn new(representative: S, field_degree: u128, class: u128) -> Option { + (field_degree > 0 && class <= 1).then_some(ArtinSchreierCenterDiscriminant { + representative, + field_degree, + class, + }) + } + + /// A coefficient `c` in the equation `z^2 + z = c`. + pub fn representative(&self) -> &S { + &self.representative + } + + /// Degree `m` of the represented field over `F_2`. + pub fn field_degree(&self) -> u128 { + self.field_degree + } + + /// The quotient class in `F/AS(F)`, represented by its absolute trace bit. + pub fn class(&self) -> u128 { + self.class + } + + /// The quadratic etale algebra is split exactly when `c` is in the image of + /// `x -> x^2 + x`. + pub fn is_split(&self) -> bool { + self.class == 0 + } + + /// Return the canonical display representation. + pub fn display(&self) -> String { + self.to_string() + } +} + +impl PartialEq for ArtinSchreierCenterDiscriminant { + fn eq(&self, other: &Self) -> bool { + self.field_degree == other.field_degree && self.class == other.class + } +} + +impl Eq for ArtinSchreierCenterDiscriminant {} + +impl std::fmt::Display for ArtinSchreierCenterDiscriminant { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "AS(F_2^{}, representative={}, class={})", + self.field_degree, self.representative, self.class + ) + } +} + +/// Discriminant class for a const-generic finite field, whose characteristic is +/// selected only after monomorphization. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum FiniteFieldCenterDiscriminant { + /// Odd-characteristic Kummer square class. + Kummer(FiniteFieldMilnorK1Class), + /// Characteristic-two Artin--Schreier class. + ArtinSchreier(ArtinSchreierCenterDiscriminant), +} + +/// Clifford-center report over an odd rational function field. +pub type FunctionFieldCliffordCenterInvariants = CliffordCenterInvariants< + RationalFunction, + FunctionFieldMilnorK1Class, + FunctionFieldBrauerWallClass, +>; + +impl FiniteFieldCenterDiscriminant { + /// Whether the associated quadratic etale algebra is split. + pub fn is_split(&self) -> bool { + match self { + FiniteFieldCenterDiscriminant::Kummer(class) => class.is_trivial(), + FiniteFieldCenterDiscriminant::ArtinSchreier(class) => class.is_split(), + } + } + + /// Return the canonical display representation. + pub fn display(&self) -> String { + self.to_string() + } +} + +impl std::fmt::Display for FiniteFieldCenterDiscriminant { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + FiniteFieldCenterDiscriminant::Kummer(class) => write!(f, "{class}"), + FiniteFieldCenterDiscriminant::ArtinSchreier(class) => write!(f, "{class}"), + } + } +} + +fn place_quadratic_center( + dim: usize, + generator: Multivector, + relation: QuadraticEtaleRelation, + discriminant_class: D, + split: bool, + brauer_wall_class: B, + brauer_wall_agrees: bool, +) -> CliffordCenterInvariants { + if dim == 0 { + return CliffordCenterInvariants { + dimension: 0, + center: DiscriminantEtaleAlgebra::GroundField, + even_center: DiscriminantEtaleAlgebra::GroundField, + brauer_wall_class, + brauer_wall_agrees, + }; + } + let quadratic = DiscriminantEtaleAlgebra::QuadraticEtale { + generator, + relation, + discriminant_class, + split, + }; + let (center, even_center) = if dim.is_multiple_of(2) { + (DiscriminantEtaleAlgebra::GroundField, quadratic) + } else { + (quadratic, DiscriminantEtaleAlgebra::GroundField) + }; + CliffordCenterInvariants { + dimension: dim, + center, + even_center, + brauer_wall_class, + brauer_wall_agrees, + } +} + +fn kummer_volume( + metric: &Metric, +) -> Result<(Multivector, S), CliffordCenterError> { + let diagonal = as_diagonal(metric).ok_or(CliffordCenterError::DiagonalizerFailure)?; + let radical_dim = diagonal.q().iter().filter(|x| x.is_zero()).count(); + if radical_dim != 0 { + return Err(CliffordCenterError::SingularForm { radical_dim }); + } + + let algebra = CliffordAlgebra::new(metric.dim(), metric.clone()); + let volume = if metric.b().is_empty() && metric.a().is_empty() { + algebra.pseudoscalar() + } else { + let two = S::one().add(&S::one()); + let half = two + .inv() + .ok_or(CliffordCenterError::UnsupportedFieldOrArithmetic)?; + let symmetric_contraction: BTreeMap<(usize, usize), S> = metric + .b() + .iter() + .map(|(&indices, value)| (indices, value.mul(&half))) + .collect(); + let exterior_gauge = CliffordAlgebra::new( + metric.dim(), + Metric::general( + metric.q().to_vec(), + metric.b().clone(), + symmetric_contraction, + ), + ); + exterior_gauge + .transport_gauge_to(&algebra, &exterior_gauge.pseudoscalar()) + .ok_or(CliffordCenterError::GeneratorRelationFailure)? + }; + let square_mv = algebra.mul(&volume, &volume); + let square = algebra.scalar_part(&square_mv); + if square_mv != algebra.scalar(square.clone()) || square.is_zero() { + return Err(CliffordCenterError::GeneratorRelationFailure); + } + Ok((volume, square)) +} + +fn vector_from_coordinates( + algebra: &CliffordAlgebra, + coordinates: &[S], +) -> Multivector { + coordinates + .iter() + .enumerate() + .fold(algebra.zero(), |acc, (i, coefficient)| { + algebra.add(&acc, &algebra.scalar_mul(coefficient, &algebra.e(i))) + }) +} + +fn quadratic_value_char2(vector: &[S], q: &[S], polar: &[Vec]) -> S { + let mut value = S::zero(); + for i in 0..vector.len() { + value = value.add(&vector[i].mul(&vector[i]).mul(&q[i])); + for j in (i + 1)..vector.len() { + value = value.add(&vector[i].mul(&vector[j]).mul(&polar[i][j])); + } + } + value +} + +fn polar_value(left: &[S], right: &[S], polar: &[Vec]) -> S { + let mut value = S::zero(); + for i in 0..left.len() { + for j in (i + 1)..left.len() { + let cross = left[i].mul(&right[j]).add(&left[j].mul(&right[i])); + value = value.add(&cross.mul(&polar[i][j])); + } + } + value +} + +fn vector_scale(coefficient: &S, vector: &[S]) -> Vec { + vector.iter().map(|x| coefficient.mul(x)).collect() +} + +fn vector_add(left: &[S], right: &[S]) -> Vec { + left.iter().zip(right).map(|(x, y)| x.add(y)).collect() +} + +struct Char2CenterReduction { + generator: Multivector, + representative: S, + arf: u128, + radical_dim: usize, +} + +fn char2_center_reduction( + metric: &Metric, + trace_to_f2: impl Fn(&S) -> Option, +) -> Result, CliffordCenterError> { + if !metric.a().is_empty() { + return Err(CliffordCenterError::GeneralBilinearMetric); + } + let n = metric.dim(); + let mut polar = vec![vec![S::zero(); n]; n]; + for (&(i, j), value) in metric.b() { + polar[i][j] = value.clone(); + polar[j][i] = value.clone(); + } + let mut vectors: Vec> = (0..n) + .map(|i| { + let mut vector = vec![S::zero(); n]; + vector[i] = S::one(); + vector + }) + .collect(); + let mut pairs = Vec::new(); + let mut representative = S::zero(); + let mut radical_dim = 0usize; + + while let Some(a) = vectors.pop() { + if let Some(position) = vectors + .iter() + .position(|candidate| !polar_value(&a, candidate, &polar).is_zero()) + { + let b_raw = vectors.swap_remove(position); + let pairing = polar_value(&a, &b_raw, &polar); + let pairing_inv = pairing + .inv() + .ok_or(CliffordCenterError::UnsupportedFieldOrArithmetic)?; + let b = vector_scale(&pairing_inv, &b_raw); + for vector in &mut vectors { + let wb = polar_value(vector, &b, &polar); + let wa = polar_value(vector, &a, &polar); + let mut orthogonalized = vector.clone(); + if !wb.is_zero() { + orthogonalized = vector_add(&orthogonalized, &vector_scale(&wb, &a)); + } + if !wa.is_zero() { + orthogonalized = vector_add(&orthogonalized, &vector_scale(&wa, &b)); + } + *vector = orthogonalized; + } + let qa = quadratic_value_char2(&a, metric.q(), &polar); + let qb = quadratic_value_char2(&b, metric.q(), &polar); + representative = representative.add(&qa.mul(&qb)); + pairs.push((a, b)); + } else { + radical_dim += 1; + } + } + + if radical_dim != 0 { + return Err(CliffordCenterError::SingularForm { radical_dim }); + } + let arf = trace_to_f2(&representative) + .filter(|class| *class <= 1) + .ok_or(CliffordCenterError::UnsupportedFieldOrArithmetic)?; + let algebra = CliffordAlgebra::new(n, metric.clone()); + let generator = pairs.iter().fold(algebra.zero(), |acc, (a, b)| { + let a = vector_from_coordinates(&algebra, a); + let b = vector_from_coordinates(&algebra, b); + algebra.add(&acc, &algebra.mul(&a, &b)) + }); + let relation_value = algebra.add(&algebra.mul(&generator, &generator), &generator); + if relation_value != algebra.scalar(representative.clone()) { + return Err(CliffordCenterError::GeneratorRelationFailure); + } + Ok(Char2CenterReduction { + generator, + representative, + arf, + radical_dim, + }) +} + +fn char2_centers( + metric: &Metric, + reduction: Char2CenterReduction, + field_degree: u128, + brauer_wall_class: B, + brauer_wall_agrees: bool, +) -> Result, B>, CliffordCenterError> +{ + debug_assert_eq!(reduction.radical_dim, 0); + let discriminant = ArtinSchreierCenterDiscriminant::new( + reduction.representative.clone(), + field_degree, + reduction.arf, + ) + .ok_or(CliffordCenterError::UnsupportedFieldOrArithmetic)?; + Ok(place_quadratic_center( + metric.dim(), + reduction.generator, + QuadraticEtaleRelation::ArtinSchreier { + representative: reduction.representative, + }, + discriminant.clone(), + discriminant.is_split(), + brauer_wall_class, + brauer_wall_agrees, + )) +} + +pub(crate) fn centers_real( + metric: &Metric, +) -> Result< + CliffordCenterInvariants, + CliffordCenterError, +> { + let brauer_wall = + bw_class_real(metric).ok_or(CliffordCenterError::UnsupportedFieldOrArithmetic)?; + let (generator, square) = kummer_volume(metric)?; + let discriminant = RealCenterDiscriminant { + negative: square.sign() == Ordering::Less, + }; + let agrees = match brauer_wall { + BrauerWallClass::Real(index) => { + index % 2 == (metric.dim() % 2) as u128 + && matches!(index, 1 | 2 | 5 | 6) == discriminant.negative + } + _ => false, + }; + Ok(place_quadratic_center( + metric.dim(), + generator, + QuadraticEtaleRelation::Kummer { + generator_square: square, + }, + discriminant, + discriminant.is_split(), + brauer_wall, + agrees, + )) +} + +pub(crate) fn centers_complex( + metric: &Metric>, +) -> Result< + CliffordCenterInvariants, ComplexCenterDiscriminant, BrauerWallClass>, + CliffordCenterError, +> { + let brauer_wall = + bw_class_complex(metric).ok_or(CliffordCenterError::UnsupportedFieldOrArithmetic)?; + let (generator, square) = kummer_volume(metric)?; + let agrees = matches!( + brauer_wall, + BrauerWallClass::Complex(parity) if parity == (metric.dim() % 2) as u128 + ); + Ok(place_quadratic_center( + metric.dim(), + generator, + QuadraticEtaleRelation::Kummer { + generator_square: square, + }, + ComplexCenterDiscriminant, + true, + brauer_wall, + agrees, + )) +} + +pub(crate) fn centers_rational( + metric: &Metric, +) -> Result< + CliffordCenterInvariants, + CliffordCenterError, +> { + let brauer_wall = + bw_class_rational(metric).ok_or(CliffordCenterError::UnsupportedFieldOrArithmetic)?; + let (generator, square) = kummer_volume(metric)?; + let discriminant = RationalMilnorK1Class::from_rational(&square) + .ok_or(CliffordCenterError::UnsupportedFieldOrArithmetic)?; + let agrees = brauer_wall.dimension_parity() == (metric.dim() % 2) as u128 + && brauer_wall.signed_discriminant() == discriminant.representative(); + Ok(place_quadratic_center( + metric.dim(), + generator, + QuadraticEtaleRelation::Kummer { + generator_square: square, + }, + discriminant, + discriminant.is_trivial(), + brauer_wall, + agrees, + )) +} + +fn centers_finite_odd( + metric: &Metric, +) -> Result< + CliffordCenterInvariants, BrauerWallClass>, + CliffordCenterError, +> +where + F: FiniteOddField + Mod2MilnorField, +{ + F::ensure_supported().ok_or(CliffordCenterError::UnsupportedFieldOrArithmetic)?; + let brauer_wall = + bw_class_finite_odd(metric).ok_or(CliffordCenterError::UnsupportedFieldOrArithmetic)?; + let (generator, square) = kummer_volume(metric)?; + let class = crate::forms::milnor_symbol_1(&square) + .ok_or(CliffordCenterError::UnsupportedFieldOrArithmetic)?; + let agrees = matches!( + brauer_wall, + BrauerWallClass::OddChar { e0, sclass, .. } + if e0 == (metric.dim() % 2) as u128 && sclass == class.nonsquare() + ); + let discriminant = FiniteFieldCenterDiscriminant::Kummer(class); + Ok(place_quadratic_center( + metric.dim(), + generator, + QuadraticEtaleRelation::Kummer { + generator_square: square, + }, + discriminant.clone(), + discriminant.is_split(), + brauer_wall, + agrees, + )) +} + +pub(crate) fn centers_fp( + metric: &Metric>, +) -> Result< + CliffordCenterInvariants, FiniteFieldCenterDiscriminant>, BrauerWallClass>, + CliffordCenterError, +> { + if P != 2 { + return centers_finite_odd(metric); + } + if !Fp::

::modulus_is_prime() { + return Err(CliffordCenterError::UnsupportedFieldOrArithmetic); + } + let reduction = char2_center_reduction(metric, |x| Some(x.value() & 1))?; + let qd = metric + .q() + .iter() + .map(|x| x.value() == 1) + .collect::>(); + let mut polar = vec![0u128; metric.dim()]; + for (&(i, j), value) in metric.b() { + if value.value() == 1 { + polar[i] |= 1u128 << j; + polar[j] |= 1u128 << i; + } + } + let existing = arf_f2(metric.dim(), &qd, &polar); + let arf = existing.arf; + let brauer_wall = BrauerWallClass::Char2 { + field_degree: 1, + arf, + }; + let discriminant = ArtinSchreierCenterDiscriminant::new(reduction.representative, 1, arf) + .ok_or(CliffordCenterError::UnsupportedFieldOrArithmetic)?; + let discriminant = FiniteFieldCenterDiscriminant::ArtinSchreier(discriminant); + Ok(place_quadratic_center( + metric.dim(), + reduction.generator, + QuadraticEtaleRelation::ArtinSchreier { + representative: reduction.representative, + }, + discriminant.clone(), + discriminant.is_split(), + brauer_wall, + existing.radical_dim == 0 && existing.arf == reduction.arf, + )) +} + +pub(crate) fn centers_fpn( + metric: &Metric>, +) -> Result< + CliffordCenterInvariants, FiniteFieldCenterDiscriminant>, BrauerWallClass>, + CliffordCenterError, +> { + if P != 2 { + return centers_finite_odd(metric); + } + if !Fpn::::is_supported_field() { + return Err(CliffordCenterError::UnsupportedFieldOrArithmetic); + } + let reduction = char2_center_reduction(metric, |x| Some(x.trace().value()))?; + let existing = + arf_fpn_char2(metric).ok_or(CliffordCenterError::UnsupportedFieldOrArithmetic)?; + let field_degree = N as u128; + let brauer_wall = BrauerWallClass::Char2 { + field_degree, + arf: existing.arf, + }; + let agrees = existing.radical_dim == 0 && existing.arf == reduction.arf; + let discriminant = + ArtinSchreierCenterDiscriminant::new(reduction.representative, field_degree, reduction.arf) + .ok_or(CliffordCenterError::UnsupportedFieldOrArithmetic)?; + let discriminant = FiniteFieldCenterDiscriminant::ArtinSchreier(discriminant); + Ok(place_quadratic_center( + metric.dim(), + reduction.generator, + QuadraticEtaleRelation::ArtinSchreier { + representative: reduction.representative, + }, + discriminant.clone(), + discriminant.is_split(), + brauer_wall, + agrees, + )) +} + +pub(crate) fn centers_function_field( + metric: &Metric>, +) -> Result, CliffordCenterError> { + let brauer_wall = + bw_class_function_field(metric).ok_or(CliffordCenterError::UnsupportedFieldOrArithmetic)?; + let (generator, square) = kummer_volume(metric)?; + let discriminant = crate::forms::milnor_symbol_1(&square) + .ok_or(CliffordCenterError::UnsupportedFieldOrArithmetic)?; + let bw_discriminant = crate::forms::milnor_symbol_1(brauer_wall.signed_discriminant()) + .ok_or(CliffordCenterError::UnsupportedFieldOrArithmetic)?; + let agrees = brauer_wall.dimension_parity() == (metric.dim() % 2) as u128 + && discriminant == bw_discriminant; + let split = discriminant.is_trivial(); + Ok(place_quadratic_center( + metric.dim(), + generator, + QuadraticEtaleRelation::Kummer { + generator_square: square, + }, + discriminant, + split, + brauer_wall, + agrees, + )) +} + +fn nimber_metric_field_degree(metric: &Metric) -> u128 { + metric + .q() + .iter() + .map(|x| nim_degree(x.0)) + .chain(metric.b().values().map(|x| nim_degree(x.0))) + .max() + .unwrap_or(1) +} + +pub(crate) fn centers_nimber( + metric: &Metric, +) -> Result< + CliffordCenterInvariants, BrauerWallClass>, + CliffordCenterError, +> { + let field_degree = nimber_metric_field_degree(metric); + let reduction = char2_center_reduction(metric, |x| Some(nim_trace(x.0, field_degree)))?; + let brauer_wall = + bw_class_nimber(metric).ok_or(CliffordCenterError::UnsupportedFieldOrArithmetic)?; + let agrees = matches!( + brauer_wall, + BrauerWallClass::Char2 { + field_degree: degree, + arf, + } if degree == field_degree && arf == reduction.arf + ); + char2_centers(metric, reduction, field_degree, brauer_wall, agrees) +} + +fn ordinal_trace_to_f2(x: &Ordinal, degree: u128) -> Option { + let mut trace = Ordinal::zero(); + let mut conjugate = x.clone(); + for i in 0..degree { + trace = trace.add(&conjugate); + if i + 1 != degree { + conjugate = conjugate.nim_mul(&conjugate)?; + } + } + match trace.as_finite()? { + 0 => Some(0), + 1 => Some(1), + _ => None, + } +} + +pub(crate) fn centers_ordinal( + metric: &Metric, +) -> Result< + CliffordCenterInvariants, BrauerWallClass>, + CliffordCenterError, +> { + let field_degree = ordinal_metric_finite_subfield_degree(metric) + .ok_or(CliffordCenterError::UnsupportedFieldOrArithmetic)?; + let reduction = char2_center_reduction(metric, |x| ordinal_trace_to_f2(x, field_degree))?; + let existing = + arf_ordinal_finite(metric).ok_or(CliffordCenterError::UnsupportedFieldOrArithmetic)?; + let brauer_wall = BrauerWallClass::Char2 { + field_degree, + arf: existing.arf, + }; + let agrees = existing.radical_dim == 0 && existing.arf == reduction.arf; + char2_centers(metric, reduction, field_degree, brauer_wall, agrees) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::scalar::{Fp, Fpn}; + use std::collections::BTreeMap; + + fn q(n: i128) -> Rational { + Rational::from_int(n) + } + + fn assert_kummer_relation( + metric: &Metric, + centers: &CliffordCenterInvariants, + ) { + let algebra = CliffordAlgebra::new(metric.dim(), metric.clone()); + let quadratic = centers.quadratic_center().expect("positive dimension"); + let generator = quadratic.generator().unwrap(); + let QuadraticEtaleRelation::Kummer { generator_square } = quadratic.relation().unwrap() + else { + panic!("expected Kummer relation") + }; + assert_eq!( + algebra.mul(generator, generator), + algebra.scalar(generator_square.clone()) + ); + } + + fn assert_artin_schreier_relation( + metric: &Metric, + centers: &CliffordCenterInvariants, + ) { + let algebra = CliffordAlgebra::new(metric.dim(), metric.clone()); + let quadratic = centers.quadratic_center().expect("positive dimension"); + let generator = quadratic.generator().unwrap(); + let QuadraticEtaleRelation::ArtinSchreier { representative } = + quadratic.relation().unwrap() + else { + panic!("expected Artin-Schreier relation") + }; + assert_eq!( + algebra.add(&algebra.mul(generator, generator), generator), + algebra.scalar(representative.clone()) + ); + } + + fn assert_full_central(metric: &Metric, generator: &Multivector) { + let algebra = CliffordAlgebra::new(metric.dim(), metric.clone()); + for i in 0..metric.dim() { + assert!(algebra.commutator(generator, &algebra.e(i)).is_zero()); + } + } + + fn assert_even_central(metric: &Metric, generator: &Multivector) { + let algebra = CliffordAlgebra::new(metric.dim(), metric.clone()); + for i in 0..metric.dim() { + for j in (i + 1)..metric.dim() { + let even = algebra.mul(&algebra.e(i), &algebra.e(j)); + assert!(algebra.commutator(generator, &even).is_zero()); + } + } + } + + #[test] + fn rational_centers_switch_with_parity_and_materialize_volume_relation() { + let odd = Metric::diagonal(vec![q(2)]); + let odd_centers = centers_rational(&odd).unwrap(); + assert_eq!(odd_centers.center().degree(), 2); + assert!(odd_centers.even_center().is_ground_field()); + assert_eq!(odd_centers.center().quadratic_is_split(), Some(false)); + assert_eq!( + odd_centers.center().display(), + "QuadraticEtale(omega^2=2, class=MilnorK1Mod2(Q; 2), split=false)" + ); + assert!(odd_centers.brauer_wall_agrees()); + assert_kummer_relation(&odd, &odd_centers); + assert_full_central(&odd, odd_centers.center().generator().unwrap()); + assert_eq!(odd.clifford_centers(), Ok(odd_centers.clone())); + let odd_algebra = CliffordAlgebra::new(odd.dim(), odd.clone()); + assert_eq!(odd_algebra.centers(), Ok(odd_centers)); + + let even = Metric::diagonal(vec![q(1), q(1)]); + let even_centers = centers_rational(&even).unwrap(); + assert!(even_centers.center().is_ground_field()); + assert_eq!(even_centers.even_center().degree(), 2); + assert_eq!( + even_centers + .even_center() + .discriminant_class() + .unwrap() + .representative(), + -1 + ); + assert!(even_centers.brauer_wall_agrees()); + assert_kummer_relation(&even, &even_centers); + assert_even_central(&even, even_centers.even_center().generator().unwrap()); + + let split = Metric::diagonal(vec![q(1), q(-1)]); + assert_eq!( + centers_rational(&split) + .unwrap() + .even_center() + .quadratic_is_split(), + Some(true) + ); + } + + #[test] + fn nondiagonal_and_general_gauge_centers_use_actual_central_generators() { + let mut polar = BTreeMap::new(); + polar.insert((0, 1), q(2)); + let hyperbolic = Metric::new(vec![q(0), q(0)], polar); + let centers = centers_rational(&hyperbolic).unwrap(); + assert_eq!(centers.even_center().quadratic_is_split(), Some(true)); + assert_kummer_relation(&hyperbolic, ¢ers); + assert_even_central(&hyperbolic, centers.even_center().generator().unwrap()); + + let nondiagonal_odd = hyperbolic.direct_sum(&Metric::diagonal(vec![q(1)])); + let centers = centers_rational(&nondiagonal_odd).unwrap(); + assert_kummer_relation(&nondiagonal_odd, ¢ers); + assert_full_central(&nondiagonal_odd, centers.center().generator().unwrap()); + + let mut upper = BTreeMap::new(); + upper.insert((0, 1), q(1)); + let general = Metric::general(vec![q(1), q(1)], BTreeMap::new(), upper); + let centers = centers_rational(&general).unwrap(); + assert_kummer_relation(&general, ¢ers); + assert_even_central(&general, centers.even_center().generator().unwrap()); + } + + #[test] + fn finite_odd_and_function_field_centers_match_brauer_wall_coordinates() { + let finite = Metric::diagonal(vec![Fp::<5>::from_int(1), Fp::<5>::from_int(2)]); + let finite_centers = centers_fp(&finite).unwrap(); + assert!(finite_centers.brauer_wall_agrees()); + assert_eq!( + finite_centers.even_center().quadratic_is_split(), + Some(false) + ); + assert_kummer_relation(&finite, &finite_centers); + + type F5t = RationalFunction>; + let t = F5t::new(vec![Fp::<5>::zero(), Fp::<5>::one()], vec![Fp::<5>::one()]); + let function = Metric::diagonal(vec![t.clone()]); + let function_centers = centers_function_field(&function).unwrap(); + assert!(function_centers.brauer_wall_agrees()); + assert_eq!(function_centers.center().quadratic_is_split(), Some(false)); + assert_kummer_relation(&function, &function_centers); + } + + #[test] + fn real_and_complex_center_classes_match_the_classification_tables() { + for positive in 0..=4 { + for negative in 0..=4 { + let coefficients = std::iter::repeat_n(Surreal::one(), positive) + .chain(std::iter::repeat_n(Surreal::one().neg(), negative)) + .collect(); + let real = Metric::diagonal(coefficients); + let real_centers = centers_real(&real).unwrap(); + assert!(real_centers.brauer_wall_agrees()); + if positive + negative > 0 { + assert_kummer_relation(&real, &real_centers); + } + } + } + + let real = Metric::diagonal(vec![Surreal::one(), Surreal::one()]); + assert_eq!( + centers_real(&real) + .unwrap() + .even_center() + .quadratic_is_split(), + Some(false) + ); + + let complex = Metric::diagonal(vec![ + Surcomplex::::one(), + Surcomplex::::one(), + ]); + let complex_centers = centers_complex(&complex).unwrap(); + assert!(complex_centers.brauer_wall_agrees()); + assert_eq!( + complex_centers.even_center().quadratic_is_split(), + Some(true) + ); + assert_kummer_relation(&complex, &complex_centers); + } + + fn f2_plane(q0: i128, q1: i128) -> Metric> { + let mut polar = BTreeMap::new(); + polar.insert((0, 1), Fpn::<2, 1>::one()); + Metric::new( + vec![Fpn::<2, 1>::from_int(q0), Fpn::<2, 1>::from_int(q1)], + polar, + ) + } + + #[test] + fn characteristic_two_even_center_is_the_artin_schreier_discriminant_algebra() { + let split = f2_plane(0, 0); + let split_centers = centers_fpn(&split).unwrap(); + assert!(split_centers.center().is_ground_field()); + assert_eq!(split_centers.even_center().quadratic_is_split(), Some(true)); + assert!(split_centers.brauer_wall_agrees()); + assert_artin_schreier_relation(&split, &split_centers); + assert_even_central(&split, split_centers.even_center().generator().unwrap()); + + let nonsplit = f2_plane(1, 1); + let nonsplit_centers = centers_fpn(&nonsplit).unwrap(); + assert_eq!( + nonsplit_centers.even_center().quadratic_is_split(), + Some(false) + ); + assert!(nonsplit_centers.brauer_wall_agrees()); + assert_artin_schreier_relation(&nonsplit, &nonsplit_centers); + assert_even_central( + &nonsplit, + nonsplit_centers.even_center().generator().unwrap(), + ); + + let rank_four = split.direct_sum(&nonsplit); + let rank_four_centers = centers_fpn(&rank_four).unwrap(); + assert_eq!( + rank_four_centers.even_center().quadratic_is_split(), + Some(false) + ); + assert!(rank_four_centers.brauer_wall_agrees()); + assert_artin_schreier_relation(&rank_four, &rank_four_centers); + assert_even_central( + &rank_four, + rank_four_centers.even_center().generator().unwrap(), + ); + } + + #[test] + fn nimber_and_ordinal_centers_reuse_their_finite_field_windows() { + let mut nim_polar = BTreeMap::new(); + nim_polar.insert((0, 1), Nimber(1)); + let nim = Metric::new(vec![Nimber(1), Nimber(1)], nim_polar); + let nim_centers = centers_nimber(&nim).unwrap(); + assert!(nim_centers.brauer_wall_agrees()); + assert_eq!(nim_centers.even_center().quadratic_is_split(), Some(false)); + assert_artin_schreier_relation(&nim, &nim_centers); + + let mut ord_polar = BTreeMap::new(); + ord_polar.insert((0, 1), Ordinal::from_int(1)); + let ord = Metric::new(vec![Ordinal::from_int(1), Ordinal::from_int(1)], ord_polar); + let ord_centers = centers_ordinal(&ord).unwrap(); + assert!(ord_centers.brauer_wall_agrees()); + assert_eq!(ord_centers.even_center().quadratic_is_split(), Some(false)); + assert_artin_schreier_relation(&ord, &ord_centers); + } + + #[test] + fn dimension_zero_and_singular_forms_keep_their_exact_boundaries() { + let empty = Metric::::diagonal(Vec::new()); + let centers = centers_rational(&empty).unwrap(); + assert!(centers.center().is_ground_field()); + assert!(centers.even_center().is_ground_field()); + assert!(centers.quadratic_center().is_none()); + + let singular = Metric::diagonal(vec![q(1), q(0)]); + assert_eq!( + centers_rational(&singular), + Err(CliffordCenterError::SingularForm { radical_dim: 1 }) + ); + } +} diff --git a/src/forms/witt/class.rs b/src/forms/witt/class.rs index 758a14e4..3318a991 100644 --- a/src/forms/witt/class.rs +++ b/src/forms/witt/class.rs @@ -124,7 +124,7 @@ impl WittClass { } /// Deprecated alias for [`zero_f2`](Self::zero_f2). - #[deprecated(since = "0.0.0", note = "use zero_f2() for clarity")] + #[deprecated(since = "1.0.0", note = "use zero_f2() for clarity")] pub fn zero() -> Self { Self::zero_f2() } diff --git a/src/forms/witt/milnor.rs b/src/forms/witt/milnor.rs index f2269d26..0849020d 100644 --- a/src/forms/witt/milnor.rs +++ b/src/forms/witt/milnor.rs @@ -1,4 +1,27 @@ -//! Milnor residue maps assembled as global Witt invariants. +//! Mod-two Milnor symbols and Milnor residue maps assembled as global Witt +//! invariants. +//! +//! The symbol layer exposes the part of Milnor K-theory already computed by the +//! crate's form and Brauer surfaces: +//! +//! ```text +//! K^M_0(F)/2 = Z/2, +//! K^M_1(F)/2 = F*/F*^2, +//! K^M_2(F)/2 = H^2(F, Z/2) = Br(F)[2] (char(F) != 2). +//! ``` +//! +//! [`Mod2MilnorField`] constructs the pure symbols `{a}` and `{a,b}`. The +//! degree-one carriers are square classes; the degree-two carriers reuse the +//! exact quaternion/Brauer classes already maintained by the rational and +//! odd-characteristic function-field layers. [`ClassifyMilnor`] exposes the +//! strict graded maps `e_0`, `e_1`, and `e_2` on metrics: unlike a reporting +//! tuple, `e_n` returns [`MilnorInvariantError::OutsideFundamentalIdeal`] unless +//! the form actually lies in `I^n`. +//! +//! This module deliberately stops at degree two and characteristic not two. +//! Characteristic-two quadratic Witt theory uses the Kato/Artin--Schreier +//! filtration rather than this Milnor filtration, and wild local norm-residue +//! symbols remain outside the implemented surface. //! //! The Springer layer computes per-place residue buckets; this module assembles //! the Witt-group-level maps supplied by Milnor's exact sequence @@ -45,13 +68,633 @@ //! are outside this Witt-residue map. use crate::forms::{ - legendre, relevant_primes, try_chi_kappa, try_kappa_order, try_relevant_places_ff, - try_residue_unit_at, try_valuation_at_ff, unit_part, val_p, FiniteOddField, FunctionFieldPlace, - WittClassG, + as_diagonal, bw_class_function_field, bw_class_rational, finite_odd_witt, is_global_square_ff, + legendre, relevant_primes, try_chi_kappa, try_hilbert_symbol_qp, try_kappa_order, + try_relevant_places_ff, try_residue_unit_at, try_square_free, try_tame_symbol_exponent_ff, + try_valuation_at_ff, unit_part, val_p, Brauer2Class, FiniteOddField, FunctionFieldBrauer2Class, + FunctionFieldPlace, WittClassG, +}; +use crate::scalar::{ + is_prime_u128, ExactFieldScalar, Fp, Fpn, Poly, Rational, RationalFunction, Scalar, }; -use crate::scalar::{Poly, RationalFunction, Scalar}; use std::collections::BTreeMap; +// --------------------------------------------------------------------------- +// Degree <= 2 mod-two Milnor classes and pure symbols. +// --------------------------------------------------------------------------- + +/// A class in `K^M_0(F)/2 = Z/2`. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct MilnorK0Class(u128); + +impl MilnorK0Class { + /// Construct the parity class of an integer. + pub fn from_parity(value: u128) -> Self { + MilnorK0Class(value & 1) + } + + /// Construct the degree-zero class of a vector-space dimension. + pub fn from_dimension(dim: usize) -> Self { + MilnorK0Class((dim & 1) as u128) + } + + /// The representative bit, `0` or `1`. + pub fn value(self) -> u128 { + self.0 + } + + /// Addition in `Z/2`. + pub fn sum(self, other: Self) -> Self { + MilnorK0Class(self.0 ^ other.0) + } + + /// Multiplication in `Z/2`. + pub fn product(self, other: Self) -> Self { + MilnorK0Class(self.0 & other.0) + } + + /// Return the canonical display representation. + pub fn display(&self) -> String { + self.to_string() + } +} + +impl std::fmt::Display for MilnorK0Class { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "MilnorK0Mod2({})", self.0) + } +} + +/// A class in `K^M_1(Q)/2 = Q*/Q*^2`, stored as a nonzero square-free `i128` +/// representative. A rational `n/d` has the same square class as `n*d`. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct RationalMilnorK1Class { + representative: i128, +} + +impl RationalMilnorK1Class { + /// Construct `{a}` for a nonzero rational `a`. `None` on zero or bounded + /// `i128` overflow while reducing the square class. + pub fn from_rational(a: &Rational) -> Option { + if a.is_zero() { + return None; + } + let representative = try_square_free(a.numer().checked_mul(a.denom())?)?; + Some(RationalMilnorK1Class { representative }) + } + + fn from_representative(representative: i128) -> Option { + let representative = try_square_free(representative)?; + (representative != 0).then_some(RationalMilnorK1Class { representative }) + } + + /// The canonical square-free representative. + pub fn representative(self) -> i128 { + self.representative + } + + /// Whether this is the trivial square class. + pub fn is_trivial(self) -> bool { + self.representative == 1 + } + + /// Addition in `K^M_1/2`, induced by multiplication in `Q*`. + /// `None` if the bounded representative product overflows. + pub fn try_add(self, other: Self) -> Option { + RationalMilnorK1Class::from_representative( + self.representative.checked_mul(other.representative)?, + ) + } + + /// Return the canonical display representation. + pub fn display(&self) -> String { + self.to_string() + } +} + +impl std::fmt::Display for RationalMilnorK1Class { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "MilnorK1Mod2(Q; {})", self.representative) + } +} + +/// A class in `K^M_1(F_q)/2 = F_q*/F_q*^2` for an odd finite field, including +/// residue extensions whose concrete scalar type is selected dynamically by a +/// place. The class bit is `0` for a square and `1` for a nonsquare. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct FiniteFieldMilnorK1Class { + characteristic: u128, + field_order: u128, + nonsquare: u128, +} + +impl FiniteFieldMilnorK1Class { + fn new(characteristic: u128, field_order: u128, nonsquare: u128) -> Option { + (characteristic != 2 && nonsquare <= 1).then_some(FiniteFieldMilnorK1Class { + characteristic, + field_order, + nonsquare, + }) + } + + /// Characteristic prime of the finite field. + pub fn characteristic(self) -> u128 { + self.characteristic + } + + /// Order of the finite field. + pub fn field_order(self) -> u128 { + self.field_order + } + + /// The square-class bit: `0` for square, `1` for nonsquare. + pub fn nonsquare(self) -> u128 { + self.nonsquare + } + + /// Whether this is the trivial square class. + pub fn is_trivial(self) -> bool { + self.nonsquare == 0 + } + + /// Addition in `K^M_1/2`. `None` when the operands name different fields. + pub fn try_add(self, other: Self) -> Option { + if self.characteristic != other.characteristic || self.field_order != other.field_order { + return None; + } + FiniteFieldMilnorK1Class::new( + self.characteristic, + self.field_order, + self.nonsquare ^ other.nonsquare, + ) + } + + /// Return the canonical display representation. + pub fn display(&self) -> String { + self.to_string() + } +} + +impl std::fmt::Display for FiniteFieldMilnorK1Class { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "MilnorK1Mod2(F_{}; {})", + self.field_order, self.nonsquare + ) + } +} + +/// The unique class in `K^M_2(F_q)/2 = 0` for a finite field of odd +/// characteristic. Field metadata prevents accidental cross-field addition. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct FiniteFieldMilnorK2Class { + characteristic: u128, + field_order: u128, +} + +impl FiniteFieldMilnorK2Class { + fn new(characteristic: u128, field_order: u128) -> Option { + (characteristic != 2).then_some(FiniteFieldMilnorK2Class { + characteristic, + field_order, + }) + } + + /// Characteristic prime of the finite field. + pub fn characteristic(self) -> u128 { + self.characteristic + } + + /// Order of the finite field. + pub fn field_order(self) -> u128 { + self.field_order + } + + /// Every degree-two mod-two Milnor class over a finite field is trivial. + pub fn is_trivial(self) -> bool { + true + } + + /// Addition in the trivial group. `None` when the operands name different + /// fields. + pub fn try_add(self, other: Self) -> Option { + (self == other).then_some(self) + } + + /// Return the canonical display representation. + pub fn display(&self) -> String { + self.to_string() + } +} + +impl std::fmt::Display for FiniteFieldMilnorK2Class { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "MilnorK2Mod2(F_{}; 0)", self.field_order) + } +} + +/// A class in `K^M_1(F_q(t))/2`, stored by a nonzero representative. Equality +/// is equality modulo global squares, not literal rational-function equality. +#[derive(Debug, Clone)] +pub struct FunctionFieldMilnorK1Class { + representative: RationalFunction, +} + +impl FunctionFieldMilnorK1Class { + fn new(representative: RationalFunction) -> Option { + (!representative.is_zero()).then_some(FunctionFieldMilnorK1Class { representative }) + } + + /// A representative of the global square class. + pub fn representative(&self) -> &RationalFunction { + &self.representative + } + + /// Whether the representative is a global square. + pub fn is_trivial(&self) -> bool { + is_global_square_ff(&self.representative) + } + + /// Addition in `K^M_1/2`, induced by multiplication in `F_q(t)*`. + pub fn add(&self, other: &Self) -> Self { + FunctionFieldMilnorK1Class { + representative: self.representative.mul(&other.representative), + } + } + + /// Return the canonical display representation. + pub fn display(&self) -> String { + self.to_string() + } +} + +impl PartialEq for FunctionFieldMilnorK1Class { + fn eq(&self, other: &Self) -> bool { + let Some(other_inv) = other.representative.inv() else { + return false; + }; + is_global_square_ff(&self.representative.mul(&other_inv)) + } +} + +impl Eq for FunctionFieldMilnorK1Class {} + +impl std::fmt::Display for FunctionFieldMilnorK1Class { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "MilnorK1Mod2(F_{}(t); {})", + S::field_order(), + self.representative + ) + } +} + +/// `K^M_2(Q)/2`, realized by the norm-residue map as the rational two-torsion +/// Brauer group. +pub type RationalMilnorK2Class = Brauer2Class; + +/// `K^M_2(F_q(t))/2`, realized by the norm-residue map as the function-field +/// two-torsion Brauer group. +pub type FunctionFieldMilnorK2Class = FunctionFieldBrauer2Class; + +/// Exact characteristic-not-two fields for which Ogdoad exposes degree-one and +/// degree-two mod-two Milnor symbols. +pub trait Mod2MilnorField: ExactFieldScalar { + /// Carrier of `K^M_1(Self)/2`. + type K1Class; + /// Carrier of `K^M_2(Self)/2`. + type K2Class; + + /// Whether this monomorphization is inside the implemented + /// characteristic-not-two field domain. + fn supports_mod2_milnor() -> bool; + + /// The degree-one pure symbol `{a}`. `None` for `a = 0` or when bounded + /// square-class arithmetic leaves the represented domain. + fn milnor_symbol_1(a: &Self) -> Option; + + /// The degree-two pure symbol `{a,b}`. `None` if either argument is zero or + /// when the exact Brauer calculation leaves the represented domain. + fn milnor_symbol_2(a: &Self, b: &Self) -> Option; +} + +fn finite_milnor_symbol_1(a: &F) -> Option { + F::ensure_supported()?; + if a.is_zero() { + return None; + } + FiniteFieldMilnorK1Class::new( + F::characteristic_prime(), + F::field_order(), + u128::from(!F::is_square_value(*a)), + ) +} + +fn finite_milnor_symbol_2(a: &F, b: &F) -> Option { + F::ensure_supported()?; + if a.is_zero() || b.is_zero() { + return None; + } + FiniteFieldMilnorK2Class::new(F::characteristic_prime(), F::field_order()) +} + +impl Mod2MilnorField for Rational { + type K1Class = RationalMilnorK1Class; + type K2Class = RationalMilnorK2Class; + + fn supports_mod2_milnor() -> bool { + true + } + + fn milnor_symbol_1(a: &Self) -> Option { + RationalMilnorK1Class::from_rational(a) + } + + fn milnor_symbol_2(a: &Self, b: &Self) -> Option { + let a = RationalMilnorK1Class::from_rational(a)?.representative(); + let b = RationalMilnorK1Class::from_rational(b)?.representative(); + Brauer2Class::quaternion(a, b) + } +} + +impl Mod2MilnorField for Fp

{ + type K1Class = FiniteFieldMilnorK1Class; + type K2Class = FiniteFieldMilnorK2Class; + + fn supports_mod2_milnor() -> bool { + ::is_supported_odd_field() + } + + fn milnor_symbol_1(a: &Self) -> Option { + finite_milnor_symbol_1(a) + } + + fn milnor_symbol_2(a: &Self, b: &Self) -> Option { + finite_milnor_symbol_2(a, b) + } +} + +impl Mod2MilnorField for Fpn { + type K1Class = FiniteFieldMilnorK1Class; + type K2Class = FiniteFieldMilnorK2Class; + + fn supports_mod2_milnor() -> bool { + ::is_supported_odd_field() + } + + fn milnor_symbol_1(a: &Self) -> Option { + finite_milnor_symbol_1(a) + } + + fn milnor_symbol_2(a: &Self, b: &Self) -> Option { + finite_milnor_symbol_2(a, b) + } +} + +impl Mod2MilnorField for RationalFunction { + type K1Class = FunctionFieldMilnorK1Class; + type K2Class = FunctionFieldMilnorK2Class; + + fn supports_mod2_milnor() -> bool { + S::is_supported_odd_field() + } + + fn milnor_symbol_1(a: &Self) -> Option { + S::ensure_supported()?; + FunctionFieldMilnorK1Class::new(a.clone()) + } + + fn milnor_symbol_2(a: &Self, b: &Self) -> Option { + S::ensure_supported()?; + FunctionFieldBrauer2Class::quaternion(a, b) + } +} + +/// Construct the degree-one pure symbol `{a}` generically. +pub fn milnor_symbol_1(a: &F) -> Option { + F::milnor_symbol_1(a) +} + +/// Construct the degree-two pure symbol `{a,b}` generically. +pub fn milnor_symbol_2(a: &F, b: &F) -> Option { + F::milnor_symbol_2(a, b) +} + +// --------------------------------------------------------------------------- +// Strict e_n maps on the fundamental-ideal filtration. +// --------------------------------------------------------------------------- + +/// Why a strict mod-two Milnor invariant of a metric is undefined. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[non_exhaustive] +pub enum MilnorInvariantError { + /// The metric could not be diagonalized in the implemented + /// characteristic-not-two field domain. + DiagonalizerFailure, + /// The diagonalized form has a nonzero radical, so it does not represent a + /// class in the nonsingular Witt ring. + SingularForm { + /// Dimension of the radical. + radical_dim: usize, + }, + /// `e_n` was requested for a form outside `I^n`. + OutsideFundamentalIdeal { + /// Requested power `n`. + power: usize, + }, + /// The field parameters or bounded exact arithmetic are outside the + /// implemented symbol surface. + UnsupportedFieldOrArithmetic, +} + +impl std::fmt::Display for MilnorInvariantError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + MilnorInvariantError::DiagonalizerFailure => { + f.write_str("metric could not be diagonalized for mod-two Milnor invariants") + } + MilnorInvariantError::SingularForm { radical_dim } => { + write!( + f, + "Milnor invariants require a nonsingular form (radical_dim={radical_dim})" + ) + } + MilnorInvariantError::OutsideFundamentalIdeal { power } => { + write!(f, "form is outside the fundamental-ideal power I^{power}") + } + MilnorInvariantError::UnsupportedFieldOrArithmetic => { + f.write_str("field or bounded arithmetic is outside the mod-two Milnor surface") + } + } + } +} + +fn strict_diagonal( + metric: &crate::clifford::Metric, +) -> Result, MilnorInvariantError> { + let diagonal = as_diagonal(metric).ok_or(MilnorInvariantError::DiagonalizerFailure)?; + let radical_dim = diagonal.q().iter().filter(|x| x.is_zero()).count(); + if radical_dim != 0 { + return Err(MilnorInvariantError::SingularForm { radical_dim }); + } + Ok(diagonal) +} + +pub(crate) fn strict_milnor_e0( + metric: &crate::clifford::Metric, +) -> Result { + if !F::supports_mod2_milnor() { + return Err(MilnorInvariantError::UnsupportedFieldOrArithmetic); + } + let diagonal = strict_diagonal(metric)?; + Ok(MilnorK0Class::from_dimension(diagonal.dim())) +} + +pub(crate) fn finite_milnor_e1( + metric: &crate::clifford::Metric, +) -> Result { + F::ensure_supported().ok_or(MilnorInvariantError::UnsupportedFieldOrArithmetic)?; + let diagonal = strict_diagonal(metric)?; + let WittClassG::OddChar { + e0, + sclass, + field_order, + .. + } = finite_odd_witt(&diagonal).ok_or(MilnorInvariantError::UnsupportedFieldOrArithmetic)? + else { + unreachable!("finite_odd_witt returns the odd-characteristic variant") + }; + if e0 != 0 { + return Err(MilnorInvariantError::OutsideFundamentalIdeal { power: 1 }); + } + FiniteFieldMilnorK1Class::new(F::characteristic_prime(), field_order, sclass) + .ok_or(MilnorInvariantError::UnsupportedFieldOrArithmetic) +} + +pub(crate) fn finite_milnor_e2( + metric: &crate::clifford::Metric, +) -> Result { + let e1 = finite_milnor_e1(metric)?; + if !e1.is_trivial() { + return Err(MilnorInvariantError::OutsideFundamentalIdeal { power: 2 }); + } + FiniteFieldMilnorK2Class::new(e1.characteristic(), e1.field_order()) + .ok_or(MilnorInvariantError::UnsupportedFieldOrArithmetic) +} + +pub(crate) fn rational_milnor_e1( + metric: &crate::clifford::Metric, +) -> Result { + let diagonal = strict_diagonal(metric)?; + let bw = + bw_class_rational(&diagonal).ok_or(MilnorInvariantError::UnsupportedFieldOrArithmetic)?; + if bw.dimension_parity() != 0 { + return Err(MilnorInvariantError::OutsideFundamentalIdeal { power: 1 }); + } + RationalMilnorK1Class::from_representative(bw.signed_discriminant()) + .ok_or(MilnorInvariantError::UnsupportedFieldOrArithmetic) +} + +pub(crate) fn rational_milnor_e2( + metric: &crate::clifford::Metric, +) -> Result { + let diagonal = strict_diagonal(metric)?; + let bw = + bw_class_rational(&diagonal).ok_or(MilnorInvariantError::UnsupportedFieldOrArithmetic)?; + if bw.dimension_parity() != 0 || bw.signed_discriminant() != 1 { + return Err(MilnorInvariantError::OutsideFundamentalIdeal { power: 2 }); + } + Ok(bw.clifford_brauer_class().clone()) +} + +pub(crate) fn function_field_milnor_e1( + metric: &crate::clifford::Metric>, +) -> Result, MilnorInvariantError> { + let diagonal = strict_diagonal(metric)?; + let bw = bw_class_function_field(&diagonal) + .ok_or(MilnorInvariantError::UnsupportedFieldOrArithmetic)?; + if bw.dimension_parity() != 0 { + return Err(MilnorInvariantError::OutsideFundamentalIdeal { power: 1 }); + } + FunctionFieldMilnorK1Class::new(bw.signed_discriminant().clone()) + .ok_or(MilnorInvariantError::UnsupportedFieldOrArithmetic) +} + +pub(crate) fn function_field_milnor_e2( + metric: &crate::clifford::Metric>, +) -> Result, MilnorInvariantError> { + let diagonal = strict_diagonal(metric)?; + let bw = bw_class_function_field(&diagonal) + .ok_or(MilnorInvariantError::UnsupportedFieldOrArithmetic)?; + if bw.dimension_parity() != 0 || !is_global_square_ff(bw.signed_discriminant()) { + return Err(MilnorInvariantError::OutsideFundamentalIdeal { power: 2 }); + } + Ok(bw.clifford_brauer_class().clone()) +} + +// --------------------------------------------------------------------------- +// Tame residue maps on pure symbols. +// --------------------------------------------------------------------------- + +/// The discrete-valuation boundary `partial_p {a} = v_p(a) mod 2` from +/// `K^M_1(Q)/2` to `K^M_0(F_p)/2`. +pub fn milnor_residue_symbol_1_q(a: &Rational, p: u128) -> Option { + if a.is_zero() || !is_prime_u128(p) { + return None; + } + let pi = i128::try_from(p).ok()?; + let valuation = val_p(a.numer(), pi) as i128 - val_p(a.denom(), pi) as i128; + Some(MilnorK0Class::from_parity(valuation.rem_euclid(2) as u128)) +} + +/// The tame boundary of `{a,b}` at an odd rational prime, returned as a square +/// class in `K^M_1(F_p)/2`. The dyadic wild symbol is deliberately not supplied. +pub fn milnor_residue_symbol_2_q( + a: &Rational, + b: &Rational, + p: u128, +) -> Option { + if p == 2 || !is_prime_u128(p) { + return None; + } + let a = RationalMilnorK1Class::from_rational(a)?.representative(); + let b = RationalMilnorK1Class::from_rational(b)?.representative(); + let nonsquare = u128::from(try_hilbert_symbol_qp(a, b, p)? == -1); + FiniteFieldMilnorK1Class::new(p, p, nonsquare) +} + +/// The discrete-valuation boundary `partial_v {a} = v(a) mod 2` from +/// `K^M_1(F_q(t))/2` to `K^M_0(kappa(v))/2`. +pub fn milnor_residue_symbol_1_ff( + a: &RationalFunction, + place: &FunctionFieldPlace, +) -> Option { + if a.is_zero() { + return None; + } + Some(MilnorK0Class::from_parity( + try_valuation_at_ff(a, place)?.rem_euclid(2) as u128, + )) +} + +/// The tame boundary of `{a,b}` at a place of `F_q(t)`, returned as the +/// residue-field square class. It is the quadratic (`n = 2`) slice of the +/// existing tame Kummer symbol. +pub fn milnor_residue_symbol_2_ff( + a: &RationalFunction, + b: &RationalFunction, + place: &FunctionFieldPlace, +) -> Option { + let nonsquare = try_tame_symbol_exponent_ff(2, a, b, place)?; + FiniteFieldMilnorK1Class::new( + S::characteristic_prime(), + try_kappa_order(place)?, + nonsquare, + ) +} + /// The split Milnor invariant of a diagonal form over odd `F_q(t)`. /// /// The first component is the constant-field class selected at `∞`; the vector is @@ -223,7 +866,210 @@ mod tests { use super::*; use crate::clifford::Metric; use crate::forms::{springer_decompose_qp, try_is_isotropic_q}; - use crate::scalar::{Fp, Qp, RationalFunction}; + use crate::scalar::{Fp, Fpn, Qp, Rational, RationalFunction}; + + fn q(n: i128) -> Rational { + Rational::from_int(n) + } + + #[test] + fn degree_zero_is_zmod_two() { + let zero = MilnorK0Class::from_dimension(4); + let one = MilnorK0Class::from_dimension(5); + assert_eq!(zero.value(), 0); + assert_eq!(one.value(), 1); + assert_eq!(one.sum(one), zero); + assert_eq!(one.product(one), one); + assert_eq!(one.product(zero), zero); + assert_eq!(one.to_string(), "MilnorK0Mod2(1)"); + } + + #[test] + fn rational_k1_is_the_canonical_square_class() { + let two = milnor_symbol_1(&q(2)).unwrap(); + let eight = milnor_symbol_1(&q(8)).unwrap(); + let three = milnor_symbol_1(&q(3)).unwrap(); + let six = milnor_symbol_1(&q(6)).unwrap(); + assert_eq!(two, eight, "square factors do not change {{a}}"); + assert_eq!(two.try_add(three), Some(six)); + assert_eq!( + milnor_symbol_1(&Rational::new(2, 3)), + milnor_symbol_1(&q(6)) + ); + assert!(milnor_symbol_1(&Rational::zero()).is_none()); + assert_eq!(two.to_string(), "MilnorK1Mod2(Q; 2)"); + } + + #[test] + fn rational_k2_obeys_symbol_relations() { + let (a, b, c) = (q(2), q(3), q(5)); + let ab = a.mul(&b); + let lhs = milnor_symbol_2(&ab, &c).unwrap(); + let rhs = milnor_symbol_2(&a, &c) + .unwrap() + .add(&milnor_symbol_2(&b, &c).unwrap()); + assert_eq!(lhs, rhs, "{{ab,c}} = {{a,c}} + {{b,c}}"); + assert_eq!( + milnor_symbol_2(&a, &b), + milnor_symbol_2(&b, &a), + "degree-two mod-two symbols are symmetric" + ); + for n in -8..=8 { + if n == 0 || n == 1 { + continue; + } + let x = q(n); + let one_minus_x = q(1).add(&x.neg()); + assert!( + milnor_symbol_2(&x, &one_minus_x).unwrap().is_split(), + "Steinberg: {{a,1-a}}=0 at a={n}" + ); + } + assert!( + milnor_symbol_2(&a, &a.neg()).unwrap().is_split(), + "{{a,-a}}=0" + ); + assert!(milnor_symbol_2(&Rational::zero(), &b).is_none()); + } + + #[test] + fn finite_field_symbols_are_square_class_and_trivial_k2() { + let two = Fp::<5>::from_int(2); + let four = Fp::<5>::from_int(4); + let k1_two = milnor_symbol_1(&two).unwrap(); + let k1_four = milnor_symbol_1(&four).unwrap(); + assert_eq!(k1_two.nonsquare(), 1); + assert!(k1_four.is_trivial()); + assert!(milnor_symbol_2(&two, &four).unwrap().is_trivial()); + assert_eq!(k1_two.to_string(), "MilnorK1Mod2(F_5; 1)"); + + type F9 = Fpn<3, 2>; + for i in 1..F9::field_order() { + let x = ::from_index(i); + let square = x.mul(&x); + assert!(milnor_symbol_1(&square).unwrap().is_trivial()); + assert!(milnor_symbol_2(&x, &square).unwrap().is_trivial()); + } + + assert!(milnor_symbol_1(&Fp::<2>::one()).is_none()); + assert!(milnor_symbol_2(&Fp::<2>::one(), &Fp::<2>::one()).is_none()); + } + + #[test] + fn strict_en_maps_have_their_actual_ideal_domains() { + let two = q(2); + let three = q(3); + let p1 = crate::forms::pfister1(&two); + assert_eq!(p1.milnor_e0().unwrap().value(), 0); + assert_eq!(p1.milnor_e1().unwrap(), milnor_symbol_1(&two).unwrap()); + assert_eq!( + p1.milnor_e2(), + Err(MilnorInvariantError::OutsideFundamentalIdeal { power: 2 }) + ); + + let p2 = crate::forms::pfister(&[two.clone(), three.clone()]); + assert!(p2.milnor_e1().unwrap().is_trivial()); + assert_eq!( + p2.milnor_e2().unwrap(), + milnor_symbol_2(&two, &three).unwrap(), + "e2(<>) = {{a,b}}" + ); + let algebra = crate::clifford::CliffordAlgebra::new(p2.dim(), p2.clone()); + assert_eq!(algebra.milnor_e2(), p2.milnor_e2()); + + let odd = Metric::diagonal(vec![q(1), q(2), q(3)]); + assert_eq!( + odd.milnor_e1(), + Err(MilnorInvariantError::OutsideFundamentalIdeal { power: 1 }) + ); + let singular = Metric::diagonal(vec![q(1), q(0)]); + assert_eq!( + singular.milnor_e0(), + Err(MilnorInvariantError::SingularForm { radical_dim: 1 }) + ); + } + + #[test] + fn strict_en_maps_cover_finite_and_function_fields() { + let a = Fp::<5>::from_int(2); + let b = Fp::<5>::from_int(3); + let p2 = crate::forms::pfister(&[a, b]); + assert!(p2.milnor_e1().unwrap().is_trivial()); + assert!(p2.milnor_e2().unwrap().is_trivial()); + + let t = rf(&[0, 1], &[1]); + let two = rf(&[2], &[1]); + let ff_p1 = crate::forms::pfister1(&t); + assert_eq!(ff_p1.milnor_e1().unwrap(), milnor_symbol_1(&t).unwrap()); + let ff_p2 = crate::forms::pfister(&[t.clone(), two.clone()]); + assert!(ff_p2.milnor_e1().unwrap().is_trivial()); + assert_eq!( + ff_p2.milnor_e2().unwrap(), + milnor_symbol_2(&t, &two).unwrap() + ); + } + + #[test] + fn function_field_k1_equality_is_modulo_global_squares() { + let t = rf(&[0, 1], &[1]); + let t1 = rf(&[1, 1], &[1]); + let square = t1.mul(&t1); + assert_eq!( + milnor_symbol_1(&t), + milnor_symbol_1(&t.mul(&square)), + "multiplication by (t+1)^2 preserves the square class" + ); + assert!(milnor_symbol_1(&t) + .unwrap() + .add(&milnor_symbol_1(&t).unwrap()) + .is_trivial()); + assert_eq!(milnor_symbol_2(&t, &t1), milnor_symbol_2(&t1, &t)); + let one_minus_t = F5::one().add(&t.neg()); + assert!(milnor_symbol_2(&t, &one_minus_t).unwrap().is_split()); + } + + #[test] + fn pure_symbol_residues_reuse_the_existing_local_symbols() { + let eighteen_fifths = Rational::new(18, 5); + assert_eq!( + milnor_residue_symbol_1_q(&eighteen_fifths, 3) + .unwrap() + .value(), + 0 + ); + assert_eq!( + milnor_residue_symbol_1_q(&eighteen_fifths, 5) + .unwrap() + .value(), + 1 + ); + for p in [3, 5, 7, 11] { + let residue = milnor_residue_symbol_2_q(&q(2), &q(3), p).unwrap(); + assert_eq!( + residue.nonsquare(), + u128::from(try_hilbert_symbol_qp(2, 3, p).unwrap() == -1) + ); + } + assert!(milnor_residue_symbol_2_q(&q(-1), &q(-1), 2).is_none()); + + let t = rf(&[0, 1], &[1]); + let two = rf(&[2], &[1]); + let at_t = FunctionFieldPlace::Finite(poly(&[0, 1])); + assert_eq!(milnor_residue_symbol_1_ff(&t, &at_t).unwrap().value(), 1); + let residue = milnor_residue_symbol_2_ff(&t, &two, &at_t).unwrap(); + assert_eq!( + residue.nonsquare(), + try_tame_symbol_exponent_ff(2, &t, &two, &at_t).unwrap() + ); + assert_eq!(residue.field_order(), 5); + assert_eq!( + milnor_symbol_2(&t, &two) + .unwrap() + .ramified_places() + .contains(&at_t), + !residue.is_trivial() + ); + } /// `∂₅` via the capped `Q₅` Springer engine: the Witt class of the odd-valuation /// (parity-1) residue layer, built independently of the `i128` route. diff --git a/src/forms/witt/mod.rs b/src/forms/witt/mod.rs index 5a9deaed..2c6701cc 100644 --- a/src/forms/witt/mod.rs +++ b/src/forms/witt/mod.rs @@ -9,15 +9,18 @@ //! complex, rational, finite-field, and function-field domains. //! * `brauer_rational` computes rational two-torsion Hasse--Witt and Clifford //! Brauer classes. +//! * `centers` constructs full and even Clifford centers as discriminant +//! quadratic etale algebras and compares them with Brauer--Wall coordinates. //! * `cyclic` provides `Q/Z`-valued local invariants for unramified cyclic //! algebras and tame Kummer symbols. -//! * `milnor` assembles rational and odd-characteristic function-field residue -//! maps. +//! * `milnor` exposes degree-at-most-two mod-two Milnor symbols, strict `e_n` +//! maps, and rational/function-field residue maps. //! //! Child modules are re-exported through this module. mod brauer_rational; mod brauer_wall; +mod centers; mod class; mod cyclic; mod milnor; @@ -25,6 +28,7 @@ mod ring; pub use brauer_rational::*; pub use brauer_wall::*; +pub use centers::*; pub use class::*; pub use cyclic::*; pub use milnor::*; diff --git a/src/forms/witt/ring.rs b/src/forms/witt/ring.rs index 39100503..f3558f12 100644 --- a/src/forms/witt/ring.rs +++ b/src/forms/witt/ring.rs @@ -17,6 +17,10 @@ //! | 2 | **Hasse**/Clifford | [`oddchar::hasse_invariant_finite_odd`] | //! //! Thus discriminant and Hasse appear as successive invariants `e₁` and `e₂`. +//! [`Metric::milnor_e1`](crate::clifford::Metric::milnor_e1) and +//! [`Metric::milnor_e2`](crate::clifford::Metric::milnor_e2) are the strict +//! graded maps: they reject a form outside `I` or `I²`. [`EnStaircase`] remains +//! the compact finite-field report and does not by itself certify those domains. //! //! ## Stabilization — where the staircase stops, per field //! @@ -103,10 +107,12 @@ pub fn in_fundamental_ideal(metric: &Metric) -> Option { // The cohomological invariant staircase. // --------------------------------------------------------------------------- -/// The low cohomological invariants `(e₀, e₁, e₂)` of an odd-characteristic form, -/// with the field's stabilization recorded. `e₀ = dim mod 2`, `e₁ =` signed-disc -/// square-class (the genuine `H¹` invariant, reused from [`finite_odd_witt`]), and -/// `e₂ =` the Hasse invariant — `+1` over a finite field, where `I² = 0`. +/// A compact report of the low cohomological coordinates `(e₀, e₁, e₂)` of an +/// odd-characteristic form, with the field's stabilization recorded. This report +/// lists all coordinates together; use the strict `Metric::milnor_e_n` methods +/// when the `I^n` domain is part of the claim. `e₀ = dim mod 2`, `e₁ =` +/// signed-discriminant square class, and `e₂ =` the Hasse invariant — `+1` over a +/// finite field, where `I² = 0`. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct EnStaircase { /// `e₀ ∈ H⁰ = ℤ/2`: the dimension mod 2. @@ -123,9 +129,8 @@ pub struct EnStaircase { /// characteristic. `None` if non-diagonal. Over a finite field `I² = 0`, so /// `stabilizes_at = 2` and `e₂` is always `+1`; the genuine content is `(e₀, e₁)`. pub fn e_staircase_finite_odd(metric: &Metric) -> Option { - let (e0, e1) = match finite_odd_witt(metric)? { - WittClassG::OddChar { e0, sclass, .. } => (e0, sclass), - _ => unreachable!("finite_odd_witt returns the OddChar variant"), + let WittClassG::OddChar { e0, sclass: e1, .. } = finite_odd_witt(metric)? else { + unreachable!("finite_odd_witt returns the OddChar variant") }; Some(EnStaircase { e0, diff --git a/src/games/atomic_weight.rs b/src/games/atomic_weight.rs index 6f1ace6a..bb3d6a5a 100644 --- a/src/games/atomic_weight.rs +++ b/src/games/atomic_weight.rs @@ -52,9 +52,8 @@ pub fn atomic_weight(g: &Game) -> Option { let a_canon = Game::new(a_left.clone(), a_right.clone()).canonical(); // If A is not an integer, the candidate value stands. - let a_int = match integer_value(&a_canon) { - None => return Some(a_canon), - Some(k) => k, + let Some(a_int) = integer_value(&a_canon) else { + return Some(a_canon); }; // Integer case: resolve by the far star ⋆N, N > every nimber in G. diff --git a/src/games/loopy/nim_values.rs b/src/games/loopy/nim_values.rs index 6354aea0..c95b96a2 100644 --- a/src/games/loopy/nim_values.rs +++ b/src/games/loopy/nim_values.rs @@ -177,7 +177,7 @@ pub fn loopy_nim_values_certified( fn solve_mex_sidling(succ: &[Vec], is_side: &[bool]) -> Option<(Vec, usize)> { let n = succ.len(); let finite: Vec = (0..n).filter(|&v| !is_side[v]).collect(); - let mut order = finite.clone(); + let mut order = finite; order.sort_by_key(|&v| succ[v].iter().filter(|&&w| !is_side[w]).count()); let mut assigned = vec![false; n]; for (v, &side) in is_side.iter().enumerate() { diff --git a/src/games/misere.rs b/src/games/misere.rs index d9f7945b..0f713f91 100644 --- a/src/games/misere.rs +++ b/src/games/misere.rs @@ -11,8 +11,8 @@ use std::collections::{HashMap, HashSet}; use std::fmt; use std::hash::Hash; -// Historical module path retained for the already-public move generator; the -// normal-play sequence and certificate machinery live in `games::octal`. +// Compatibility re-export for the public move generator; normal-play sequence +// and certificate machinery live in `games::octal`. pub use crate::games::octal::octal_moves; fn misere_is_n_inner( diff --git a/src/games/octal.rs b/src/games/octal.rs index f6e15ff5..14657dde 100644 --- a/src/games/octal.rs +++ b/src/games/octal.rs @@ -73,8 +73,8 @@ impl OctalCode { /// Moves of an octal game `0.d_1d_2...` (`code[k-1] = d_k`) on a heap multiset. /// Removing `k` tokens may leave zero, one, or two nonempty heaps according to -/// bits `1`, `2`, and `4` of `d_k`. Values above seven retain the historical -/// low-three-bit behavior; checked new APIs use [`OctalCode`]. +/// bits `1`, `2`, and `4` of `d_k`. For compatibility, values above seven use +/// their low three bits; [`OctalCode`] rejects them. pub fn octal_moves(code: &[u128], pos: &[u128]) -> Vec> { let mut out = Vec::new(); for idx in 0..pos.len() { diff --git a/src/linalg/field.rs b/src/linalg/field.rs index 951baf79..1d74502e 100644 --- a/src/linalg/field.rs +++ b/src/linalg/field.rs @@ -7,6 +7,51 @@ use crate::scalar::Scalar; +/// Rank of a row-major matrix by unit-pivot elimination. +/// +/// Returns `None` when a nonzero residual matrix remains after all available +/// unit pivots have been exhausted. Over a field this is ordinary matrix rank; +/// over a ring it succeeds only when unit pivots suffice. +pub(crate) fn unit_pivot_rank(mut m: Vec>) -> Option { + let nrows = m.len(); + let ncols = m.first().map_or(0, Vec::len); + if m.iter().any(|row| row.len() != ncols) { + return None; + } + + let mut row = 0; + for col in 0..ncols { + let Some(piv) = (row..nrows).find(|&r| m[r][col].inv().is_some()) else { + continue; + }; + m.swap(row, piv); + let pinv = m[row][col].inv().expect("pivot is invertible"); + for c in 0..ncols { + m[row][c] = m[row][c].mul(&pinv); + } + for r in 0..nrows { + if r == row { + continue; + } + let factor = m[r][col].clone(); + if factor.is_zero() { + continue; + } + for c in 0..ncols { + m[r][c] = m[r][c].sub(&factor.mul(&m[row][c])); + } + } + row += 1; + if row == nrows { + break; + } + } + if (row..nrows).any(|r| (0..ncols).any(|c| !m[r][c].is_zero())) { + return None; + } + Some(row) +} + /// Solve the square system `A x = b` by unit-pivot Gauss--Jordan elimination. /// /// Returns `None` if some pivot column has no invertible entry. Panics if `A` @@ -168,6 +213,16 @@ mod tests { ); } + #[test] + fn rank_handles_rectangular_field_matrices() { + assert_eq!( + unit_pivot_rank(vec![vec![r(1), r(2), r(3)], vec![r(2), r(4), r(6)]]), + Some(1) + ); + assert_eq!(unit_pivot_rank::(vec![]), Some(0)); + assert_eq!(unit_pivot_rank(vec![vec![r(1)], vec![r(0), r(1)]]), None); + } + #[test] fn nullspace_returns_none_on_required_nonunit_pivot() { let m = vec![vec![Integer(0), Integer(2), Integer(-2)]]; diff --git a/src/py/forms.rs b/src/py/forms.rs index 005507de..13026c91 100644 --- a/src/py/forms.rs +++ b/src/py/forms.rs @@ -1017,6 +1017,50 @@ impl PyFiniteFieldInvariants { } } +#[pyclass( + name = "FiniteFieldNumericInvariants", + module = "ogdoad", + skip_from_py_object +)] +#[derive(Clone)] +struct PyFiniteFieldNumericInvariants { + inner: crate::forms::FiniteFieldNumericInvariants, +} + +#[pymethods] +impl PyFiniteFieldNumericInvariants { + #[getter] + fn characteristic(&self) -> u128 { + self.inner.characteristic + } + #[getter] + fn absolute_degree(&self) -> usize { + self.inner.absolute_degree + } + #[getter] + fn field_order(&self) -> u128 { + self.inner.field_order + } + #[getter] + fn level(&self) -> usize { + self.inner.level + } + #[getter] + fn pythagoras_number(&self) -> usize { + self.inner.pythagoras_number + } + #[getter] + fn u_invariant(&self) -> usize { + self.inner.u_invariant + } + fn display(&self) -> String { + self.inner.display() + } + fn __repr__(&self) -> String { + self.inner.display() + } +} + /// Classify a surreal Clifford algebra on the exact-square real-table subdomain /// as a matrix algebra over ℝ/ℂ/ℍ. Symmetric metrics are diagonalized when possible. #[pyfunction] @@ -1337,6 +1381,55 @@ fn dickson_of_versor(v: &NimberMV) -> PyResult { .ok_or_else(|| PyValueError::new_err("not an invertible homogeneous versor")) } +#[pyclass( + name = "Char2SymmetryFactorization", + module = "ogdoad", + skip_from_py_object +)] +#[derive(Clone)] +struct PyChar2SymmetryFactorization { + degree: usize, + matrix: Vec>, + factors: Vec>, + dickson: u128, + clifford_verified: bool, + rendered: String, +} + +#[pymethods] +impl PyChar2SymmetryFactorization { + #[getter] + fn degree(&self) -> usize { + self.degree + } + #[getter] + fn matrix(&self) -> Vec> { + self.matrix.clone() + } + #[getter] + fn factors(&self) -> Vec> { + self.factors.clone() + } + #[getter] + fn factor_count(&self) -> usize { + self.factors.len() + } + #[getter] + fn dickson(&self) -> u128 { + self.dickson + } + #[getter] + fn clifford_verified(&self) -> bool { + self.clifford_verified + } + fn display(&self) -> String { + self.rendered.clone() + } + fn __repr__(&self) -> String { + self.rendered.clone() + } +} + // --------------------------------------------------------------------------- // Alternating and Hermitian forms (the "form + involution" siblings) // --------------------------------------------------------------------------- @@ -1533,6 +1626,17 @@ impl PyHermitianForm { inner: self.inner.signature(|x| x.sign()), } } + /// Restrict `h` over `Surcomplex/Surreal` to the ordinary quadratic form + /// `q(v)=h(v,v)` over `Surreal`, doubling the dimension. + fn restrict_scalars(&self) -> PyResult { + let metric = self + .inner + .restrict_scalars() + .map_err(|err| PyValueError::new_err(err.to_string()))?; + Ok(SurrealAlgebra { + inner: Arc::new(CliffordAlgebra::new(metric.dim(), metric)), + }) + } fn __repr__(&self) -> String { format!("HermitianForm(dim={})", self.inner.dim()) } @@ -1724,6 +1828,31 @@ impl PyFiniteHermitianForm { }) }) } + /// Restrict this Hermitian form to an ordinary quadratic algebra over its + /// fixed field. The concrete return type is `Fp2Algebra`, `F4Algebra`, + /// `Fp3Algebra`, or `Fp5Algebra`, preserving backend separation. + fn restrict_scalars(&self, py: Python<'_>) -> PyResult> { + macro_rules! restrict_into { + ($p:literal, $degree:literal, $alg:ident) => {{ + let form = finite_hermitian_form_fpn::<$p, $degree>(&self.gram)?; + let metric = form + .restrict_scalars() + .map_err(|err| PyValueError::new_err(err.to_string()))?; + $alg { + inner: Arc::new(CliffordAlgebra::new(metric.dim(), metric)), + } + .into_py_any(py) + }}; + } + + match (self.p, self.degree) { + (2, 2) => restrict_into!(2, 2, Fp2Algebra), + (2, 4) => restrict_into!(2, 4, F4Algebra), + (3, 2) => restrict_into!(3, 2, Fp3Algebra), + (5, 2) => restrict_into!(5, 2, Fp5Algebra), + _ => Err(unsupported_finite_hermitian_field_err()), + } + } fn __repr__(&self) -> String { format!( "FiniteHermitianForm(F_{}^{}, dim={})", @@ -1923,6 +2052,16 @@ macro_rules! with_finite_char2_field { }}; } +macro_rules! with_supported_finite_field { + ($p:expr, $degree:expr, |$field:ident| $body:expr) => {{ + if $p == 2 { + with_finite_char2_field!($degree, |$field| $body) + } else { + with_finite_odd_field!($p, $degree, |$field| $body) + } + }}; +} + type PyFFPoly = Vec; type PyFFRationalFunction = (PyFFPoly, PyFFPoly); @@ -2164,6 +2303,94 @@ fn finite_char2_field_index(x: F) -> u128 { .expect("finite char-2 field element must be enumerated by from_index") } +fn parse_char2_matrix(matrix: &[Vec]) -> PyResult>> { + F::ensure_supported().ok_or_else(unsupported_char2_finite_field_err)?; + let order = F::field_order(); + matrix + .iter() + .map(|row| { + row.iter() + .map(|&value| { + if value < order { + Ok(F::from_index(value)) + } else { + Err(PyValueError::new_err(format!( + "field element index {value} is outside F_{order}" + ))) + } + }) + .collect() + }) + .collect() +} + +fn wrap_char2_factorization( + degree: usize, + certificate: crate::forms::Char2SymmetryFactorization, +) -> PyChar2SymmetryFactorization { + let matrix = certificate + .matrix() + .iter() + .map(|row| { + row.iter() + .copied() + .map(finite_char2_field_index::) + .collect() + }) + .collect(); + let factors = certificate + .factors() + .iter() + .map(|root| { + root.iter() + .copied() + .map(finite_char2_field_index::) + .collect() + }) + .collect(); + PyChar2SymmetryFactorization { + degree, + matrix, + factors, + dickson: certificate.dickson(), + clifford_verified: certificate.verifies_clifford_action(), + rendered: certificate.display(), + } +} + +/// The additive characteristic-two spinor norm of an isometry of `F_{2^degree}`. +#[pyfunction] +#[pyo3(signature = (q, b, matrix, degree=1))] +fn char2_spinor_norm( + q: Vec, + b: BTreeMap<(usize, usize), u128>, + matrix: Vec>, + degree: usize, +) -> PyResult { + with_finite_char2_metric!(degree, &q, &b, |metric| { + let parsed = parse_char2_matrix::(&matrix)?; + crate::forms::char2_spinor_norm(&metric, &parsed) + .map_err(|error| PyValueError::new_err(error.to_string())) + }) +} + +/// Certify a vector-symmetry factorization of an isometry of `F_{2^degree}`. +#[pyfunction] +#[pyo3(signature = (q, b, matrix, degree=1))] +fn factor_char2_isometry( + q: Vec, + b: BTreeMap<(usize, usize), u128>, + matrix: Vec>, + degree: usize, +) -> PyResult { + with_finite_char2_metric!(degree, &q, &b, |metric| { + let parsed = parse_char2_matrix::(&matrix)?; + crate::forms::factor_char2_isometry(&metric, &parsed) + .map(|certificate| wrap_char2_factorization(degree, certificate)) + .map_err(|error| PyValueError::new_err(error.to_string())) + }) +} + fn char2_ff_poly_indices(poly: &Poly) -> PyFFPoly { poly.coeffs() .iter() @@ -3848,30 +4075,10 @@ fn unsupported_odd_prime_field_err() -> PyErr { PyValueError::new_err("supported odd prime fields: F_3, F_5, F_7, F_11, F_13") } -macro_rules! with_prime_field { - ($p:expr, $body:ident) => {{ - match $p { - 2 => $body::<2>(), - 3 => $body::<3>(), - 5 => $body::<5>(), - 7 => $body::<7>(), - 11 => $body::<11>(), - 13 => $body::<13>(), - _ => return Err(unsupported_prime_field_err()), - } - }}; -} - -fn level_for_prime() -> PyResult> { - Ok(crate::forms::level::

()) -} - -fn pythagoras_for_prime() -> PyResult> { - Ok(crate::forms::pythagoras_number::

()) -} - -fn u_invariant_for_prime() -> PyResult> { - Ok(crate::forms::u_invariant::

()) +fn numeric_invariants_for_field( +) -> PyResult { + crate::forms::finite_field_numeric_invariants::() + .ok_or_else(|| PyValueError::new_err("unsupported finite-field parameters")) } fn sum_of_squares_for_prime(x: i128, n: usize) -> PyResult { @@ -3888,27 +4095,47 @@ fn hilbert_symbol_prime(a: i128, b: i128) -> PyResult { )) } -/// The level/Stufe of the prime field `F_p`: the least `n` for which `-1` is a -/// sum of `n` squares. Supported primes are `2, 3, 5, 7, 11, 13`; other inputs -/// raise `ValueError`. +/// Numeric finite-field invariants for `F_{p^degree}`. +#[pyfunction] +#[pyo3(signature = (p, degree=1))] +fn finite_field_numeric_invariants( + p: u128, + degree: usize, +) -> PyResult { + with_supported_finite_field!(p, degree, |F| { + Ok(PyFiniteFieldNumericInvariants { + inner: numeric_invariants_for_field::()?, + }) + }) +} + +/// The level/Stufe of `F_{p^degree}`: the least `n` for which `-1` is a sum of +/// `n` squares. #[pyfunction] -fn level(p: u128) -> PyResult> { - with_prime_field!(p, level_for_prime) +#[pyo3(signature = (p, degree=1))] +fn level(p: u128, degree: usize) -> PyResult { + with_supported_finite_field!(p, degree, |F| { + Ok(numeric_invariants_for_field::()?.level) + }) } -/// The Pythagoras number of the prime field `F_p`: least `n` such that every sum -/// of squares is already a sum of `n` squares. +/// The Pythagoras number of `F_{p^degree}`. #[pyfunction] -fn pythagoras_number(p: u128) -> PyResult> { - with_prime_field!(p, pythagoras_for_prime) +#[pyo3(signature = (p, degree=1))] +fn pythagoras_number(p: u128, degree: usize) -> PyResult { + with_supported_finite_field!(p, degree, |F| { + Ok(numeric_invariants_for_field::()?.pythagoras_number) + }) } -/// The u-invariant of the prime field `F_p`: largest dimension of an anisotropic -/// quadratic form. In characteristic 2 this returns `None` because the diagonal -/// odd-characteristic model is not the right form theory. +/// The quadratic u-invariant of `F_{p^degree}`. Characteristic two uses regular +/// quadratic forms. #[pyfunction] -fn u_invariant(p: u128) -> PyResult> { - with_prime_field!(p, u_invariant_for_prime) +#[pyo3(signature = (p, degree=1))] +fn u_invariant(p: u128, degree: usize) -> PyResult { + with_supported_finite_field!(p, degree, |F| { + Ok(numeric_invariants_for_field::()?.u_invariant) + }) } /// Is `x` a sum of exactly `n` squares in the prime field `F_p`? @@ -7790,6 +8017,8 @@ pub(crate) fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; + m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; @@ -7998,12 +8227,15 @@ pub(crate) fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(witt_class_error, m)?)?; m.add_function(wrap_pyfunction!(dickson_matrix, m)?)?; m.add_function(wrap_pyfunction!(dickson_of_versor, m)?)?; + m.add_function(wrap_pyfunction!(char2_spinor_norm, m)?)?; + m.add_function(wrap_pyfunction!(factor_char2_isometry, m)?)?; m.add_function(wrap_pyfunction!(classify_symplectic, m)?)?; m.add_function(wrap_pyfunction!(classify_symplectic_nimber, m)?)?; m.add_function(wrap_pyfunction!(witt_decompose_real, m)?)?; m.add_function(wrap_pyfunction!(isometric_ordinal_finite, m)?)?; m.add_function(wrap_pyfunction!(ordinal_witt, m)?)?; m.add_function(wrap_pyfunction!(artin_schreier_class_finite, m)?)?; + m.add_function(wrap_pyfunction!(finite_field_numeric_invariants, m)?)?; m.add_function(wrap_pyfunction!(level, m)?)?; m.add_function(wrap_pyfunction!(pythagoras_number, m)?)?; m.add_function(wrap_pyfunction!(u_invariant, m)?)?; diff --git a/src/scalar/extension.rs b/src/scalar/extension.rs index 1e28e212..5864044d 100644 --- a/src/scalar/extension.rs +++ b/src/scalar/extension.rs @@ -17,6 +17,11 @@ //! The `Fpn` implementation delegates to the shared //! [`FiniteField`](crate::scalar::FiniteField) relative trace and norm. //! +//! [`QuadraticInvolution`] is the companion capability for restriction of +//! Hermitian forms. It names the involution-fixed world rather than the one +//! distinguished `FieldExtension::Base`, so it also covers the current +//! `F_16/F_4` intermediate-field cell exactly. +//! //! # Exclusions //! //! Two functors are outside the implemented interface: @@ -35,6 +40,37 @@ use crate::scalar::{ nim_square, nim_trace, Fp, Fpn, Nimber, Ordered, Qq, Scalar, Surcomplex, WittVec, }; +/// A scalar algebra with a distinguished order-two involution and an explicitly +/// represented fixed scalar world. +/// +/// This is the structure needed to restrict a Hermitian form from `E` to the +/// ordinary quadratic form `q(v) = h(v,v)` over the fixed world `F`. It is +/// deliberately separate from [`FieldExtension`]: `Fpn` currently names +/// `Fp

` as its distinguished extension base, while its middle-Frobenius +/// involution fixes the intermediate field `Fpn`. +/// +/// The returned basis has exactly two elements because `E/F` is quadratic. +/// [`project_fixed`](Self::project_fixed) returns `None` when its argument is not +/// fixed by the involution or cannot be represented by the selected `Fixed` +/// backend. +pub trait QuadraticInvolution: Scalar { + /// The scalar world fixed by the involution. + type Fixed: Scalar; + + /// The nontrivial involution of `E/F`. + fn involute(&self) -> Self; + + /// A distinguished `F`-basis of `E`. + fn fixed_basis() -> [Self; 2]; + + /// Embed an element of the fixed world into `E`. + fn embed_fixed(value: &Self::Fixed) -> Self; + + /// Project an involution-fixed element of `E` into the represented fixed + /// world. + fn project_fixed(value: &Self) -> Option; +} + /// A finite separable field extension `E/F` over a distinguished base `F`, with the /// degree and the relative trace `Tr_{E/F}` and norm `N_{E/F}`. pub trait FieldExtension: Scalar { @@ -64,6 +100,26 @@ pub trait FieldExtension: Scalar { // Adjoin `i`, a root of `x²+1`. The `Ordered` bound restricts this implementation // to characteristic-zero ordered bases, where `x²+1` is irreducible. +impl QuadraticInvolution for Surcomplex { + type Fixed = S; + + fn involute(&self) -> Self { + self.conj() + } + + fn fixed_basis() -> [Self; 2] { + [Self::one(), Self::i()] + } + + fn embed_fixed(value: &S) -> Self { + Surcomplex::new(value.clone(), S::zero()) + } + + fn project_fixed(value: &Self) -> Option { + value.im.is_zero().then(|| value.re.clone()) + } +} + impl FieldExtension for Surcomplex { type Base = S; fn extension_degree() -> usize { @@ -106,6 +162,72 @@ impl FieldExtension for Fpn { } } +impl QuadraticInvolution for Fpn { + type Fixed = Fp

; + + fn involute(&self) -> Self { + use crate::scalar::FiniteField; + self.frobenius() + } + + fn fixed_basis() -> [Self; 2] { + [Self::one(), Self::generator()] + } + + fn embed_fixed(value: &Fp

) -> Self { + Self::constant(value.value()) + } + + fn project_fixed(value: &Self) -> Option> { + use crate::scalar::FiniteField; + if value.frobenius() != *value || value.coeffs()[1] != 0 { + return None; + } + Some(Fp::

::from_u128(value.coeff(0))) + } +} + +// `F_16/F_4` is the one currently public Hermitian cell whose fixed field is +// not the prime field named by `FieldExtension::Base`. The two `Fpn` backends +// use independently generated polynomial bases, so this implementation records +// an explicit isomorphism: a primitive generator of `F_4^*` maps to the unique +// order-three subgroup generator obtained from a primitive element of `F_16`. +impl QuadraticInvolution for Fpn<2, 4> { + type Fixed = Fpn<2, 2>; + + fn involute(&self) -> Self { + use crate::scalar::FiniteField; + self.frobenius_iter(2) + } + + fn fixed_basis() -> [Self; 2] { + [Self::one(), Self::primitive_element()] + } + + fn embed_fixed(value: &Self::Fixed) -> Self { + use crate::scalar::FiniteField; + if value.is_zero() { + return Self::zero(); + } + let fixed_generator = FiniteField::pow(&Self::primitive_element(), 5); + let base_generator = Fpn::<2, 2>::primitive_element(); + let exponent = base_generator + .discrete_log(*value) + .expect("nonzero F_4 element has a discrete log to a primitive generator"); + FiniteField::pow(&fixed_generator, exponent) + } + + fn project_fixed(value: &Self) -> Option { + if value.involute() != *value { + return None; + } + (0..4).find_map(|code| { + let base = Fpn::<2, 2>::from_coeffs(&[code & 1, (code >> 1) & 1]); + (Self::embed_fixed(&base) == *value).then_some(base) + }) + } +} + // ───────────────────────── Qq / Qq (= Q_p) (degree F) ───────────── // // The unramified local extension Q_q = Frac(W_N(F_q)) over Q_p. It is Galois with @@ -387,6 +509,37 @@ mod tests { ); } + #[test] + fn f16_f4_quadratic_involution_has_an_exact_fixed_field_isomorphism() { + type F4 = Fpn<2, 2>; + type F16 = Fpn<2, 4>; + + let elements: Vec = (0..4) + .map(|code| F4::from_coeffs(&[code & 1, (code >> 1) & 1])) + .collect(); + for a in &elements { + let embedded_a = ::embed_fixed(a); + assert_eq!(embedded_a.involute(), embedded_a); + assert_eq!( + ::project_fixed(&embedded_a), + Some(*a) + ); + for b in &elements { + assert_eq!( + ::embed_fixed(&a.add(b)), + embedded_a.add(&::embed_fixed(b)) + ); + assert_eq!( + ::embed_fixed(&a.mul(b)), + embedded_a.mul(&::embed_fixed(b)) + ); + } + } + + let basis = ::fixed_basis(); + assert_ne!(basis[1].involute(), basis[1]); + } + // ---------- Qq over Qp (the unramified local extension, Witt Frobenius) ---------- #[test] diff --git a/src/scalar/finite_field/fpn.rs b/src/scalar/finite_field/fpn.rs index 7341cb33..88416f5a 100644 --- a/src/scalar/finite_field/fpn.rs +++ b/src/scalar/finite_field/fpn.rs @@ -254,9 +254,8 @@ fn poly_gcd(mut a: Vec, mut b: Vec, p: u128) -> Vec { } fn poly_make_monic(poly: Vec, p: u128) -> Vec { - let d = match poly_degree(&poly) { - Some(d) => d, - None => return Vec::new(), + let Some(d) = poly_degree(&poly) else { + return Vec::new(); }; let inv = mod_inverse_u128(poly[d], p).expect("nonzero finite-field coefficient"); trim_poly( diff --git a/src/scalar/functor/gauss.rs b/src/scalar/functor/gauss.rs index ea065cb2..2a4b3449 100644 --- a/src/scalar/functor/gauss.rs +++ b/src/scalar/functor/gauss.rs @@ -250,10 +250,9 @@ impl ResidueField for Gauss { fn residue(&self) -> Option { match self.valuation() { - None => Some(RationalFunction::zero()), Some(v) if v < 0 => None, Some(0) => gauss_angular_component(self), - Some(_) => Some(RationalFunction::zero()), + None | Some(_) => Some(RationalFunction::zero()), } } diff --git a/src/scalar/functor/ramified.rs b/src/scalar/functor/ramified.rs index 5c1f0cc9..bd38b61e 100644 --- a/src/scalar/functor/ramified.rs +++ b/src/scalar/functor/ramified.rs @@ -291,10 +291,9 @@ impl ResidueField for Ramified { fn residue(&self) -> Option { match self.valuation() { - None => Some(S::Residue::zero()), Some(v) if v < 0 => None, Some(0) => self.residue_unit(), - Some(_) => Some(S::Residue::zero()), + None | Some(_) => Some(S::Residue::zero()), } } diff --git a/src/scalar/poly.rs b/src/scalar/poly.rs index a4945631..bde7bb35 100644 --- a/src/scalar/poly.rs +++ b/src/scalar/poly.rs @@ -38,8 +38,7 @@ pub(crate) fn atomic(s: &str) -> bool { match ch { '(' => depth += 1, ')' => depth -= 1, - ' ' if depth == 0 => return false, - '⋅' | '∧' | '↑' | '/' | '+' | '-' if depth == 0 => return false, + ' ' | '⋅' | '∧' | '↑' | '/' | '+' | '-' if depth == 0 => return false, _ => {} } } diff --git a/src/scalar/residue.rs b/src/scalar/residue.rs index be8a6c24..162802ea 100644 --- a/src/scalar/residue.rs +++ b/src/scalar/residue.rs @@ -75,10 +75,9 @@ impl ResidueField for Qp { type Residue = Fp

; fn residue(&self) -> Option> { match self.valuation() { - None => Some(Fp::

::zero()), // 0 ↦ 0 - Some(v) if v < 0 => None, // not integral + Some(v) if v < 0 => None, // not integral Some(0) => Some(Fp::

::from_u128(self.unit() % P)), - Some(_) => Some(Fp::

::zero()), // in 𝔪 + None | Some(_) => Some(Fp::

::zero()), // 0 or in 𝔪 } } fn residue_unit(&self) -> Option> { @@ -101,10 +100,9 @@ impl ResidueField for Qq type Residue = Fpn; fn residue(&self) -> Option> { match self.valuation() { - None => Some(Fpn::::zero()), Some(v) if v < 0 => None, Some(0) => self.unit_residue(), - Some(_) => Some(Fpn::::zero()), + None | Some(_) => Some(Fpn::::zero()), } } fn residue_unit(&self) -> Option> { @@ -125,10 +123,9 @@ impl ResidueField for Laurent { type Residue = S; fn residue(&self) -> Option { match self.valuation() { - None => Some(S::zero()), Some(v) if v < 0 => None, Some(0) => self.leading_coeff(), // coeff at t⁰ = the constant term - Some(_) => Some(S::zero()), + None | Some(_) => Some(S::zero()), } } fn residue_unit(&self) -> Option { diff --git a/src/scalar/small/analytic.rs b/src/scalar/small/analytic.rs index 491efa76..88c00823 100644 --- a/src/scalar/small/analytic.rs +++ b/src/scalar/small/analytic.rs @@ -85,8 +85,7 @@ fn is_square_mod_two_power(a: u128, k: u128) -> bool { /// retained unit precision is too short to decide the positive case. fn q2_unit_is_square(unit: u128, k: u128) -> Option { match k { - 0 => None, - 1 => None, + 0 | 1 => None, 2 => { if unit % 4 == 3 { Some(false) diff --git a/writeups/excess.bib b/writeups/excess.bib index 6e1239e2..22bf3d5c 100644 --- a/writeups/excess.bib +++ b/writeups/excess.bib @@ -34,6 +34,29 @@ @misc{dimuro2011 url = {https://arxiv.org/abs/1108.0962} } +@article{popovych2018, + author = {Popovych, Roman}, + title = {Multiplicative Orders of Elements in {Conway's} Towers of Finite Fields}, + journal = {Algebra and Discrete Mathematics}, + volume = {25}, + number = {1}, + year = {2018}, + pages = {137--146}, + url = {https://admjournal.luguniv.edu.ua/index.php/adm/article/view/172} +} + +@article{caglierohermanszechtman2025, + author = {Cagliero, Leandro and Herman, Allen and Szechtman, Fernando}, + title = {{Artin--Schreier} Towers of Finite Fields}, + journal = {Finite Fields and Their Applications}, + volume = {106}, + year = {2025}, + pages = {102606}, + doi = {10.1016/j.ffa.2025.102606}, + eprint = {2405.10159}, + archiveprefix = {arXiv} +} + @book{lidlniederreiter1997, author = {Lidl, Rudolf and Niederreiter, Harald}, title = {Finite Fields}, @@ -66,6 +89,17 @@ @article{hoechsmann1995 doi = {10.1007/BF02849803} } +@article{uehara1985, + author = {Uehara, Tsuyoshi}, + title = {On Cyclotomic Units Connected with {$p$}-Adic Characters}, + journal = {Journal of the Mathematical Society of Japan}, + volume = {37}, + number = {1}, + year = {1985}, + pages = {65--77}, + doi = {10.2969/jmsj/03710065} +} + @book{rosen2002, author = {Rosen, Michael}, title = {Number Theory in Function Fields}, diff --git a/writeups/excess.pdf b/writeups/excess.pdf index 9371be2f..21e37e10 100644 Binary files a/writeups/excess.pdf and b/writeups/excess.pdf differ diff --git a/writeups/excess.tex b/writeups/excess.tex index 598d3c6a..bba98556 100644 --- a/writeups/excess.tex +++ b/writeups/excess.tex @@ -12,13 +12,14 @@ \newtheorem{lemma}[theorem]{Lemma} \newtheorem{corollary}[theorem]{Corollary} \newtheorem{conjecture}[theorem]{Conjecture} +\newtheorem*{thmA}{Theorem A} \theoremstyle{definition} \newtheorem{definition}[theorem]{Definition} \theoremstyle{remark} \newtheorem{remark}[theorem]{Remark} \title{Finite Excess in Ordinal Nim Multiplication:\\ -Selected Order Reductions} +Four Selected-Order Problems} \author{GPT 5.6 Sol \and Claude Fable 5 \and a9lim} \date{} @@ -26,31 +27,27 @@ \maketitle \begin{abstract} -Lenstra's description of ordinal nim multiplication attaches to each odd -prime $p$ a finite excess $m_p$. Available values suggest that $m_p$ is -always $0$, $1$, or $4$, with the value determined by the component support -of $f(p)=\operatorname{ord}_p(2)$. The universal rule remains open. - -We reduce the rule to four exact multiplicative-order assertions. The zero -arm asks a synchronized structural norm to generate every primary component -of a primitive-support quotient. The ordinary singleton arm asks a marked -cyclotomic unit to have full projective order at the unique prime over two -selected by Conway ancestry. The cubic arm asks a recursively selected -Gaussian period to generate a norm-one torus. The exceptional -$2\cdot3^k$ arm asks an explicit corrected norm to have full primary order; -equivalently, certain Capelli compositions are irreducible. The reductions -retain the full primary valuation and the selected Frobenius phase. - -For the cubic and exceptional arms we identify exact principal-ray and -reduced-unit obstructions. For the singleton-even zero arm we identify a -deterministic Conway--Fermat packet recursion and a supersingular realization; -both expose the selected orbit without proving its order. We also obtain an -unconditional level-dependent bound in the exceptional column. Generic -trace, norm, conductor, class-number, factor-shape, or unselected reciprocity -data do not evaluate the marked coordinate. Lean checks the algebraic -reductions and named finite certificates, but defines rather than proves the -universal exceptional target. Source-backed rows and exact finite -calculations are stated separately from universal theorems. +Lenstra's construction of ordinal nim multiplication assigns a finite excess +$m_p$ to every odd prime $p$. The observed values obey a simple $0/1/4$ +rule determined by the component support of +$f(p)=\operatorname{ord}_p(2)$, but no universal proof is known. + +We give an exact status theorem. The rule is equivalent to four +full-primary-order assertions for elements selected by the literal Conway +tower: a synchronized structural norm, a marked cyclotomic unit, a Gaussian +period in a norm-one torus, and a corrected conductor-five norm. These are +lossless reductions, not hypotheses, and all four universal assertions remain +open. The paper then isolates the arithmetic content of each coordinate. It +constructs the primitive-support quotient and the Conway--Fermat packet +recursion, identifies the ordinary Artin symbol, proves the cubic Singer +recursion and a selected-order lower bound, and gives the exceptional Capelli +criterion and a level-dependent translate bound. A final reciprocity theorem +shows that Frobenius, character, and affine-holonomy transforms of one marked +Kummer phase can only erase or re-encode that phase; they cannot evaluate it +without an independent ancestry input. + +The proof record distinguishes paper theorems, kernel-checked algebra, named +finite certificates, source-backed data, and the still-open universal rule. \end{abstract} \section{Introduction} @@ -90,11 +87,29 @@ \section{Introduction} valuable for individual rows and as countermodels to overstrong lemmas, but they do not prove Conjecture~\ref{conj:rule}. +\begin{thmA} +The $0/1/4$ rule is equivalent to the conjunction of four selected-order +assertions $Z,O,C,D$ stated in Theorem~\ref{thm:arms}. Each assertion is an +exact Euler nonpower test for one Conway-selected coordinate and retains the +full primary valuation. The reductions, the coordinate formulas, the +selected Singer bound of Proposition~\ref{prop:sieve}, and the exceptional +translate bound of Proposition~\ref{prop:d-bound} are proved here. None of +$Z,O,C,D$ is proved universally; consequently the $0/1/4$ rule and absolute +boundedness of the finite excess remain open. +\end{thmA} + +This theorem is the organizing spine of the paper. The point is not to +replace one conjecture by four opaque ones, but to remove every ambient-field +ambiguity while preserving the one datum that coarse invariants lose: the +Frobenius orbit selected recursively by the Conway tower. + After finite-field preliminaries, Section~\ref{sec:arms} gives the four-arm equivalence. Sections~\ref{sec:z}--\ref{sec:d} develop one lossless selected -coordinate for each arm. Section~\ref{sec:bounded} separates the stronger -$0/1/4$ rule from absolute boundedness. Section~\ref{sec:verification} -states the formal and finite-evidence boundary. +coordinate for each arm. Section~\ref{sec:nim-reciprocity} proves the +saturation boundary for reciprocity arguments built from one marked phase. +Section~\ref{sec:bounded} separates the stronger $0/1/4$ rule from absolute +boundedness, and Section~\ref{sec:verification} records the authority of every +claim class used in the paper. \section{Finite-field preliminaries}\label{sec:prelim} @@ -158,7 +173,7 @@ \section{Finite-field preliminaries}\label{sec:prelim} $(2^h-1)/p$ and apply Proposition~\ref{prop:power}. \end{proof} -\section{The four exact arms}\label{sec:arms} +\section{Exact four-arm reduction}\label{sec:arms} For $h>1$, put \[ @@ -237,7 +252,7 @@ \section{The four exact arms}\label{sec:arms} universal assertions. None of $Z,O,C,D$ is presently known in full. \end{remark} -\section{The zero arm}\label{sec:z} +\section{The zero arm: structural norms and Fermat packets}\label{sec:z} \subsection{Synchronized structural norm} @@ -376,16 +391,109 @@ \subsection{The singleton-even Conway--Fermat chain} of different conductor. Consequently any induction must use the complete marked orbit, not only the integer conductor. -There is also a supersingular realization on the fixed curve -$Y^2+Y=X^3$. It identifies the selected quotient value with the ramified -function $(Y+1)/Y$ at the recursively selected point. The point has the -expected Fermat annihilator, but the function value is not a Weil-pairing -value, and torsion alone does not determine its order. Equivalently, the -remaining statement is exclusion of every proper-conductor packet along the +This isolates the part not settled by the existing Conway-tower literature. +Popovych proves lower bounds and verifies the required relative full orders +through level eleven using the known Fermat factorizations +\cite{popovych2018}. Cagliero, Herman, and Szechtman prove an exact product +formula for global order in terms of the relative orders, as well as a normal +basis theorem for the associated tower elements +\cite{caglierohermanszechtman2025}. Neither result forces every relative +factor $\delta_n$ to equal $F_n$; that all-level equality is precisely the +remaining selected assertion here. + +There is an exact supersingular realization of the selected ancestry. + +\begin{proposition}[supersingular realization and pairing boundary] +\label{prop:z-elliptic} +On +\[ + \mathcal E:\quad y^2+y=x^3+x^2 +\] +put $P_n=(c_n,c_{n+1})$. Then $P_n\in\mathcal E$, and for $n\geq2$, +\[ + [F_n]P_n=O. +\] +More precisely, if $\pi$ is absolute Frobenius and $\iota=\pi+[1]$, then +\[ + \iota(x,y)=(x+1,y+x+1),\qquad + \iota^2=[-1],\qquad \pi^4=[-4]. +\] +For $g=(y+1)/y$, one has +\[ + g(P_n)=\frac{c_{n+1}+1}{c_{n+1}}, + \qquad \operatorname{ord}(g(P_n))=\delta_{n+1}. +\] +If $T=(0,0)$, then $T$ has order five and +\[ + \operatorname{div}(g) + =2[-T]+[2T]-2[T]-[-2T]. +\] +Thus $g$ is the quotient of the two fixed doubling Miller functions at +$-T$ and $T$, but it is not a Weil or reduced-Tate pairing function attached +to the $F_n$-torsion point $P_n$ when $n\geq2$. +\end{proposition} + +\begin{proof} +The Conway recursion gives +\[ + c_{n+1}^2+c_{n+1} + =c_n(c_n^2+c_n)=c_n^3+c_n^2, +\] +so $P_n$ lies on $\mathcal E$. The coordinate formula for +$\iota(P)=\pi(P)+P$ follows from the chord law. Applying it twice gives the +inverse $(x,y)\mapsto(x,y+1)$, so +$\pi^2+2\pi+2=0$ in $\operatorname{End}(\mathcal E)$ and hence +$\pi^4=[-4]$. + +Put $H=2^{n+1}$. The $2^H$-Frobenius fixes $c_n$ and sends $c_{n+1}$ to +$c_{n+1}+1$, hence sends $P_n$ to $-P_n$. For $n\geq2$, +\[ + \pi^H=(\pi^4)^{2^{n-1}}=[2^{2^n}], +\] +which gives $[2^{2^n}+1]P_n=O$. + +The formula for $g(P_n)$ is immediate from the coordinates, and its order is +$\delta_{n+1}$ by the definition of $u_{n+1}$. The five rational points of +$\mathcal E/\mathbb F_2$ show that $T$ has order five. Direct intersection +with the two horizontal lines gives +\[ + \operatorname{div}(y)=2[T]+[-2T]-3[O],\qquad + \operatorname{div}(y+1)=2[-T]+[2T]-3[O], +\] +and subtraction gives the divisor of $g$. Equivalently, +$g=f_{2,-T}/f_{2,T}$ for functions normalized at $O$. Its divisor is +supported on fixed rational five-torsion, with coefficients $2,1,-2,-1$. +For $n\geq2$, these valuations are not divisible by $F_n\geq17$, whereas the +divisor of an $F_n$-Miller function and every $F_n$-th-power correction have +all coefficients divisible by $F_n$. Thus $g$ cannot be such a function +attached to $P_n$, even modulo an $F_n$-th power. +A Weil or reduced Tate pairing on $\mathcal E[F_n]$ takes values in +$\mu_{F_n}$, whereas $g(P_n)$ has order dividing $F_{n+1}$. Pairwise +coprimality of the Fermat numbers and $\delta_{n+1}>1$ exclude equality. +\end{proof} + +The proposition supplies exact CM annihilator control but not generator +status. For $n\geq3$, no elliptic endomorphism can carry $P_n$ to +$P_{n-1}$: the image order would divide both coprime numbers $F_n$ and +$F_{n-1}$, although $P_{n-1}\ne O$. For $n=2$, the same conclusion follows +from $\operatorname{ord}(P_1)=3$ and $\operatorname{ord}(P_2)\mid17$. +Thus the relation +$x(P_n)=y(P_{n-1})$ is not an isogeny division tower, and pairing functoriality +along this proposed tower cannot propagate primitivity. A perfect alternating +pairing likewise guarantees some detecting partner, not nonvanishing on a +prescribed line. + +The tempting cubic curve $Y^2+Y=X^3$ is not a coordinate rewrite of +$\mathcal E$: the two curves have respectively three and five rational points +over $\mathbb F_2$. For $u=(Y+1)/Y$ on that auxiliary curve, +$u/(u+1)^2=X^3$; every positive-level Fermat-torsion value is already a cube +because $\gcd(F_n,3)=1$. Its natural cubic Kummer class is therefore trivial +and cannot replace the selected five-torsion function above. The remaining +statement is still exclusion of every proper-conductor packet along the complete resultant chain. Trace, relative norm, irreducibility, and the etale Jacobian hold for proper packets as well as the full packet. -\section{The ordinary singleton arm}\label{sec:o} +\section{The ordinary arm: a marked cyclotomic unit}\label{sec:o} Fix an odd prime $r$, write $b=b_r$, and abbreviate $x_a=x_{r,a}$, $y_a=x_a+1$, and $F_a=F_{r,a}$. @@ -459,6 +567,37 @@ \section{The ordinary singleton arm}\label{sec:o} about the new selected class. \end{proof} +The selected symbol has an exact Jacobi--Fourier expansion. Let +$Q=|F_a|$, let $H=\langle x_a\rangle\subset F_a^\times$ have order $N_a$, +put $M=(Q-1)/N_a$, and let $\chi$ be a complex-valued order-$p$ power +character. Extend complex-valued multiplicative characters by zero at zero +and define +\[ + f(h)=\chi(1-h),\qquad + \widehat f(\theta)=\sum_{h\in H}f(h)\theta(h)^{-1} + \quad(\theta\in\widehat H). +\] +For an extension $\psi_\theta$ of $\theta$ to $F_a^\times$, and with +$H^\perp$ the character group of $F_a^\times/H$, one has +\[ + \widehat f(\theta) + =\frac1M\sum_{\lambda\in H^\perp} + J(\psi_\theta^{-1}\lambda,\chi), + \qquad + f(x_a)=\frac1{N_a}\sum_{\theta\in\widehat H} + \theta(x_a)\widehat f(\theta). +\] +Indeed, the character average +$M^{-1}\sum_{\lambda\in H^\perp}\lambda$ is the indicator of $H$; +inserting it in the first Fourier sum gives the Jacobi sums, and Fourier +inversion gives the second identity. This is a fully explicit reciprocity +formula, but the selected label survives in $\theta(x_a)$ and the Jacobi +unit phases. Ideal factorization or valuation formulas for the Jacobi sums +do not by themselves determine this inverse-DFT entry. This separation of +the ideal and $p$-adic unit conditions is also visible in Uehara's +cyclotomic-unit criterion \cite{uehara1985}; it supplies an exact test, not a +uniform nonvanishing theorem for the Conway-selected entry. + This residue-symbol condition is a marked Artin-symbol assertion, not a statement about decomposition or conductor. Its ray character splits into a ramified local logarithm and an unramified ideal-class character evaluated @@ -472,7 +611,7 @@ \section{The ordinary singleton arm}\label{sec:o} need not have a zero unramified term; the open assertion is noncancellation in the actual selected sum. -\section{The cubic arm}\label{sec:c} +\section{The cubic arm: a selected Singer generator}\label{sec:c} Let $h=3^k$, $\zeta_k=\kappa_{3^k}$, and $\gamma_k=\zeta_k+\zeta_k^{-1}$. @@ -610,7 +749,7 @@ \section{The cubic arm}\label{sec:c} \cite{hoechsmann1995}. Arm $C$ is exactly the assertion that both factors in the displayed index formula vanish at every level. -\section{The exceptional arm}\label{sec:d} +\section{The exceptional arm: corrected norm and Capelli}\label{sec:d} Let $h=3^k$, $q=2^h$, $\zeta=\zeta_k$, and $\omega=\zeta^h\in\mathbb F_4$. @@ -760,6 +899,181 @@ \section{The exceptional arm}\label{sec:d} principal-ray factors cannot force one another through the shared prime over two. Each requires its own marked unit-image calculation. +Even a perfect one-dimensional Kummer--Artin pairing does not show that a +prescribed prime has nonzero image in the Artin quotient. The obstruction +already occurs arithmetically. + +\begin{proposition}[perfect-pairing no-go]\label{prop:pairing-no-go} +There exist a number field $K$ containing $\mu_3$, a global unit $u$ which is +not a cube in $K$, and exactly one prime $\mathfrak q$ of $K$ above two; this +prime splits completely in the nontrivial cyclic Kummer extension +$K(\sqrt[3]{u})/K$. +\end{proposition} + +\begin{proof} +Take +\[ + K=\mathbb Q(\zeta_{12})=\mathbb Q(\zeta_3,\sqrt3), + \qquad u=2+\sqrt3. +\] +The Pell equation gives +$\mathcal O_{\mathbb Q(\sqrt3)}^\times=\{\mathord\pm u^n:n\in\mathbb Z\}$, +so $u$ is not a cube in the real quadratic subfield. If $X^3-u$ acquired a +root in its quadratic extension $K$, its cubic factorization over the real +subfield would also have a linear factor. Hence $u$ is not a cube in $K$, +and adjoining a cube root gives a nontrivial cyclic cubic extension. + +Locally at two, $\mathbb Q_2(\zeta_3)/\mathbb Q_2$ is unramified quadratic +and adjoining $i$ is ramified quadratic. The two local quadratics are +linearly disjoint, so $K\otimes\mathbb Q_2$ is a degree-four field; hence two +has a unique prime $\mathfrak q$ in $K$. In its residue field +$\sqrt3\equiv1$, so $u\equiv1\pmod{\mathfrak q}$. The polynomial $X^3-u$ +has the simple root $1$ modulo $\mathfrak q$, because its derivative at one +is the unit $3$. Hensel's lemma makes $u$ a cube in $K_{\mathfrak q}$; since +$\mu_3\subset K_{\mathfrak q}$, the polynomial splits completely there and +$\mathfrak q$ splits completely in $K(\sqrt[3]{u})$. +\end{proof} + +Thus nontriviality of the Kummer line, one-dimensionality of its dual, and +even uniqueness of the prime above two are compatible with zero marked +pairing. In the four arms, perfect duality reduces the target to +nonvanishing of the Conway-selected prime class; it does not prove that +nonvanishing. + +\section{Nim reciprocity saturation and marked holonomy} +\label{sec:nim-reciprocity} + +The residue-symbol reductions suggest using reciprocity to evaluate the +remaining phase. There is a precise saturation boundary for that program. +Let $\ell$ be an odd prime, let $s\in\mu_\ell$, and put +$E=\operatorname{ord}_\ell(2)$. The two-axis Frobenius--cyclotomic family +is $s_{j,c}=s^{2^j c}$ for $0\leq j2$. In the strict octal class, for +every $n\ge2$ the code +\(0.\underbrace{33\cdots3}_{2^{n-1}\text{ digits}}\) has exact quotient +$\mathcal T_n$, realizing every tame quotient in the finite $|P|=2$ +classification. +\item In mis\`ere Grundy's game, the exact quotients generated through heaps +$12$, $13$, and $18$ have orders $6$, $12$, and $24$, respectively, with +the presentations and reduced translation tables given below. Any finite +full Grundy quotient has a nontrivial kernel. +\end{enumerate} +\end{thmA} + +The normal form in item~3 belongs to a comparison class, while the tame family +in the same item is a strict finite-octal theorem. Item~4 is an exact prefix +theorem plus a conditional structure theorem. None of the four items asserts +automatic periodicity for valid split traces or finiteness of the full Grundy +quotient. +Sections~\ref{sec:tables} and~\ref{sec:traces} build the trace calculus, +Section~\ref{sec:normalform} gives the comparison normal form, +Section~\ref{sec:tame} realizes the tame family, and +Section~\ref{sec:grundy} gives the Grundy extensions. +Section~\ref{sec:boundary} states the formal and open boundaries. + +\section{Transition tables and deterministic values}\label{sec:tables} + +Let $A$ be a set of finite impartial games closed under options and +disjunctive sum. Under mis\`ere play the terminal game is an $N$-position. +For $G,H\in A$, put +\[ + G\sim_A H + \quad\Longleftrightarrow\quad + o^-(G+X)=o^-(H+X)\quad\hbox{for every }X\in A. +\] +The quotient $Q(A)=A/{\sim_A}$ is a commutative monoid. It comes with the +distinguished subset $P$ of previous-player wins. The pair $(Q,P)$ is +\emph{reduced}: if $x\ne y$, then some $z$ has exactly one of $xz,yz$ in +$P$. Also $1\notin P$, because the terminal position is $N$. + +Plambeck and Siegel developed this quotient method and the generalized mex +rule in \cite{plambecksiegel2008}; Siegel subsequently proved the abstract +classification by valid transition tables in \cite{siegel2007}. A +transition pair is $(x,E)\in Q\times\Pow(Q)$, with product +\begin{equation} + (x,E)(y,F)=(xy,yE\cup xF). +\end{equation} +A transition table $T$ on $(Q,P)$ is valid when it is complete, closed under +the displayed product, parity-correct, +\begin{equation} + (x,E)\in T + \quad\Longrightarrow\quad + x\in P\ \Longleftrightarrow\ E\ne\varnothing\text{ and }E\cap P=\varnothing, +\end{equation} +and well-founded: there is $R:Q\to\N$, with $R(1)=0$, such that every +$x$ has some $(x,E)\in T$ satisfying $R(e)d$, whole-heap removal is also inactive, and +\begin{equation} + E_n=\{x_{n-k}:A_k,\ 1\le k\le d\}. +\end{equation} + +\begin{theorem}[Automatic periodicity]\label{thm:periodicity} +Let $\Gamma$ be a finite no-split octal code with last nonzero digit $d$. +If $Q(\Gamma)$ is finite, then its single-heap quotient word +$x_n=\Phi(H_n)$ is ultimately periodic. A repeated state occurs among at +most $|Q|^d+1$ consecutive $d$-windows after the exceptional prefix. +\end{theorem} + +\begin{proof} +The ordered window +\[ + W_n=(x_{n-d},\ldots,x_{n-1})\in Q^d +\] +determines $E_n$ by the preceding formula. By +Theorem~\ref{thm:determinism}, $E_n$ determines $x_n$. Thus $W_n$ +determines $W_{n+1}$. The deterministic map acts on at most $|Q|^d$ +states, so one state repeats and the entire subsequent orbit is periodic. +\end{proof} + +\begin{corollary}\label{cor:decidable} +For a fixed finite no-split code and a fixed finite reduced $(Q,P)$, exact +realization is decidable. The same is true when the code length is bounded +in advance. +\end{corollary} + +Indeed there are finitely many transition tables on $Q$; validity is a +finite condition, and each induced trace either fails or enters a cycle. +Generation and parity can then be checked on finite data. The existential +problem over codes of unbounded length is not settled by this argument. + +Splitting is the exact place where finite memory fails. The new term +\[ + S_m=\{x_i x_j:i,j\ge1,\ i+j=m\} +\] +depends on an unbounded convolution of the prefix. Nevertheless an already +periodic trace remains finitely certifiable. + +\begin{proposition}[Periodic convolution]\label{prop:convolution} +Assume $x_{n+p}=x_n$ for all $n\ge N$, where $N,p\ge1$. Then +\[ + S_{m+p}=S_m\qquad(m\ge2N+p). +\] +Consequently the records $(x_n,E_n)$ of a length-$d$ octal code are +periodic for all $n\ge2N+p+d$. +\end{proposition} + +\begin{proof} +For a pair $i+j=m+p$, one of $i,j$ is at least $N+p$; subtract $p$ from +that component and use periodicity. Conversely, for $i+j=m$, one component +is at least $N$; add $p$ to it. Commutativity permits reordering. The +one-remainder terms are easier, and whole-heap bits are inactive past $d$. +\end{proof} + +More explicitly, if the code is supported through digit $d$, then checking +one full period of records after any bound $B\ge2N+p+d$ certifies every later +heap record. Thus a proposed preperiod, period, valid table, finite trace +through that first tail period, and generation check form an exact +infinite-rule certificate. This is a transition-table form of the +periodicity argument in \cite{siegel2006notes,plambecksiegel2008}. + +\subsection{The Boolean-language boundary}\label{sec:languages} + +The convolution obstruction can be made exact. For languages +$L,M\subseteq\N$, write +\[ + L+M=\{i+j:i\in L,\ j\in M\}. +\] +Fix a candidate word $x=(x_n)$ and put $X_q=\{n:x_n=q\}$. For each +$q\in Q$, let $A_q$ be the language of heap sizes at which $q$ occurs as an +option value. In terms of the three masks it is +\begin{equation} + A_q=C_q\ \cup\!\bigcup_{k\in A}\bigl(k+(X_q\cap\N_{>0})\bigr)\ \cup\! + \bigcup_{k\in B}\ \bigcup_{uv=q} + \bigl(k+(X_u\cap\N_{>0})+(X_v\cap\N_{>0})\bigr), +\end{equation} +where $C_1=C$ is the finite whole-removal language and $C_q=\varnothing$ +for $q\ne1$. Bounds excluding empty remainders are understood in the +shifted terms. For $S\subseteq Q$, define the exact Boolean cell +\begin{equation} + D_S=\bigcap_{q\in S}A_q\ \cap\! + \bigcap_{q\notin S}(\N\setminus A_q). +\end{equation} + +\begin{proposition}[Exact language equations]\label{prop:language-equations} +Let $\mu:\Pow(Q)\rightharpoonup Q$ be any finite partial decoder. A word +$x$ is its total octal trace exactly when +\begin{equation} + X_q=\bigcup_{\mu(S)=q}D_S\qquad(q\in Q) +\end{equation} +and the failure language +\begin{equation} + F_\mu=\bigcup_{\mu(S)\text{ undefined}}D_S +\end{equation} +is empty. Coefficient $n$ belongs to $F_\mu$ exactly when the decoder has no +value on the complete option set at $n$. +\end{proposition} + +\begin{proof} +The first displayed equation is simply the whole-, one-, and +two-remainder definition sorted by quotient value. Hence $n\in D_S$ if and +only if the complete option-value set at $H_n$ is exactly $S$. Applying +$\mu$ coefficientwise gives both displayed conclusions. +\end{proof} + +These equations are unary, but they are not finite-state equations. Unary +conjunctive grammars already generate nonregular languages such as +$\{a^{4^r}:r\ge0\}$ \cite{jez2008}. In fact the unconstrained trace problem +reaches the full effective boundary. + +\begin{theorem}[Arbitrary-decoder hardness]\label{thm:decoder-hardness} +For the single fixed octal code $0.7$, the following problem is +$\Pi^0_1$-complete: given a finite commutative monoid $M$ and an arbitrary +finite partial decoder $\mu:\Pow(M)\rightharpoonup M$, decide whether the trace +starting from $x_0=1$ is total. +\end{theorem} + +\begin{proof} +Non-totality has a least failing coefficient, so it is recursively +enumerable and totality is in $\Pi^0_1$. + +For hardness, start with a unary unambiguous conjunctive grammar. Emptiness +for these grammars is undecidable by an effective reduction from Turing +machine emptiness \cite[Lemma~12]{jezokhotin2017}. Effectively put the +grammar in epsilon-free binary conjunctive normal form and prefix a fixed +two-letter word, so that lengths $0$ and $1$ are irrelevant while emptiness +is preserved. Unambiguity is needed only for the cited source theorem; the +normalization need not preserve it. Let $\mathcal N$ be the resulting finite +nonterminal set and let $V_n\subseteq\mathcal N$ be its truth vector on +$a^n$. Take the full, effectively known Boolean cube +$C=\Pow(\mathcal N)$ as the color set and form the commutative monoid +\[ + M_C=\{\text{multisets on $C$ of degree at most $2$}\}\sqcup\{\bot\}. +\] +Multiplication is multiset union until degree $2$ and is $\bot$ after +overflow, with $\bot$ absorbing; the empty multiset is the identity. This is +associative: for three ordinary factors both parenthesizations give their +union when its total degree is at most two and $\bot$ otherwise, while an +existing $\bot$ remains absorbing. + +Assign $H_h$ the singleton marker $[V_{h+1}]$. Under $0.7$, the option set +of $H_h$ for $h\ge3$ is exactly +\begin{equation} + \{[V_h]\}\ \cup + \{[V_{i+1},V_{j+1}]:i,j\ge1,\ i+j=h-1\}. +\end{equation} +The degree-two markers encode every binary cut of the word of length $h+1$ +whose two parts have length at least two. The degree-one predecessor marker, +together with the fixed vector $V_1$, encodes the two cuts having a part of +length one. Because the alphabet is unary, unordered pairs lose no cut +information. Thus the exact option set determines $V_{h+1}$. + +More explicitly, the normalized grammar gives an effective finite update +map +\[ + \mathsf H:C\times\Pow(\{[u,v]:u,v\in C\})\longrightarrow C. +\] +It computes the next truth vector from the predecessor vector and the set of +unordered binary-cut vectors; the two cuts involving $V_1$ are folded into +the update. For every decodable signature +\[ + S=\{[c]\}\cup R, + \qquad R\subseteq\{[u,v]:u,v\in C\}, +\] +define $\mu(S)=[\mathsf H(c,R)]$ when the start nonterminal is absent from +$\mathsf H(c,R)$, and leave $\mu(S)$ undefined when it is present. This is a +uniform finite definition, independent of which signatures the eventual +trace reaches. Malformed signatures may be left undefined. The genuinely +exceptional $H_1$ cell computes $V_2$ directly. The $H_2$ cell is the +decodable signature $(c,R)=(V_2,\varnothing)$, for which +$\mathsf H(V_2,\varnothing)=V_3$ because only the two length-one cuts occur. +Both cells are left undefined exactly when their output start bit is true. + +Before the first accepted length, induction now gives precisely the asserted +markers. At the first accepted length the decoder fails. Hence the trace is +total if and only if the grammar language is empty. Turing-machine emptiness +is $\Pi^0_1$-complete, completing the reduction. +\end{proof} + +The decoder in Theorem~\ref{thm:decoder-hardness} is deliberately arbitrary. +It need not extend to a reduced, closed, parity-correct, ranked mis\`ere +transition table. Therefore the theorem neither proves undecidability of +natural realization nor decides a fixed valid-table trace. It proves the +more surgical statement needed here: any decision argument must use the +valid-table axioms in a load-bearing way. Finiteness of $Q$, commutativity of +its multiplication, and unary input alone are insufficient. + +\section{A finite-exception heap normal form}\label{sec:normalform} + +The abstract table theorem does admit a uniform one-species heap realization +once the finite prefix is allowed to be source-local. This comparison result +pinpoints why the direct construction does not immediately become octal. + +\begin{theorem}[Finite-exception unary-heap normal form] +\label{thm:normalform} +Let $(Q,P)$ be a nontrivial finite reduced bipartite commutative monoid. +Suppose $T$ is a closed parity-correct transition table and $R:Q\to\N$, with +$R(1)=0$, such that every $q\in Q$ has a record $(q,E_q)\in T$ satisfying +$R(e)N$, $\opts(H_n)=\{H_{n-1}\}$; and +\item the exact mis\`ere quotient of all finite sums of these heaps is + isomorphic to $(Q,P)$. +\end{enumerate} +\end{theorem} + +\begin{proof} +First find a star record. Choose a nonidentity value $a$ of least rank and a +descending record $(a,E)$. Every member of $E$ has smaller rank, hence is +$1$. The set $E$ cannot be empty, since determinism against +$(1,\varnothing)$ would give $a=1$. Therefore $(a,\{1\})\in T$. + +For each $q\in Q$, choose a descending record $(q,E_q)$. Enumerate the +elements of $Q$ in nondecreasing rank and make one prefix heap $B_q$ with +options $B_e$ for $e\in E_q$. Every target is an earlier heap. Append one +inert padding heap $I$, followed by heaps $U_0,U_1,\ldots$ with +\[ + \opts(U_0)=\{I\},\qquad \opts(U_{k+1})=\{U_k\}. +\] +Assign the values +\[ + \Phi(B_q)=q,\qquad \Phi(I)=1,\qquad + \Phi(U_k)=a^{k+1}. +\] +Closure of $T$ and $(a,\{1\})\in T$ give by induction +\[ + (a^{k+1},\{a^k\})\in T, +\] +so every single-heap record lies in $T$. The transition record of a sum is +the product of its component records and therefore also lies in $T$. +All moves strictly lower the sum of the displayed heap ranks. Induction on +that rank and parity show +\[ + o^-(G)=P\quad\Longleftrightarrow\quad\Phi(G)\in P +\] +for every position $G$. Each $q$ is represented by $B_q$, so $\Phi$ is +surjective. Equal values are indistinguishable, while reducedness and +surjectivity realize a separating context for unequal values. Hence the +fibers of $\Phi$ are exactly the indistinguishability classes. +\end{proof} + +This is an eventually translation-invariant finite-exception heap normal +form, not a finite-octal realization. Turning a prefix edge +$H_i\to H_{i-d}$ into an octal digit at offset $d$ also creates translated +moves from every larger heap. The construction supplies no values for those +additional targets. Controlling that cross-talk---possibly by a different, +generator-level encoding---remains an unresolved bridge. The comparison +class and finite octal games are therefore not ordered by inclusion: the +former permits arbitrary source-local prefix moves but only a unary tail, +whereas the latter permits persistent multi-removal and split moves. + +The inert pad is not merely an expositional nuisance. + +\begin{proposition}[Inert-pad obstruction]\label{prop:pad-obstruction} +Assume $|Q|>2$, and choose one strictly descending record $(q,E_q)$ for each +$q\in Q$ as in Theorem~\ref{thm:normalform}. There is no finite octal code +with prefix heaps $B_q$ followed by a later terminal heap $I$ such that every +$B_q$ has exact option-value set $E_q$. +\end{proposition} + +\begin{proof} +Determinism identifies every chosen empty record with +$(1,\varnothing)$ and identifies all chosen records with option set $\{1\}$ +with the unique star value. Since $|Q|>2$, some chosen $E_q$ therefore +contains an $e\ne1$. + +A whole-heap-removal bit contributes only $1$, so the option $e$ at $B_q$ +must come from a one-remainder or two-remainder bit at some removal size $k$. +Such a bit is persistent. If it leaves one remainder at the earlier heap +$B_q$, then $k<|B_q|<|I|$ and it leaves a remainder at $I$. If it leaves two +remainders at $B_q$, then $k+2\le |B_q|<|I|$ and it also gives a split option +at $I$. Either conclusion contradicts that $I$ is terminal. +\end{proof} + +Thus no placement trick, including spacing the representatives farther +apart, turns the proof of Theorem~\ref{thm:normalform} verbatim into an octal +proof. This does not rule out octal universality: a successful compiler may +replace the inert pad by an active bridge or may encode quotient generators +contextually rather than representing every chosen descending record by one +heap. + +\section{Uniform realization of the tame family}\label{sec:tame} + +For $n\ge2$, let +\[ + m=2^{n-1},\qquad L=m+1, +\] +and let $\Gamma_n$ be the finite octal code +\[ + \Gamma_n=0.\underbrace{33\cdots3}_{m\text{ digits}}. +\] +A move subtracts any number in $\{1,\ldots,m\}$ from one heap, deleting it +if the remainder is zero and otherwise leaving the one remainder heap. +There are no splits. + +Define +\[ + Q_n=\{1,a\}\sqcup\{z_v:v\in\F_2^n\},\qquad + P_n=\{a,z_0\}. +\] +Identify $v\in\F_2^n$ with an integer in $[0,2^n)$ and write vector addition +as XOR. Multiplication is +\begin{equation} + a^2=1,\qquad z_u z_v=z_{u\xor v},\qquad + a z_v=z_{v\xor1}. +\end{equation} +This is Siegel's tame quotient $\mathcal T_n$: its kernel is +$\{z_v\}\cong(\mathbb Z/2)^n$, and $|Q_n|=2^n+2$ +\cite{plambecksiegel2008,siegel2006notes}. + +\begin{theorem}[Tame-family realization]\label{thm:tame} +For every $n\ge2$, the exact mis\`ere quotient of $\Gamma_n$ is +$\mathcal T_n$. +\end{theorem} + +\begin{proof} +For a heap $H_h$, let $r(h)$ be the residue of $h$ modulo $L$ and define +\begin{equation} + \Phi(H_h)= + \begin{cases} + 1,&r(h)=0,\\ + a,&r(h)=1,\\ + z_{r(h)},&2\le r(h)\le m. + \end{cases} +\end{equation} +Extend $\Phi$ multiplicatively. + +We first prove the outcome criterion directly. Discard residue-zero heaps. +Let $t\in\F_2$ be the parity of the number of residue-one heaps, and let +$R$ be the multiset of residues in $\{2,\ldots,m\}$. From +the displayed multiplication laws, +\begin{equation} + \Phi(X)\in P_n\quad\Longleftrightarrow\quad + \begin{cases} + t=1,&R=\varnothing,\\ + t\xor\displaystyle\bigoplus_{r\in R}r=0,&R\ne\varnothing. + \end{cases} +\end{equation} + +Induct on total counter count. A legal move changes one residue $r$ to a +different residue $s$. If $R=\varnothing$ and $t=1$, changing a residue +$0$ or $1$ either flips the parity or creates a nonzero XOR, so every option +fails this criterion. If $R\ne\varnothing$ and the displayed +XOR is zero, there must be at least two residues at least $2$: one such +residue cannot cancel either $0$ or $1$. A move changes the XOR by +$r\xor s\ne0$, and at least one large residue remains. Hence every option +of a predicted $P$-position is predicted $N$. + +Conversely suppose the position is predicted $N$. If $R=\varnothing$ and +$t=0$, move a residue-one heap to residue zero when one exists. Otherwise +the nonempty position has a positive residue-zero heap; subtracting $m$ +moves it to residue one. Either move makes $t=1$. + +Now suppose at least two residues at least $2$ occur, and write +\[ + w=t\xor\bigoplus_{r\in R}r\ne0. +\] +Choose a component residue $r$ whose binary expansion contains the highest +set bit of $w$ and put $s=r\xor w$. Such a component exists among the +residues contributing to the XOR: it may be a residue-one component when +the highest bit is the low bit, and otherwise it lies in $R$. Then $s0\text{ even};\quad\text{or}\\ + k\text{ even},\ e\text{ odd},\ j=0;\quad\text{or}\\ + k\text{ odd},\ e\text{ odd},\ j=0\text{ or }j\ge3\text{ odd}. + \end{cases} +\end{equation} +For completeness, the induction uses only +\[ + a\to1,\qquad b\to\{1,a\},\qquad c\to\{a,b,b^2\}. +\] +Here is the complete case induction. Put $\epsilon=e\bmod2$. For a listed +$P$-state with $k$ even, either $(\epsilon,j)=(0,\text{positive even})$ +or $(1,0)$; the $a,b,c$ moves land respectively in the complementary +parity classes, and the three $c$ replacements land at +$(1,j),(0,j+1),(0,j+2)$ with $k$ odd. None is listed. For a listed state +with $k$ odd, $\epsilon=1$ and $j=0$ or odd at least $3$; the $a$ move +sets $\epsilon=0$, the $b$ moves make $j$ even, and the $c$ moves land at +$(0,j),(1,j+1),(1,j+2)$ with $k$ even. Again none is listed. + +Conversely, every unlisted nonterminal state has the following listed +option. When $k$ is even: +\begin{center} +\small +\begin{tabular}{ccl} +\toprule +$\epsilon$ & $j$ & move to a $P$-state\\ +\midrule +$0$ & $0$ & $c\to a$ (unless $k=0$, when the position is terminal)\\ +$0$ & $1$ & $b\to a$\\ +$0$ & odd $\ge3$ & $b\to1$\\ +$1$ & positive even & $a\to1$\\ +$1$ & $1$ & $b\to1$\\ +$1$ & odd $\ge3$ & $b\to a$\\ +\bottomrule +\end{tabular} +\end{center} +When $k$ is odd, the witnesses are +\begin{center} +\small +\begin{tabular}{ccl@{\qquad}ccl} +\toprule +$\epsilon$&$j$&move&$\epsilon$&$j$&move\\ +\midrule +$0$&$0$&$c\to a$&$0$&positive even&$c\to b^2$\\ +$0$&odd&$c\to b$&$1$&$1$&$b\to1$\\ +$1$&$2$&$c\to a$&$1$&even $\ge4$&$b\to1$\\ +\bottomrule +\end{tabular} +\end{center} +The omitted odd-$k$ cases are exactly the listed $P$ cases. This proves the +mis\`ere recursion for all multiplicities; no exponent cutoff is used. + +The outcome law also proves the presentation rather than merely suggesting +it. It first factors raw monomials through the sixteen forms +$a^\epsilon b^j c^\eta$, with $\epsilon,\eta\in\{0,1\}$ and +$j\in\{0,1,2,3\}$, using +$a^2=c^2=1$ and $b^4=b^2$. It is invariant under +$b^2\sim ab^3c$. The congruence generated by this relation pairs the eight +forms in the $b^2$-ideal into four two-element orbits and leaves the eight +forms with $j=0,1$ fixed. Thus it has exactly twelve candidate classes. +The resulting quotient has presentation +\begin{equation} + Q_{13}=\langle a,b,c\mid a^2=c^2=1,\ b^4=b^2,\ + b^2=ab^3c\rangle, + \qquad P_{13}=\{a,ac,b^2\}. +\end{equation} +It has the twelve normal forms +\[ + 1,c,b,bc,b^2,b^2c,b^3,b^3c,a,ac,ab,abc. +\] +Reduction is visible from their distinct $P$-translation rows: +\begin{center} +\small +\begin{tabular}{c@{\;}l@{\qquad}c@{\;}l} +\toprule +$1$&$\{b^2,a,ac\}$&$c$&$\{b^2c,a,ac\}$\\ +$b$&$\{b,b^3\}$&$bc$&$\{bc,b^3c\}$\\ +$b^2$&$\{1,b^2,abc\}$&$b^2c$&$\{c,b^2c,ab\}$\\ +$b^3$&$\{b,b^3,ac\}$&$b^3c$&$\{bc,b^3c,a\}$\\ +$a$&$\{1,c,b^3c\}$&$ac$&$\{1,c,b^3\}$\\ +$ab$&$\{b^2c,ab\}$&$abc$&$\{b^2,abc\}$\\ +\bottomrule +\end{tabular} +\end{center} +Here the row at $x$ is $\{z:xz\in P_{13}\}$. All rows differ, so the +bipartite monoid is reduced. Thus the all-multiplicity outcome law proves: + +\begin{proposition}\label{prop:grundy13} +The exact mis\`ere quotient generated by $H_1,\ldots,H_{13}$ is the +twelve-element reduced bipartite monoid in the preceding presentation. +\end{proposition} + +\subsection{The second extension at heap 18} + +The next heaps have values +\[ + x_{14}=b,\quad x_{15}=a,\quad x_{16}=c,\quad x_{17}=b. +\] +Here these assertions are again global, not guesses made inside $Q_{13}$. +With $0$ denoting the terminal game, the split rule gives +\begin{align*} + \opts(H_{14})&=\{c,ab,a,0\},& + \opts(H_{15})&=\{c,b,0\},\\ + \opts(H_{16})&=\{ac,b^2,b,a\},& + \opts(H_{17})&=\{c,ab,a,0\}. +\end{align*} +The reversible-option reductions of Grundy and Smith give respectively +$H_{14}\equiv_-b$, $H_{15}\equiv_-a$, $H_{16}\equiv_-c$, and +$H_{17}\equiv_-b$; these are also the four reductions printed in +\cite[p.~421]{berlekampconwayguy2001}. Heap $18$ has exact option set +$\{0,b,c,bc\}$ and creates a generator $d=H_{18}$. By +Lemma~\ref{lem:global-substitution}, every position generated by +$H_1,\ldots,H_{18}$ is globally equivalent to a count state +$a^e b^j c^k d^r$, and every such count state occurs in that universe. + +The all-multiplicity recursion extends once more: +\begin{equation} + \begin{aligned} + a^e b^j c^k d^r\in P + \quad\Longleftrightarrow\quad + &\bigl(r\text{ even and }a^e b^j c^k\text{ satisfies the}\bigr.\\ + &\qquad\bigl.\text{preceding three-generator law}\bigr)\\ + &\text{or }\bigl(r\text{ odd},\ e\text{ even},\ j=0\bigr). + \end{aligned} +\end{equation} +To check the recursion, use $d\to\{1,b,c,bc\}$. If $r$ is even and the +three-generator state is $P$, the preceding proof handles the $a,b,c$ +moves, while every $d$ move has odd $r$ and either odd $e$ or positive +$j$. If the three-generator state is $N$, use its displayed winning move. +The only case with no $a,b,c$ component is terminal when $r=0$; for positive +even $r$, the move $d\to1$ reaches the odd-$r$ clause. + +If $r$ is odd, a state in the displayed $P$ clause has even $e$ and $j=0$. +An $a$ move makes $e$ odd, a $c$ move makes either $e$ odd or $j$ positive, +and a $d$ move lands, at even $r$, in one of +\[ + (\epsilon,j,k)=(0,0,k),(0,1,k),(0,0,k+1),(0,1,k+1), +\] +all of which fail the three-generator criterion. Conversely an odd-$r$ +$N$-state has a $P$ move in the following exhaustive cases: +\begin{center} +\small +\begin{tabular}{ccl@{\qquad}ccl} +\toprule +$\epsilon$&$j$&move&$\epsilon$&$j$&move\\ +\midrule +$1$&$0$&$a\to1$&$0$&$1$&$b\to1$\\ +$0$&even $\ge2$&$d\to1$ if $k$ even; $d\to c$ if $k$ odd + &$0$&odd $\ge3$&$d\to b$ if $k$ even; $d\to bc$ if $k$ odd\\ +$1$&$1$&$b\to a$ + &$1$&even $\ge2$&$d\to bc$ if $k$ even; $d\to b$ if $k$ odd\\ +$1$&odd $\ge3$&$d\to c$ if $k$ even; $d\to1$ if $k$ odd&&&\\ +\bottomrule +\end{tabular} +\end{center} +Each target is one of the three even-$r$ cases already proved. This +establishes the outcome formula for all $(e,j,k,r)\in\N^4$. + +As before, the formula itself identifies the finite presentation. Before +the last relation there are $32$ forms +$a^\epsilon b^j c^\eta d^\rho$, with +$\epsilon,\eta,\rho\in\{0,1\}$ and $0\le j\le3$, subject to +$a^2=c^2=d^2=1$ and $b^4=b^2$. The relation +$b^2=ab^3c$ acts freely in the sixteen-element $b^2$-ideal, producing eight +pairs, and leaves the sixteen forms with $j=0,1$ fixed. Hence exactly +$24$ candidate forms remain, and the outcome law descends to them. The +quotient is +\begin{equation} + Q_{18}=Q_{13}\times\langle d\mid d^2=1\rangle, + \qquad P_{18}=\{a,ac,b^2,d,cd\}. +\end{equation} +Its $24$ normal forms are the twelve above and their products by $d$. +For an explicit reduction certificate, let $R$ be the ordered set of twelve +base forms, put $P_0=\{a,ac,b^2\}$ and $P_1=\{1,c\}$, and define +\[ + A_q=\{z\in R:qz\in P_0\},\qquad + B_q=\{z\in R:qz\in P_1\}. +\] +The first coordinate $A_q$ is the $Q_{13}$ row already displayed. The +second coordinates are +\begin{center} +\small +\begin{tabular}{c@{\;}c@{\qquad}c@{\;}c@{\qquad}c@{\;}c} +\toprule +$q$&$B_q$&$q$&$B_q$&$q$&$B_q$\\ +\midrule +$1$&$\{1,c\}$&$c$&$\{1,c\}$&$b$&$\varnothing$\\ +$bc$&$\varnothing$&$b^2$&$\varnothing$&$b^2c$&$\varnothing$\\ +$b^3$&$\varnothing$&$b^3c$&$\varnothing$&$a$&$\{a,ac\}$\\ +$ac$&$\{a,ac\}$&$ab$&$\varnothing$&$abc$&$\varnothing$\\ +\bottomrule +\end{tabular} +\end{center} +The translation row of $q$ in $Q_{18}$ is $(A_q,B_q)$, while that of $qd$ +is $(B_q,A_q)$. The twelve displayed pairs and their twelve swaps are all +distinct, so every two normal forms have a separating context. Hence: + +\begin{theorem}[Exact Grundy prefix]\label{thm:grundy18} +The exact mis\`ere quotient generated by $H_1,\ldots,H_{18}$ is the +$24$-element reduced bipartite monoid in the preceding presentation. +\end{theorem} + +The statements concern entire submonoids generated by the indicated heaps; +no multiplicity bound is present. They do not decide whether the full +Grundy quotient is finite. They do show that the apparent period-three +tame pattern fails for an exact, explicit reason at heap $13$, and they give +the first two symbolic extension layers for a future infinite-family or +stabilization argument. + +Only the individual-heap reductions displayed above are global. The +relations $c^2=d^2=1$, $b^4=b^2$, and $b^2=ab^3c$ are conclusions inside +the localized quotient $Q_{18}$; they are not asserted as identities in the +global semigroup of all mis\`ere impartial games. (The relation $a^2=1$ +is global.) + +\subsection{A necessary kernel obstruction to finiteness} + +The split rule and finiteness alone force a nontrivial recurrent algebraic +component. This does not decide finiteness, but it excludes stabilization in +any quotient whose kernel is trivial. + +\begin{theorem}[Finite Grundy quotients have nontrivial kernel] +\label{thm:grundy-kernel} +Suppose the full mis\`ere quotient $Q$ of Grundy's game is finite. Then there +is a heap transition $(c,E)$ with $c^2\in E$. Every two consecutive positive +powers of $c$ are distinct, and the powers of $c$ are eventually periodic +with a period of length at least two. In particular, the kernel of the finite +commutative monoid $Q$ is nontrivial. +\end{theorem} + +\begin{proof} +Color each positive integer $n$ by the heap value $x_n\in Q$. A standard +distinct-summand form of Schur's theorem gives $i