From 1952dabab678fb3489da84ad19f2469bc4581365 Mon Sep 17 00:00:00 2001 From: Cody Gunton Date: Sat, 6 Jun 2026 01:39:18 -0400 Subject: [PATCH 01/14] Migrate CompPoly to Lean/Mathlib v4.30.0 (final) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Toolchain + mathlib require: v4.30.0-rc2 -> v4.30.0. rc2->final source fixes: - Data/Nat/Bitwise: (boddDiv2 _).fst/.snd no longer defeq to bodd/div2 → rw [Nat.boddDiv2_eq] - ToMathlib/MvPolynomial/Equiv: simp-normal-form drift → loosen `simp only` to `simp` - Multivariate/CMvPolynomialEvalLemmas: `convert … using 3` now yields an Iff → drop the spurious `ext` - Data/RingTheory/AlgebraTower: v4.30 `algebraMap`/`Algebra.algebraMap` no longer def-unfold via rw/simp_rw → use `RingHom.algebraMap_toAlgebra` Builds green on v4.30.0. Co-Authored-By: Claude Opus 4.8 (1M context) --- CompPoly/Data/Nat/Bitwise.lean | 4 +-- CompPoly/Data/RingTheory/AlgebraTower.lean | 6 ++-- .../Multivariate/CMvPolynomialEvalLemmas.lean | 9 +++--- CompPoly/ToMathlib/MvPolynomial/Equiv.lean | 2 +- lake-manifest.json | 28 +++++++++---------- lakefile.lean | 2 +- lean-toolchain | 2 +- 7 files changed, 26 insertions(+), 27 deletions(-) diff --git a/CompPoly/Data/Nat/Bitwise.lean b/CompPoly/Data/Nat/Bitwise.lean index d2325dc0..4b751d17 100644 --- a/CompPoly/Data/Nat/Bitwise.lean +++ b/CompPoly/Data/Nat/Bitwise.lean @@ -551,9 +551,9 @@ lemma sum_eq_xor_plus_twice_and (n : Nat) : ∀ m : ℕ, n + m = (n ^^^ m) + 2 * let bm := resDiv2M.fst let m2 := resDiv2M.snd have h_m2 : m2 = Nat.div2 m := by - rfl + show (Nat.boddDiv2 m).snd = Nat.div2 m; rw [Nat.boddDiv2_eq] have h_bm : bm = Nat.bodd m := by - rfl + show (Nat.boddDiv2 m).fst = Nat.bodd m; rw [Nat.boddDiv2_eq] let mVal := Nat.bit bm m2 set nVal := Nat.bit bn n2 set getBitN := bn.toNat diff --git a/CompPoly/Data/RingTheory/AlgebraTower.lean b/CompPoly/Data/RingTheory/AlgebraTower.lean index 4195a7b3..f3216917 100644 --- a/CompPoly/Data/RingTheory/AlgebraTower.lean +++ b/CompPoly/Data/RingTheory/AlgebraTower.lean @@ -61,7 +61,7 @@ instance AlgebraTower.toIsScalarTower (a : AlgebraTower C) {i j k : ι} simp only [map_mul] rw [←RingHom.comp_apply] unfold instIJ instJK instIK AlgebraTower.toAlgebra - simp_rw [algebraMap, Algebra.algebraMap] + simp_rw [RingHom.algebraMap_toAlgebra] have h_assoc := a.coherence' (i:=i) (j:=j) (k:=k) (h1:=h1) (h2:=h2) rw [h_assoc] rw [mul_assoc] @@ -123,8 +123,8 @@ def AlgebraTowerEquiv.toAlgEquivOverLeft (e : AlgebraTowerEquiv A B) (i j : ι) commutes' := fun r => by simp only [RingEquiv.toEquiv_eq_coe, Equiv.toFun_as_coe, EquivLike.coe_coe] unfold instAij instAiBij - rw [algebraMap, algebraMap, Algebra.algebraMap, Algebra.algebraMap,AlgebraTower.toAlgebra, - AlgebraTowerEquiv.toAlgebraOverLeft, AlgebraTowerEquiv.algebraMapRightUp] + simp only [AlgebraTower.toAlgebra, AlgebraTowerEquiv.toAlgebraOverLeft, + AlgebraTowerEquiv.algebraMapRightUp, RingHom.algebraMap_toAlgebra] simp only [RingEquiv.toRingHom_eq_coe, RingHom.coe_comp, RingHom.coe_coe, Function.comp_apply] exact Eq.symm (e.commutesLeft' i j h r) map_mul' := fun x y => by diff --git a/CompPoly/Multivariate/CMvPolynomialEvalLemmas.lean b/CompPoly/Multivariate/CMvPolynomialEvalLemmas.lean index 6593d711..56fd6134 100644 --- a/CompPoly/Multivariate/CMvPolynomialEvalLemmas.lean +++ b/CompPoly/Multivariate/CMvPolynomialEvalLemmas.lean @@ -106,11 +106,10 @@ theorem CMvPolynomial.eval_ext_univariate have hagreeUni : d < (S.filter (fun r ↦ pUni.eval r = qUni.eval r)).card := by convert hagree using 3 - · ext r - rw [show pUni = (CompPoly.CPolynomial.cmvEquiv (R := R)).symm p from rfl, - show qUni = (CompPoly.CPolynomial.cmvEquiv (R := R)).symm q from rfl, - CompPoly.CPolynomial.eval_cmvEquiv_symm, - CompPoly.CPolynomial.eval_cmvEquiv_symm] + rw [show pUni = (CompPoly.CPolynomial.cmvEquiv (R := R)).symm p from rfl, + show qUni = (CompPoly.CPolynomial.cmvEquiv (R := R)).symm q from rfl, + CompPoly.CPolynomial.eval_cmvEquiv_symm, + CompPoly.CPolynomial.eval_cmvEquiv_symm] have hUni : pUni = qUni := CompPoly.CPolynomial.eval_ext (p := pUni) (q := qUni) hdegUni hagreeUni exact (CompPoly.CPolynomial.cmvEquiv (R := R)).symm.injective hUni diff --git a/CompPoly/ToMathlib/MvPolynomial/Equiv.lean b/CompPoly/ToMathlib/MvPolynomial/Equiv.lean index ab4527e9..e92074bb 100644 --- a/CompPoly/ToMathlib/MvPolynomial/Equiv.lean +++ b/CompPoly/ToMathlib/MvPolynomial/Equiv.lean @@ -110,7 +110,7 @@ theorem eval_eq_eval_mv_eval_finSuccEquivNth (s : Fin n → R) (y : R) ((Polynomial.mapAlgHom (aeval s)).comp (finSuccEquivNth R p).toAlgHom) f congr 2 apply MvPolynomial.algHom_ext - simp only [Fin.forall_iff_succAbove p, aeval_X, Fin.insertNth_apply_same, Polynomial.mapAlgHom, + simp [Fin.forall_iff_succAbove p, aeval_X, Fin.insertNth_apply_same, Polynomial.mapAlgHom, AlgHom.toRingHom_eq_coe, coe_aeval_eq_eval, AlgEquiv.toAlgHom_eq_coe, AlgHom.coe_comp, Polynomial.coe_aeval_eq_eval, AlgHom.coe_mk, coe_mapRingHom, AlgHom.coe_coe, comp_apply, finSuccEquivNth_apply, eval₂Hom_X', Polynomial.map_X, Polynomial.eval_X, diff --git a/lake-manifest.json b/lake-manifest.json index 18f01401..b547213f 100644 --- a/lake-manifest.json +++ b/lake-manifest.json @@ -5,17 +5,17 @@ "type": "git", "subDir": null, "scope": "leanprover-community", - "rev": "5450b53e5ddc75d46418fabb605edbf36bd0beb6", + "rev": "c5ea00351c28e24afc9f0f84379aa41082b1188f", "name": "mathlib", "manifestFile": "lake-manifest.json", - "inputRev": "v4.30.0-rc2", + "inputRev": "v4.30.0", "inherited": false, "configFile": "lakefile.lean"}, {"url": "https://github.com/leanprover-community/plausible", "type": "git", "subDir": null, "scope": "leanprover-community", - "rev": "86210d4ad1b08b086d0bd638637a75246523dbb8", + "rev": "a456461b368b71d2accd95234832cd9c174b5437", "name": "plausible", "manifestFile": "lake-manifest.json", "inputRev": "main", @@ -35,7 +35,7 @@ "type": "git", "subDir": null, "scope": "leanprover-community", - "rev": "cdab3938ccabbdb044be6896e251b5814bec932e", + "rev": "515cf9d0c00ece5e661f6de4326a53dedc1e8ea1", "name": "importGraph", "manifestFile": "lake-manifest.json", "inputRev": "main", @@ -45,50 +45,50 @@ "type": "git", "subDir": null, "scope": "leanprover-community", - "rev": "2db6054a44326f8c0230ee0570e2ddb894816511", + "rev": "a84b3e2475d5c5ab979567b1ad8aea21b764bcf8", "name": "proofwidgets", "manifestFile": "lake-manifest.json", - "inputRev": "v0.0.98", + "inputRev": "v0.0.99", "inherited": true, "configFile": "lakefile.lean"}, {"url": "https://github.com/leanprover-community/aesop", "type": "git", "subDir": null, "scope": "leanprover-community", - "rev": "f0c6e183ea26531e82773feb4b73ab6595ca17a5", + "rev": "558915ae105bfd8074e22d597613d1961822adc2", "name": "aesop", "manifestFile": "lake-manifest.json", - "inputRev": "v4.30.0-rc2", + "inputRev": "v4.30.0", "inherited": true, "configFile": "lakefile.toml"}, {"url": "https://github.com/leanprover-community/quote4", "type": "git", "subDir": null, "scope": "leanprover-community", - "rev": "1cc7e819b9b9bc1e87c9edcccb62e0269e00a809", + "rev": "a6e6c34c4ef182f83b219a3a5a385f51f44bdc4c", "name": "Qq", "manifestFile": "lake-manifest.json", - "inputRev": "v4.30.0-rc2", + "inputRev": "v4.30.0", "inherited": true, "configFile": "lakefile.toml"}, {"url": "https://github.com/leanprover-community/batteries", "type": "git", "subDir": null, "scope": "leanprover-community", - "rev": "5c57f3857ba81924a88b2cdf4f062e34ec04ff11", + "rev": "32dc18cde3684679f3c003de608743b57498c56f", "name": "batteries", "manifestFile": "lake-manifest.json", - "inputRev": "v4.30.0-rc2", + "inputRev": "main", "inherited": true, "configFile": "lakefile.toml"}, {"url": "https://github.com/leanprover/lean4-cli", "type": "git", "subDir": null, "scope": "leanprover", - "rev": "13567aed1ac4f12aea9484178e07e51f8c9f7658", + "rev": "6b907cf12b2e445ccb7c24bc208ef04a1f39e84c", "name": "Cli", "manifestFile": "lake-manifest.json", - "inputRev": "v4.30.0-rc2", + "inputRev": "v4.30.0", "inherited": true, "configFile": "lakefile.toml"}], "name": "CompPoly", diff --git a/lakefile.lean b/lakefile.lean index 7e3cefc4..16d958cd 100644 --- a/lakefile.lean +++ b/lakefile.lean @@ -6,7 +6,7 @@ package CompPoly where version := v!"0.1.0" testDriver := "CompPolyTests" -require "leanprover-community" / mathlib @ git "v4.30.0-rc2" +require "leanprover-community" / mathlib @ git "v4.30.0" @[default_target] lean_lib CompPoly diff --git a/lean-toolchain b/lean-toolchain index 635bb953..af9e5d33 100644 --- a/lean-toolchain +++ b/lean-toolchain @@ -1 +1 @@ -leanprover/lean4:v4.30.0-rc2 \ No newline at end of file +leanprover/lean4:v4.30.0 From cfb75a8ef0bcfe80595bc69c561d492e76352baf Mon Sep 17 00:00:00 2001 From: Cody Gunton Date: Sat, 6 Jun 2026 02:42:54 -0400 Subject: [PATCH 02/14] Migrate CompPoly binary-tower files to v4.30.0 final MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v4.30.0 (final) made two broad-reach changes that recur across the binary-tower algebra: - `algebraMap` can no longer be `rw`/`simp_rw`-unfolded via its def. Replace `rw [algebraMap, Algebra.algebraMap]` with `simp only [RingHom.algebraMap_toAlgebra]` after unfolding the instance to its `.toAlgebra` form, or drop the now-redundant unfold where the term is already defeq (Algebra, Split, Concrete/Algebra/Basis). - `Algebra.algebraMap` is now the bundled hom (takes the type), so `Algebra.algebraMap x` (a value) breaks → `algebraMap _ _ x` (Basis, Concrete/Basis). Two intricate `multilinearBasis_apply` conv proofs (Abstract + Concrete Basis) thread the old `←algebraMap` fold / `Algebra.cast` through a long cast-heavy chain; left as documented `sorry` TODOs (not on ArkLib's path). CompPoly builds green (2418 jobs). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../Fields/Binary/Tower/Abstract/Algebra.lean | 1 - .../Fields/Binary/Tower/Abstract/Basis.lean | 34 ++--------------- .../Fields/Binary/Tower/Abstract/Split.lean | 14 +++---- .../Fields/Binary/Tower/Concrete/Algebra.lean | 9 ++--- .../Fields/Binary/Tower/Concrete/Basis.lean | 38 +++---------------- 5 files changed, 18 insertions(+), 78 deletions(-) diff --git a/CompPoly/Fields/Binary/Tower/Abstract/Algebra.lean b/CompPoly/Fields/Binary/Tower/Abstract/Algebra.lean index e9a35b6a..9c35e455 100644 --- a/CompPoly/Fields/Binary/Tower/Abstract/Algebra.lean +++ b/CompPoly/Fields/Binary/Tower/Abstract/Algebra.lean @@ -240,7 +240,6 @@ lemma algebra_adjacent_tower_eq_AdjoinRoot_algebra (k : ℕ) : rw [algebra_adjacent_tower_def] unfold canonicalEmbedding rw [←AdjoinRoot.algebraMap_eq] - rw [algebraMap, Algebra.algebraMap] exact Algebra.algebra_ext (AdjoinRoot.instAlgebra (poly k)).2.toAlgebra (AdjoinRoot.instAlgebra (poly k)) (congrFun rfl) diff --git a/CompPoly/Fields/Binary/Tower/Abstract/Basis.lean b/CompPoly/Fields/Binary/Tower/Abstract/Basis.lean index 3e358a74..37d1c771 100644 --- a/CompPoly/Fields/Binary/Tower/Abstract/Basis.lean +++ b/CompPoly/Fields/Binary/Tower/Abstract/Basis.lean @@ -174,7 +174,7 @@ lemma algebraMap_𝕏_eq_of_index_eq (r k m : ℕ) (h_k_le : k + 1 ≤ r) (h_m_l (h_eq : k = m) : letI := binaryAlgebraTower (l := k + 1) (r := r) (h_le := h_k_le) letI := binaryAlgebraTower (l := m + 1) (r := r) (h_le := h_m_le) - (Algebra.algebraMap (𝕏 k) : BTField r) = (Algebra.algebraMap (𝕏 m) : BTField r) := by + (algebraMap _ _ (𝕏 k) : BTField r) = (algebraMap _ _ (𝕏 m) : BTField r) := by subst h_eq rfl @@ -301,36 +301,8 @@ theorem multilinearBasis_apply (r : ℕ) : ∀ l : ℕ, (h_le : l ≤ r) → ∀ simp_rw [algebraMap.coe_pow] -- rhs simp_rw [algebraMap.coe_prod] -- lhs - unfold Algebra.cast - rw! (castMode:=.all) [←algebraMap] - conv_lhs => - rw [←Fin.prod_congr' (b:=r1-l) (a:=prevDiff) (h:=by omega)] - simp only [Fin.val_cast] - simp (config := { failIfUnchanged := false }) only [algebraMap, instAlgebraSucc] - erw [RingHom.map_pow] - simp (config := { failIfUnchanged := false }) only [←binaryTowerAlgebra_apply_assoc] - ------------------ Equality of bit-based powers of generators ----------------- - --- The outtermost term - have hfinProd_msb := bit_revFinProdFinEquiv_symm_2_pow_succ (n:=prevDiff) - (i:=⟨prevDiff, by omega⟩) (j:=⟨j, by omega⟩) - simp only [lt_self_iff_false, ↓reduceIte, revFinProdFinEquiv_symm_apply] at hfinProd_msb - conv_rhs => simp only [hfinProd_msb, leftDivNat] - --- Inner-prod term: prove equality of the two factors - refine congr_arg₂ (· * ·) ?_ ?_ - · congr 1 - funext i - have hfinProd_lsb := bit_revFinProdFinEquiv_symm_2_pow_succ - (n:=prevDiff) (i:=⟨i, by omega⟩) - (j:=⟨j, by omega⟩) - simp only [Fin.is_lt, ↓reduceIte, revFinProdFinEquiv_symm_apply] at hfinProd_lsb - rw [hfinProd_lsb] - rfl - · have h_exp_eq : (↑j : ℕ) / 2 ^ (r - l - 1) = (↑j : ℕ) / 2 ^ prevDiff := - congr_arg (fun d => (↑j : ℕ) / 2 ^ d) h_prevDiff.symm - refine congr_arg₂ (· ^ ·) - (algebraMap_𝕏_eq_of_index_eq r r1 (l + prevDiff) (by omega) (by omega) - h_r1_eq_l_plus_prevDiff) - h_exp_eq + sorry -- TODO(v4.30 final): rc2->final entangled `algebraMap`/`Algebra.cast` + -- unfold + cast collection in this conv; needs manual rebuild. end MultilinearBasis diff --git a/CompPoly/Fields/Binary/Tower/Abstract/Split.lean b/CompPoly/Fields/Binary/Tower/Abstract/Split.lean index 50934803..8e6946e0 100644 --- a/CompPoly/Fields/Binary/Tower/Abstract/Split.lean +++ b/CompPoly/Fields/Binary/Tower/Abstract/Split.lean @@ -149,10 +149,10 @@ theorem mul_join_via_add_smul (k : ℕ) (h_pos : k > 0) (a₁ a₀ b₁ b₀ : B have h_a₁_b₀_Z_k : (algebraMap (BTField (k - 1)) (BTField k)) b₀ * a₁ • Z k = (a₁ * b₀) • Z k := by - rw [Algebra.smul_def', ←algebraMap, ←mul_assoc, ←map_mul, ←Algebra.smul_def, mul_comm] + rw [Algebra.smul_def', ←mul_assoc, ←map_mul, ←Algebra.smul_def, mul_comm] have h_a₀_b₁_Z_k : (algebraMap (BTField (k - 1)) (BTField k)) a₀ * b₁ • Z k = (a₀ * b₁) • Z k := by - rw [Algebra.smul_def', ←algebraMap, ←mul_assoc, ←map_mul, ←Algebra.smul_def, mul_comm] + rw [Algebra.smul_def', ←mul_assoc, ←map_mul, ←Algebra.smul_def, mul_comm] have h_Z_k_pow_2 : (Z k) ^ 2 = Z (k - 1) • Z k + 1 := by rw [sumZeroIffEq (x:=(Z k)^2) (y:=Z (k - 1) • Z k + 1).mp] rw [←add_assoc] @@ -161,7 +161,6 @@ theorem mul_join_via_add_smul (k : ℕ) (h_pos : k > 0) (a₁ a₀ b₁ b₀ : B rw! (castMode:=.all) [Nat.sub_one_add_one (by omega)] at h simp only [eq_mp_eq_cast] at h convert h - rw [Algebra.algebraMap] conv_lhs => simp only [instAlgebra]; change (towerAlgebraMap (l:=k-1) (r:=k) (h_le:=by omega)) (Z (k - 1)) @@ -307,9 +306,9 @@ lemma split_algebraMap_eq_zero_x {k : ℕ} (h_pos : k > 0) (x : BTField (k - 1)) apply h.mp -- ⊢ mappedVal = join_via_add_smul h_pos 0 x unfold mappedVal - rw [algebraMap, Algebra.algebraMap] - unfold instAlgebra binaryAlgebraTower - rw [AlgebraTower.toAlgebra, AlgebraTower.algebraMap, instAlgebraTowerNatBTField] + unfold instAlgebra binaryAlgebraTower AlgebraTower.toAlgebra + simp only [RingHom.algebraMap_toAlgebra] + rw [AlgebraTower.algebraMap, instAlgebraTowerNatBTField] simp only have h_concrete_embedding_succ_1 := towerAlgebraMap_succ_1 (k:=k-1) rw! (castMode:=.all) [Nat.sub_one_add_one (by omega)] at h_concrete_embedding_succ_1 @@ -327,12 +326,11 @@ lemma split_algebraMap_eq_zero_x {k : ℕ} (h_pos : k > 0) (x : BTField (k - 1)) have h := algebraMap_adjacent_tower_def (l:=k-1) rw! (castMode:=.all) [Nat.sub_one_add_one (by omega)] at h simp only [eqRec_eq_cast] at h - rw [algebraMap, Algebra.algebraMap] at ⊢ h rw! (castMode:=.all) [Nat.sub_one_add_one (by omega)] at h simp only [cast_eq] at h unfold binaryAlgebraTower AlgebraTower.toAlgebra AlgebraTower.algebraMap instAlgebraTowerNatBTField - simp only [] -- normalize eqRec before rewrite + simp only [RingHom.algebraMap_toAlgebra] -- unfold algebraMap (v4.30: no longer rw-unfoldable) -- Both sides reduce to (cast ⋯ (canonicalEmbedding (k-1))) x through different paths erw [h_concrete_embedding_succ_1]; simp only [eqRec_eq_cast] diff --git a/CompPoly/Fields/Binary/Tower/Concrete/Algebra.lean b/CompPoly/Fields/Binary/Tower/Concrete/Algebra.lean index 4baac08e..3eed71aa 100644 --- a/CompPoly/Fields/Binary/Tower/Concrete/Algebra.lean +++ b/CompPoly/Fields/Binary/Tower/Concrete/Algebra.lean @@ -234,9 +234,9 @@ lemma split_algebraMap_eq_zero_x {k : ℕ} (h_pos : k > 0) (x : ConcreteBTField apply h -- ⊢ mappedVal = join h_pos zero x unfold mappedVal - rw [algebraMap, Algebra.algebraMap] - unfold instAlgebra ConcreteBTFieldAlgebra - rw [AlgebraTower.toAlgebra, AlgebraTower.algebraMap, instAlgebraTowerConcreteBTF] + unfold instAlgebra ConcreteBTFieldAlgebra AlgebraTower.toAlgebra + simp only [RingHom.algebraMap_toAlgebra] + rw [AlgebraTower.algebraMap, instAlgebraTowerConcreteBTF] simp only have h_concrete_embedding_succ_1 := concreteTowerAlgebraMap_succ_1 (k:=k-1) rw! (castMode:=.all) [Nat.sub_one_add_one (by omega)] at h_concrete_embedding_succ_1 @@ -439,8 +439,7 @@ lemma aeval_definingPoly_at_Z_succ (k : ℕ) : -- Z_square_mul_form uses instAlgebraLiftConcreteBTField internally rw [Z_square_mul_form (k:=k) (prev:=(getBTFResult (k:=k)))] rw [add_assoc] - rw [algebraMap, Algebra.algebraMap, instAlgebraLiftConcreteBTField] - simp only + simp only [instAlgebraLiftConcreteBTField, RingHom.algebraMap_toAlgebra] -- f uses ConcreteBTFieldAlgebra, it's same as instAlgebraLiftConcreteBTField at step = 1 rw [h_f_is_canonical_embedding, concreteTowerAlgebraMap_succ_1] simp only [canonicalAlgMap]; rw [mul_comm] diff --git a/CompPoly/Fields/Binary/Tower/Concrete/Basis.lean b/CompPoly/Fields/Binary/Tower/Concrete/Basis.lean index 70750465..345e5302 100644 --- a/CompPoly/Fields/Binary/Tower/Concrete/Basis.lean +++ b/CompPoly/Fields/Binary/Tower/Concrete/Basis.lean @@ -136,8 +136,7 @@ def basisSucc (k : ℕ) : Basis (Fin 2) (ConcreteBTField k) (ConcreteBTField (k rw [←h_add_smul] unfold join_via_add_smul simp only [Nat.add_one_sub_one] - rw [algebraMap, Algebra.algebraMap, ConcreteBTFieldAlgebra_def] - simp only + simp only [ConcreteBTFieldAlgebra_def, RingHom.algebraMap_toAlgebra] simp only [generator] rw [add_comm] congr -- .Q.E.D @@ -422,8 +421,8 @@ lemma algebraMap_𝕏_eq_of_index_eq (r k m : ℕ) (h_k_le : k + 1 ≤ r) (h_m_l (h_eq : k = m) : letI := ConcreteBTFieldAlgebra (l := k + 1) (r := r) (h_le := h_k_le) letI := ConcreteBTFieldAlgebra (l := m + 1) (r := r) (h_le := h_m_le) - (Algebra.algebraMap (𝕏 k) : ConcreteBTField r) = - (Algebra.algebraMap (𝕏 m) : ConcreteBTField r) := by + (algebraMap _ _ (𝕏 k) : ConcreteBTField r) = + (algebraMap _ _ (𝕏 m) : ConcreteBTField r) := by subst h_eq rfl @@ -547,35 +546,8 @@ theorem multilinearBasis_apply (r : ℕ) : ∀ l : ℕ, (h_le : l ≤ r) → ∀ simp_rw [algebraMap.coe_pow] simp_rw [algebraMap.coe_prod] - unfold Algebra.cast - rw! (castMode:=.all) [←algebraMap] - conv_lhs => - rw [←Fin.prod_congr' (b:=r1 - l) (a:=prevDiff) (h:=by omega)] - simp only [Fin.val_cast] - simp (config := { failIfUnchanged := false }) only [algebraMap, instAlgebraSucc] - erw [RingHom.map_pow] - simp (config := { failIfUnchanged := false }) only [←ConcreteBTFieldAlgebra_apply_assoc] - ------------------ Equality of bit-based powers of generators ----------------- - have hfinProd_msb := bit_revFinProdFinEquiv_symm_2_pow_succ (n:=prevDiff) - (i:=⟨prevDiff, by omega⟩) (j:=⟨j, by omega⟩) - simp only [lt_self_iff_false, ↓reduceIte, revFinProdFinEquiv_symm_apply] at hfinProd_msb - conv_rhs => simp only [hfinProd_msb, leftDivNat] - --- Inner-prod term: prove equality of the two factors - refine congr_arg₂ (· * ·) ?_ ?_ - · congr 1 - funext i - have hfinProd_lsb := bit_revFinProdFinEquiv_symm_2_pow_succ - (n:=prevDiff) (i:=⟨i, by omega⟩) - (j:=⟨j, by omega⟩) - simp only [Fin.is_lt, ↓reduceIte, revFinProdFinEquiv_symm_apply] at hfinProd_lsb - rw [hfinProd_lsb] - rfl - · have h_exp_eq : (↑j : ℕ) / 2 ^ (r - l - 1) = (↑j : ℕ) / 2 ^ prevDiff := - congr_arg (fun d => (↑j : ℕ) / 2 ^ d) h_prevDiff.symm - refine congr_arg₂ (· ^ ·) - (algebraMap_𝕏_eq_of_index_eq r r1 (l + prevDiff) (by omega) (by omega) - h_r1_eq_l_plus_prevDiff) - h_exp_eq + sorry -- TODO(v4.30 final): rc2->final entangled `algebraMap`/`Algebra.cast` + -- unfold + cast collection in this conv; needs manual rebuild. end ConcreteMultilinearBasis From 8defa8e5447a9faa87ebde59796a67c22bd3b1ac Mon Sep 17 00:00:00 2001 From: Cody Gunton Date: Sat, 6 Jun 2026 14:37:05 -0400 Subject: [PATCH 03/14] Close binary tower basis proofs for Lean 4.30 --- .../Fields/Binary/Tower/Abstract/Algebra.lean | 8 ++++ .../Fields/Binary/Tower/Abstract/Basis.lean | 46 +++++++++++++++++-- .../Fields/Binary/Tower/Concrete/Basis.lean | 39 ++++++++++++++-- 3 files changed, 87 insertions(+), 6 deletions(-) diff --git a/CompPoly/Fields/Binary/Tower/Abstract/Algebra.lean b/CompPoly/Fields/Binary/Tower/Abstract/Algebra.lean index 9c35e455..93e80c27 100644 --- a/CompPoly/Fields/Binary/Tower/Abstract/Algebra.lean +++ b/CompPoly/Fields/Binary/Tower/Abstract/Algebra.lean @@ -224,6 +224,14 @@ lemma algebraMap_adjacent_tower_def (l : ℕ) : rw [binaryTowerAlgebra_def] exact towerAlgebraMap_succ_1 l +lemma coe_eq_algebraMap_adjacent_tower (l : ℕ) (x : BTField l) : + (x : BTField (l + 1)) = (algebraMap (BTField l) (BTField (l + 1))) x := by + rw [algebraMap_adjacent_tower_def] + unfold canonicalEmbedding + simp only [BTField.eq_1, poly.eq_1, Z.eq_1, eq_mp_eq_cast] + erw [cast_eq] + rfl + lemma algebraMap_adjacent_tower_succ_eq_Adjoin_of (k : ℕ) : (algebraMap (BTField k) (BTField (k + 1))) = of (poly k) := by rw [algebraMap_adjacent_tower_def] diff --git a/CompPoly/Fields/Binary/Tower/Abstract/Basis.lean b/CompPoly/Fields/Binary/Tower/Abstract/Basis.lean index 37d1c771..33a64c3a 100644 --- a/CompPoly/Fields/Binary/Tower/Abstract/Basis.lean +++ b/CompPoly/Fields/Binary/Tower/Abstract/Basis.lean @@ -299,10 +299,50 @@ theorem multilinearBasis_apply (r : ℕ) : ∀ l : ℕ, (h_le : l ≤ r) → ∀ rw [Fin.prod_univ_castSucc] -- split the prod of rhs simp only [Fin.val_cast, Fin.val_castSucc, Fin.val_last] - simp_rw [algebraMap.coe_pow] -- rhs simp_rw [algebraMap.coe_prod] -- lhs - sorry -- TODO(v4.30 final): rc2->final entangled `algebraMap`/`Algebra.cast` - -- unfold + cast collection in this conv; needs manual rebuild. + have h_cast_j : 2 ^ (prevDiff + 1) = 2 ^ (r - l) := by + rw [h_r_sub_l] + have h_low_bits (x : Fin prevDiff) : + Nat.getBit x.val (leftModNat (m:=2 ^ prevDiff) (n:=2) + (by exact Nat.two_pow_pos prevDiff) (i:=Fin.cast h2.symm j)).val = + Nat.getBit x.val j.val := by + have hbits := bit_revFinProdFinEquiv_symm_2_pow_succ + (n:=prevDiff) (j:=Fin.cast h_cast_j.symm j) (i:=Fin.castSucc x) + simpa only [Fin.val_castSucc, Fin.val_cast, Fin.is_lt, ↓reduceIte, + revFinProdFinEquiv_symm_apply] using hbits.symm + have h_top_bit : + j.val / 2 ^ (r - l - 1) = Nat.getBit prevDiff j.val := by + have hbits := bit_revFinProdFinEquiv_symm_2_pow_succ + (n:=prevDiff) (j:=Fin.cast h_cast_j.symm j) (i:=Fin.last prevDiff) + simpa only [Fin.val_last, Fin.val_cast, lt_self_iff_false, ↓reduceIte, + revFinProdFinEquiv_symm_apply, leftDivNat, h_prevDiff] + using hbits.symm + rw! (castMode:=.all) [h_r1_eq_l_plus_prevDiff, h_top_bit] + rw! (castMode:=.all) [show l + prevDiff - l = prevDiff by omega] + congr 1 + apply congrArg (fun f : Fin prevDiff → BTField r => + (Finset.univ : Finset (Fin prevDiff)).prod f) + funext x + rw [h_low_bits x] + convert (binaryTowerAlgebra_apply_assoc (l:=l + x.val + 1) (mid:=r1) (r:=r) + (h_l_le_mid:=by omega) (h_mid_le_r:=by omega) + ((𝕏 (l + x.val)) ^ Nat.getBit x.val j.val)).symm using 1 + rw! (castMode:=.all) [show r = r1 + 1 by omega] + convert (coe_eq_algebraMap_adjacent_tower r1 + (((@binaryAlgebraTower (l:=l + x.val + 1) (r:=r1) (h_le:=by omega)).algebraMap) + (𝕏 (l + x.val) ^ Nat.getBit x.val j.val))) using 1 + rw! (castMode:=.all) [←h_r1_eq_l_plus_prevDiff] + rw! (castMode:=.all) [hr] + simp only [eq_mp_eq_cast] + erw [cast_eq] + change (algebraMap (BTField r1) (BTField (r1 + 1))) + (((@binaryAlgebraTower (l:=l + x.val + 1) (r:=r1) (h_le:=by omega)).algebraMap) + (𝕏 (l + x.val) ^ Nat.getBit x.val j.val)) = + (AdjoinRoot.of (poly r1)) + (((@binaryAlgebraTower (l:=l + x.val + 1) (r:=r1) (h_le:=by omega)).algebraMap) + (𝕏 (l + x.val) ^ Nat.getBit x.val j.val)) + rw [algebraMap_adjacent_tower_succ_eq_Adjoin_of] + rfl end MultilinearBasis diff --git a/CompPoly/Fields/Binary/Tower/Concrete/Basis.lean b/CompPoly/Fields/Binary/Tower/Concrete/Basis.lean index 345e5302..7015bcf5 100644 --- a/CompPoly/Fields/Binary/Tower/Concrete/Basis.lean +++ b/CompPoly/Fields/Binary/Tower/Concrete/Basis.lean @@ -544,10 +544,43 @@ theorem multilinearBasis_apply (r : ℕ) : ∀ l : ℕ, (h_le : l ≤ r) → ∀ rw [Fin.prod_univ_castSucc] simp only [Fin.val_cast, Fin.val_castSucc, Fin.val_last] - simp_rw [algebraMap.coe_pow] simp_rw [algebraMap.coe_prod] - sorry -- TODO(v4.30 final): rc2->final entangled `algebraMap`/`Algebra.cast` - -- unfold + cast collection in this conv; needs manual rebuild. + have h_cast_j : 2 ^ (prevDiff + 1) = 2 ^ (r - l) := by + rw [h_r_sub_l] + have h_low_bits (x : Fin prevDiff) : + Nat.getBit x.val (leftModNat (m:=2 ^ prevDiff) (n:=2) + (by exact Nat.two_pow_pos prevDiff) (i:=Fin.cast h2.symm j)).val = + Nat.getBit x.val j.val := by + have hbits := bit_revFinProdFinEquiv_symm_2_pow_succ + (n:=prevDiff) (j:=Fin.cast h_cast_j.symm j) (i:=Fin.castSucc x) + simpa only [Fin.val_castSucc, Fin.val_cast, Fin.is_lt, ↓reduceIte, + revFinProdFinEquiv_symm_apply] using hbits.symm + have h_top_bit : + j.val / 2 ^ (r - l - 1) = Nat.getBit prevDiff j.val := by + have hbits := bit_revFinProdFinEquiv_symm_2_pow_succ + (n:=prevDiff) (j:=Fin.cast h_cast_j.symm j) (i:=Fin.last prevDiff) + simpa only [Fin.val_last, Fin.val_cast, lt_self_iff_false, ↓reduceIte, + revFinProdFinEquiv_symm_apply, leftDivNat, h_prevDiff] + using hbits.symm + rw! (castMode:=.all) [h_r1_eq_l_plus_prevDiff, h_top_bit] + rw! (castMode:=.all) [show l + prevDiff - l = prevDiff by omega] + congr 1 + apply congrArg (fun f : Fin prevDiff → ConcreteBTField r => + (Finset.univ : Finset (Fin prevDiff)).prod f) + funext x + rw [h_low_bits x] + convert (ConcreteBTFieldAlgebra_apply_assoc (l:=l + x.val + 1) (mid:=r1) (r:=r) + (h_l_le_mid:=by omega) (h_mid_le_r:=by omega) + ((𝕏 (l + x.val)) ^ Nat.getBit x.val j.val)).symm using 1 + rw! (castMode:=.all) [show r = r1 + 1 by omega] + rw! (castMode:=.all) [←h_r1_eq_l_plus_prevDiff] + change (algebraMap (ConcreteBTField r1) (ConcreteBTField (r1 + 1))) + (((@ConcreteBTFieldAlgebra (l:=l + x.val + 1) (r:=r1) (h_le:=by omega)).algebraMap) + (𝕏 (l + x.val) ^ Nat.getBit x.val j.val)) = + (algebraMap (ConcreteBTField r1) (ConcreteBTField (r1 + 1))) + (((@ConcreteBTFieldAlgebra (l:=l + x.val + 1) (r:=r1) (h_le:=by omega)).algebraMap) + (𝕏 (l + x.val) ^ Nat.getBit x.val j.val)) + rfl end ConcreteMultilinearBasis From e4e9f0d547176064ddaadfbf7cb3a78d6925292d Mon Sep 17 00:00:00 2001 From: Cody Gunton Date: Sat, 6 Jun 2026 21:26:17 -0400 Subject: [PATCH 04/14] Remove Lean 4.30 tower simp warnings --- CompPoly/Fields/Binary/Tower/Concrete/Algebra.lean | 2 +- CompPoly/Fields/Binary/Tower/Concrete/Basis.lean | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CompPoly/Fields/Binary/Tower/Concrete/Algebra.lean b/CompPoly/Fields/Binary/Tower/Concrete/Algebra.lean index 3eed71aa..f250965a 100644 --- a/CompPoly/Fields/Binary/Tower/Concrete/Algebra.lean +++ b/CompPoly/Fields/Binary/Tower/Concrete/Algebra.lean @@ -439,7 +439,7 @@ lemma aeval_definingPoly_at_Z_succ (k : ℕ) : -- Z_square_mul_form uses instAlgebraLiftConcreteBTField internally rw [Z_square_mul_form (k:=k) (prev:=(getBTFResult (k:=k)))] rw [add_assoc] - simp only [instAlgebraLiftConcreteBTField, RingHom.algebraMap_toAlgebra] + simp only [RingHom.algebraMap_toAlgebra] -- f uses ConcreteBTFieldAlgebra, it's same as instAlgebraLiftConcreteBTField at step = 1 rw [h_f_is_canonical_embedding, concreteTowerAlgebraMap_succ_1] simp only [canonicalAlgMap]; rw [mul_comm] diff --git a/CompPoly/Fields/Binary/Tower/Concrete/Basis.lean b/CompPoly/Fields/Binary/Tower/Concrete/Basis.lean index 7015bcf5..68d560d1 100644 --- a/CompPoly/Fields/Binary/Tower/Concrete/Basis.lean +++ b/CompPoly/Fields/Binary/Tower/Concrete/Basis.lean @@ -136,7 +136,7 @@ def basisSucc (k : ℕ) : Basis (Fin 2) (ConcreteBTField k) (ConcreteBTField (k rw [←h_add_smul] unfold join_via_add_smul simp only [Nat.add_one_sub_one] - simp only [ConcreteBTFieldAlgebra_def, RingHom.algebraMap_toAlgebra] + simp only [RingHom.algebraMap_toAlgebra] simp only [generator] rw [add_comm] congr -- .Q.E.D From 0075973c81f146f6678a81f6e75edf2882f32858 Mon Sep 17 00:00:00 2001 From: Valerii Huhnin Date: Mon, 8 Jun 2026 10:21:33 +0000 Subject: [PATCH 05/14] Add smooth univariate root search --- CompPoly.lean | 15 + CompPoly/Data/Array/Lemmas.lean | 72 ++ CompPoly/Data/List/Lemmas.lean | 13 + CompPoly/Fields/KoalaBear/Basic.lean | 66 +- CompPoly/Univariate/Basic.lean | 36 +- CompPoly/Univariate/BatchEval.lean | 3 +- CompPoly/Univariate/BatchEval/Context.lean | 99 +- .../Univariate/BatchEval/Correctness.lean | 2 +- .../Univariate/BatchEval/SubproductTree.lean | 6 +- CompPoly/Univariate/Context.lean | 109 ++ CompPoly/Univariate/EuclideanAlgorithm.lean | 149 ++- CompPoly/Univariate/ManyEval/Correctness.lean | 25 +- CompPoly/Univariate/Modular.lean | 76 ++ CompPoly/Univariate/NTT/KoalaBear.lean | 49 + CompPoly/Univariate/Raw/Context.lean | 133 ++ CompPoly/Univariate/Raw/Division.lean | 28 +- CompPoly/Univariate/Raw/Modular.lean | 76 ++ CompPoly/Univariate/Roots.lean | 15 + CompPoly/Univariate/Roots/Backend.lean | 56 + CompPoly/Univariate/Roots/Context.lean | 77 ++ CompPoly/Univariate/Roots/Correctness.lean | 1092 +++++++++++++++++ CompPoly/Univariate/Roots/Enumeration.lean | 124 ++ CompPoly/Univariate/Roots/Extraction.lean | 41 + CompPoly/Univariate/Roots/RootProduct.lean | 643 ++++++++++ CompPoly/Univariate/Roots/SmoothSubgroup.lean | 14 + .../Roots/SmoothSubgroup/Basic.lean | 225 ++++ .../Roots/SmoothSubgroup/Correctness.lean | 774 ++++++++++++ CompPoly/Univariate/Roots/Splitter.lean | 57 + CompPoly/Univariate/ToPoly/Core.lean | 6 + CompPoly/Univariate/ToPoly/Impl.lean | 58 + tests/CompPolyTests.lean | 2 + .../Univariate/Roots/Enumeration.lean | 84 ++ .../Univariate/Roots/FiniteField.lean | 87 ++ 33 files changed, 4205 insertions(+), 107 deletions(-) create mode 100644 CompPoly/Univariate/Context.lean create mode 100644 CompPoly/Univariate/Modular.lean create mode 100644 CompPoly/Univariate/Raw/Context.lean create mode 100644 CompPoly/Univariate/Raw/Modular.lean create mode 100644 CompPoly/Univariate/Roots.lean create mode 100644 CompPoly/Univariate/Roots/Backend.lean create mode 100644 CompPoly/Univariate/Roots/Context.lean create mode 100644 CompPoly/Univariate/Roots/Correctness.lean create mode 100644 CompPoly/Univariate/Roots/Enumeration.lean create mode 100644 CompPoly/Univariate/Roots/Extraction.lean create mode 100644 CompPoly/Univariate/Roots/RootProduct.lean create mode 100644 CompPoly/Univariate/Roots/SmoothSubgroup.lean create mode 100644 CompPoly/Univariate/Roots/SmoothSubgroup/Basic.lean create mode 100644 CompPoly/Univariate/Roots/SmoothSubgroup/Correctness.lean create mode 100644 CompPoly/Univariate/Roots/Splitter.lean create mode 100644 tests/CompPolyTests/Univariate/Roots/Enumeration.lean create mode 100644 tests/CompPolyTests/Univariate/Roots/FiniteField.lean diff --git a/CompPoly.lean b/CompPoly.lean index d8c0bff9..aa13c9a6 100644 --- a/CompPoly.lean +++ b/CompPoly.lean @@ -99,6 +99,7 @@ import CompPoly.Univariate.BatchEval.Correctness import CompPoly.Univariate.BatchEval.Naive import CompPoly.Univariate.BatchEval.SubproductTree import CompPoly.Univariate.CMvEquiv +import CompPoly.Univariate.Context import CompPoly.Univariate.Deriv import CompPoly.Univariate.DivisionCorrectness import CompPoly.Univariate.EuclideanAlgorithm @@ -107,6 +108,7 @@ import CompPoly.Univariate.Linear import CompPoly.Univariate.ManyEval import CompPoly.Univariate.ManyEval.Basic import CompPoly.Univariate.ManyEval.Correctness +import CompPoly.Univariate.Modular import CompPoly.Univariate.NTT.BabyBear import CompPoly.Univariate.NTT.Domain import CompPoly.Univariate.NTT.Evaluation @@ -133,10 +135,23 @@ import CompPoly.Univariate.NTTFast.Plan import CompPoly.Univariate.Quotient.Core import CompPoly.Univariate.Quotient.Equiv import CompPoly.Univariate.Raw +import CompPoly.Univariate.Raw.Context import CompPoly.Univariate.Raw.Core import CompPoly.Univariate.Raw.Division +import CompPoly.Univariate.Raw.Modular import CompPoly.Univariate.Raw.Ops import CompPoly.Univariate.Raw.Proofs +import CompPoly.Univariate.Roots +import CompPoly.Univariate.Roots.Backend +import CompPoly.Univariate.Roots.Context +import CompPoly.Univariate.Roots.Correctness +import CompPoly.Univariate.Roots.Enumeration +import CompPoly.Univariate.Roots.Extraction +import CompPoly.Univariate.Roots.RootProduct +import CompPoly.Univariate.Roots.SmoothSubgroup +import CompPoly.Univariate.Roots.SmoothSubgroup.Basic +import CompPoly.Univariate.Roots.SmoothSubgroup.Correctness +import CompPoly.Univariate.Roots.Splitter import CompPoly.Univariate.ToPoly import CompPoly.Univariate.ToPoly.Core import CompPoly.Univariate.ToPoly.Degree diff --git a/CompPoly/Data/Array/Lemmas.lean b/CompPoly/Data/Array/Lemmas.lean index f62f4f62..b7659143 100644 --- a/CompPoly/Data/Array/Lemmas.lean +++ b/CompPoly/Data/Array/Lemmas.lean @@ -17,6 +17,16 @@ namespace Array variable {α : Type*} {unit : α} +/-- Remove duplicates from an array while preserving first occurrences. -/ +def eraseDups [BEq α] (xs : Array α) : Array α := + xs.foldl + (fun out x ↦ + if out.contains x then + out + else + out.push x) + #[] + /-- Checks if an array of elements from a type `R` is a boolean array, i.e., if every element is either `0` or `1`. -/ def isBoolean {R : Type _} [Zero R] [One R] (a : Array R) : Prop := @@ -65,6 +75,68 @@ lemma getElem?_eq_toList {a : Array α} {i : ℕ} : a.toList[i]? = a[i]? := by rw (occs := .pos [2]) [← Array.toArray_toList (xs := a)] rw [List.getElem?_toArray] +/-- `Array.map` and `getD` agree with indexed access on in-bounds indices. -/ +theorem getD_map_of_lt {β : Type*} (xs : Array α) (f : α → β) + (d : β) {i : Nat} (hi : i < xs.size) : + (xs.map f).getD i d = f xs[i] := by + rw [Array.getD_eq_getD_getElem?, Array.getElem?_map, Array.getElem?_eq_getElem hi] + simp + +theorem foldl_zipIdx_eq_foldl_toList_zipIdx {β : Type*} + (f : β → α × Nat → β) (init : β) (a : Array α) : + a.zipIdx.foldl f init = a.toList.zipIdx.foldl f init := by + cases a + simp + +theorem foldl_zipIdx_eq_foldl_toList_zipIdx_size {β : Type*} + (f : β → α × Nat → β) (init : β) (a : Array α) : + Array.foldl f init a.zipIdx 0 a.size = a.toList.zipIdx.foldl f init := by + cases a + simp + +theorem mem_foldl_append_of_mem {β : Type*} + (xs : Array α) (f : α → Array β) {x : α} {y : β} + (hx : x ∈ xs.toList) (hy : y ∈ (f x).toList) : + y ∈ (xs.foldl (fun out x ↦ out ++ f x) #[]).toList := by + cases xs with + | mk data => + simp at hx ⊢ + have haux : ∀ (data : List α) (acc : Array β), + y ∈ acc.toList ∨ (∃ x, x ∈ data ∧ y ∈ (f x).toList) → + y ∈ (data.foldl (fun out x ↦ out ++ f x) acc).toList := by + intro data + induction data with + | nil => + intro acc h + simp at h ⊢ + exact h + | cons z zs ih => + intro acc h + simp only [List.foldl_cons] + apply ih + rcases h with hacc | ⟨x, hx, hyx⟩ + · left + simp [hacc] + · simp only [List.mem_cons] at hx + cases hx with + | inl hxz => + subst x + left + simp [hyx] + | inr hxzs => + right + exact ⟨x, hxzs, hyx⟩ + simpa using haux data #[] (Or.inr ⟨x, hx, hy⟩) + +theorem mem_flatten_map_of_mem {β : Type*} + (xs : Array α) (f : α → Array β) {x : α} {y : β} + (hx : x ∈ xs.toList) (hy : y ∈ (f x).toList) : + y ∈ (xs.map f).flatten := by + cases xs with + | mk data => + simp at hx hy ⊢ + exact ⟨x, hx, hy⟩ + attribute [simp] Array.getElem?_eq_getElem -- @[simp] theorem matchSize_comm (a : Array α) (b : Array α) (unit : α) : diff --git a/CompPoly/Data/List/Lemmas.lean b/CompPoly/Data/List/Lemmas.lean index 25d325a0..9f22c96d 100644 --- a/CompPoly/Data/List/Lemmas.lean +++ b/CompPoly/Data/List/Lemmas.lean @@ -29,6 +29,19 @@ theorem append_getLast_dropLast {α : Type u} (l : List α) (h : l ≠ []) : simp only [cons_append, cons.injEq, true_and] apply ih +theorem foldl_congr_of_mem {α : Type u} {β : Type v} {f g : α → β → α} + (xs : List β) (acc : α) (h : ∀ acc' x, x ∈ xs → f acc' x = g acc' x) : + xs.foldl f acc = xs.foldl g acc := by + induction xs generalizing acc with + | nil => + simp + | cons x xs ih => + simp only [foldl_cons] + rw [h acc x (by simp)] + apply ih + intro acc' y hy + exact h acc' y (by simp [hy]) + theorem foldl_split_outer {α : Type u} {β : Type v} (f : α → β → α) (init : α) (l : List β) (h : l ≠ []): foldl (f:=f) (init:=init) (l) = f (foldl (f:=f) (init:=init) (l.dropLast)) (l.getLast (by omega)) := by diff --git a/CompPoly/Fields/KoalaBear/Basic.lean b/CompPoly/Fields/KoalaBear/Basic.lean index 44288729..e32de202 100644 --- a/CompPoly/Fields/KoalaBear/Basic.lean +++ b/CompPoly/Fields/KoalaBear/Basic.lean @@ -164,7 +164,7 @@ lemma twoAdicity_maximal : ¬ (2 ^ (twoAdicity + 1)) ∣ (fieldSize - 1) := by decide /-- Repeated squaring: `sqChain g n = g ^ (2^n)`. - Does `n` multiplications instead of `2^n`, making it kernel-friendly. -/ + Uses `n` multiplications and avoids expanding the exponent into `2^n` steps. -/ private def sqChain (g : Field) : Nat → Field | 0 => g | n + 1 => let h := sqChain g n; h * h @@ -286,4 +286,68 @@ lemma twoAdicGenerators_order (bits : Fin (twoAdicity + 1)) : (twoAdicGenerators_pow_twoPow_ne_one_of_lt (bits := ⟨n + 1, hb⟩) (m := n) (by simp)) (twoAdicGenerators_pow_twoPow_eq_one ⟨n + 1, hb⟩) +/-- Primitive generator used by the smooth field-root splitter. -/ +def primitiveRoot : Field := (3 : Field) + +set_option maxRecDepth 100000 in +/-- `primitiveRoot ^ 127` is the maximal two-adic generator. -/ +private lemma primitiveRoot_pow_127_eq_twoAdicGenerator : + primitiveRoot ^ 127 = + twoAdicGenerators[(⟨twoAdicity, by omega⟩ : Fin (twoAdicity + 1))] := by + unfold primitiveRoot twoAdicity + decide + +/-- `primitiveRoot ^ 2^twoAdicity` is nontrivial. -/ +private lemma primitiveRoot_pow_twoAdicity_ne_one : + primitiveRoot ^ (2 ^ twoAdicity) ≠ (1 : Field) := by + rw [← sqChain_eq_pow_two_pow] + unfold primitiveRoot twoAdicity + decide + +/-- Prime divisors of `fieldSize - 1` are exactly `2` and `127`. -/ +private lemma prime_dvd_fieldSize_sub_one_cases {p : Nat} + (hp : p.Prime) (hdvd : p ∣ fieldSize - 1) : + p = 2 ∨ p = 127 := by + have hpdvd : p ∣ 2 ^ twoAdicity * 127 := by + rw [← fieldSize_sub_one_factorization] + exact hdvd + rcases hp.dvd_mul.mp hpdvd with h2pow | h127 + · left + exact (Nat.prime_dvd_prime_iff_eq hp Nat.prime_two).mp + (hp.dvd_of_dvd_pow h2pow) + · right + exact (Nat.prime_dvd_prime_iff_eq hp (by decide : Nat.Prime 127)).mp h127 + +/-- The smooth field-root splitter generator has full multiplicative order. -/ +lemma primitiveRoot_order : orderOf primitiveRoot = fieldSize - 1 := by + refine orderOf_eq_of_pow_and_pow_div_prime (n := fieldSize - 1) ?_ ?_ ?_ + · unfold fieldSize + omega + · exact ZMod.pow_card_sub_one_eq_one (a := primitiveRoot) (by + unfold primitiveRoot + decide) + · intro p hp hdvd + rcases prime_dvd_fieldSize_sub_one_cases hp hdvd with rfl | rfl + · rw [fieldSize_sub_one_factorization, twoAdicity] + have hdiv : (2 ^ 24 * 127) / 2 = 127 * 2 ^ 23 := by decide + rw [hdiv, pow_mul, primitiveRoot_pow_127_eq_twoAdicGenerator] + exact twoAdicGenerators_pow_twoPow_ne_one_of_lt + (bits := (⟨twoAdicity, by omega⟩ : Fin (twoAdicity + 1))) (m := 23) + (by simp [twoAdicity]) + · rw [fieldSize_sub_one_factorization] + have hdiv : (2 ^ twoAdicity * 127) / 127 = 2 ^ twoAdicity := + by decide + rw [hdiv] + exact primitiveRoot_pow_twoAdicity_ne_one + +/-- Smooth subgroup refinement schedule for `fieldSize - 1 = 2^24 * 127`. -/ +def smoothRootSchedule : Array Nat := + (Array.replicate twoAdicity 2).push 127 + +/-- The KoalaBear smooth schedule refines the multiplicative group down to singleton cosets. -/ +lemma smoothRootSchedule_fold_eq_one : + smoothRootSchedule.toList.foldl (fun order ell ↦ order / ell) (fieldSize - 1) = 1 := by + unfold smoothRootSchedule fieldSize twoAdicity + decide + end KoalaBear diff --git a/CompPoly/Univariate/Basic.lean b/CompPoly/Univariate/Basic.lean index b1b5d471..93446282 100644 --- a/CompPoly/Univariate/Basic.lean +++ b/CompPoly/Univariate/Basic.lean @@ -173,6 +173,19 @@ instance [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] : Nontrivial (CPolyno @[reducible] def coeff [Zero R] (p : CPolynomial R) (i : ℕ) : R := p.val.coeff i +/-- Build a canonical polynomial from a dense coefficient array. -/ +def ofArray [Zero R] [BEq R] [LawfulBEq R] + (coeffs : Array R) : CPolynomial R := + let raw : CPolynomial.Raw R := coeffs + ⟨raw.trim, Trim.isCanonical_trim raw⟩ + +/-- Coefficients of `ofArray` are exactly the source array entries, with zero default. -/ +theorem coeff_ofArray [Zero R] [BEq R] [LawfulBEq R] + (coeffs : Array R) (i : Nat) : + (CPolynomial.ofArray coeffs).coeff i = coeffs.getD i 0 := by + unfold CPolynomial.coeff CPolynomial.ofArray + rw [CPolynomial.Raw.Trim.coeff_eq_coeff] + /-- The constant polynomial `C r`. -/ def C [Zero R] [BEq R] [LawfulBEq R] (r : R) : CPolynomial R := ⟨(Raw.C r).trim, Trim.isCanonical_trim (Raw.C r)⟩ @@ -181,6 +194,10 @@ def C [Zero R] [BEq R] [LawfulBEq R] (r : R) : CPolynomial R := def X [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] : CPolynomial R := ⟨Raw.X, Trim.isCanonical_of_trim_eq X_canonical⟩ +/-- The monic linear factor `X - C x`. -/ +def linearFactor [Field R] [BEq R] [LawfulBEq R] (x : R) : CPolynomial R := + (C (-x) : CPolynomial R) + (X : CPolynomial R) + /-- Construct a canonical monomial `c * X^n` as a `CPolynomial R`. The result is canonical (no trailing zeros) when `c ≠ 0`. @@ -842,6 +859,21 @@ theorem mod_zero [Field R] [BEq R] [LawfulBEq R] (p : CPolynomial R) : p.mod 0 = instance [Field R] [BEq R] [LawfulBEq R] : Div (CPolynomial R) := ⟨div⟩ instance [Field R] [BEq R] [LawfulBEq R] : Mod (CPolynomial R) := ⟨mod⟩ +/-- Normalize a nonzero polynomial to monic form. The zero polynomial stays zero. -/ +def monicNormalize [Field R] [BEq R] [LawfulBEq R] + (p : CPolynomial R) : CPolynomial R := + CPolynomial.ofArray (Raw.monicNormalize p.val) + +/-- Euclidean gcd with explicit fuel, normalized to a monic result. -/ +def gcdMonicWithFuel [Field R] [BEq R] [LawfulBEq R] : + Nat → CPolynomial R → CPolynomial R → CPolynomial R + | fuel, p, q => CPolynomial.ofArray (Raw.gcdMonicWithFuel fuel p.val q.val) + +/-- Monic Euclidean gcd for canonical univariate polynomials. -/ +def gcdMonic [Field R] [BEq R] [LawfulBEq R] + (p q : CPolynomial R) : CPolynomial R := + CPolynomial.ofArray (Raw.gcdMonic p.val q.val) + end Division end Operations @@ -1047,8 +1079,8 @@ instance [Ring R] [BEq R] [LawfulBEq R] : Sub (CPolynomial R) where /-- Erase the coefficient at index `n` (same as `p` except `coeff n = 0`, then trimmed). - Uses an in-place `Array.setIfInBounds` rather than subtracting a monomial, avoiding - the allocation of a length-`n` monomial array plus the padding/zip passes of `sub`. -/ + Uses an in-place `Array.setIfInBounds` and avoids allocating a length-`n` monomial + array plus the padding/zip passes of `sub`. -/ def erase [Zero R] [BEq R] [LawfulBEq R] (n : ℕ) (p : CPolynomial R) : CPolynomial R := let arr : CPolynomial.Raw R := p.val.setIfInBounds n 0 ⟨arr.trim, Trim.isCanonical_trim arr⟩ diff --git a/CompPoly/Univariate/BatchEval.lean b/CompPoly/Univariate/BatchEval.lean index b79d287d..b07b2cb3 100644 --- a/CompPoly/Univariate/BatchEval.lean +++ b/CompPoly/Univariate/BatchEval.lean @@ -3,10 +3,11 @@ Copyright (c) 2026 CompPoly. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Authors: Valerii Huhnin -/ -import CompPoly.Univariate.BatchEval.Context import CompPoly.Univariate.BatchEval.Naive import CompPoly.Univariate.BatchEval.SubproductTree import CompPoly.Univariate.BatchEval.Correctness +import CompPoly.Univariate.BatchEval.Context +import CompPoly.Univariate.Context /-! # Univariate Batch Evaluation diff --git a/CompPoly/Univariate/BatchEval/Context.lean b/CompPoly/Univariate/BatchEval/Context.lean index 38d083f5..072193c3 100644 --- a/CompPoly/Univariate/BatchEval/Context.lean +++ b/CompPoly/Univariate/BatchEval/Context.lean @@ -3,91 +3,44 @@ Copyright (c) 2026 CompPoly. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Authors: Valerii Huhnin -/ -import CompPoly.Univariate.DivisionCorrectness -import CompPoly.Univariate.NTT.FastMul -import CompPoly.Univariate.NTTFast.Correctness.Pipeline + +import CompPoly.Univariate.BatchEval.Correctness /-! -# Batch Evaluation Contexts +# Batch-Evaluation Contexts -Algorithm dictionaries for univariate batch-evaluation implementations. +Explicit context wrappers for reusable univariate batch-evaluation backends. -/ namespace CompPoly -namespace CPolynomial - -variable {R : Type*} - -/-- Explicit multiplication backend for batch-evaluation algorithms. -/ -structure MulContext (R : Type*) [Semiring R] [BEq R] [LawfulBEq R] where - /-- Multiply two canonical polynomials. -/ - mul : CPolynomial R → CPolynomial R → CPolynomial R - /-- The backend agrees with canonical polynomial multiplication. -/ - mul_eq_mul : ∀ p q, mul p q = p * q - -/-- Explicit remainder backend for algorithms that only need reduction modulo monic divisors. -/ -structure ModContext (R : Type*) [Field R] [BEq R] [LawfulBEq R] where - /-- Reduce the first polynomial modulo the second, assuming the divisor is monic. -/ - modByMonic : CPolynomial R → CPolynomial R → CPolynomial R - /-- The backend agrees with the canonical monic-remainder operation. -/ - modByMonic_eq_modByMonic : ∀ p q, modByMonic p q = CPolynomial.modByMonic p q - -namespace MulContext - -/-- The default multiplication context, backed by canonical `CPolynomial` multiplication. -/ -def naive [Semiring R] [BEq R] [LawfulBEq R] : MulContext R where - mul p q := p * q - mul_eq_mul _ _ := rfl - -/-- -NTT-backed multiplication context with canonical multiplication as a fallback. -The context asks the selector for a domain that fits the current operands. If no -supported domain is available, it falls back to ordinary `CPolynomial` -multiplication. --/ -def ntt [Field R] [BEq R] [LawfulBEq R] - (bestDomainForLength? : (requiredLen : Nat) → - Option (NTT.FittingDomain R requiredLen)) : - MulContext R where - mul := NTT.FastMul.withFallback bestDomainForLength? - mul_eq_mul := NTT.FastMul.withFallback_eq_mul bestDomainForLength? - -/-- -NTTFast-backed multiplication context with canonical multiplication as a fallback. - -The context asks the selector for a domain that fits the current operands. If no -supported domain is available, it falls back to ordinary `CPolynomial` -multiplication. --/ -def nttFast [Field R] [BEq R] [LawfulBEq R] - (bestDomainForLength? : (requiredLen : Nat) → - Option (NTT.FittingDomain R requiredLen)) : - MulContext R where - mul := NTTFast.withFallback bestDomainForLength? - mul_eq_mul := NTTFast.withFallback_eq_mul bestDomainForLength? - -end MulContext +namespace CPolynomial -namespace ModContext +/-- Contract wrapper for batch evaluation backends. -/ +structure BatchEvalContext (R : Type*) [Semiring R] where + evalBatchWith : CPolynomial R → Array R → Array R + correct : ∀ p xs, evalBatchWith p xs = CPolynomial.evalBatch p xs -/-- The default monic-remainder context, backed by `CPolynomial.modByMonic`. -/ -def naive [Field R] [BEq R] [LawfulBEq R] : ModContext R where - modByMonic p q := CPolynomial.modByMonic p q - modByMonic_eq_modByMonic _ _ := rfl +namespace BatchEvalContext -/-- A remainder-only backend for monic remainders. -/ -def remainderOnly [Field R] [BEq R] [LawfulBEq R] : ModContext R where - modByMonic p q := CPolynomial.modByMonicRemainderOnly p q - modByMonic_eq_modByMonic p q := CPolynomial.modByMonicRemainderOnly_eq_modByMonic p q +/-- Horner-backed batch evaluation context. -/ +def horner (R : Type*) [Semiring R] : BatchEvalContext R where + evalBatchWith := CPolynomial.evalBatchHorner + correct := by + intro p xs + exact CPolynomial.evalBatchHorner_eq_evalBatch p xs -/-- A reversal-based monic-remainder backend parameterized by low-product multiplication. -/ -def reversal [Field R] [BEq R] [LawfulBEq R] - (M : Raw.MulLowContext R) : ModContext R where - modByMonic p q := CPolynomial.modByMonicByReversal M p q - modByMonic_eq_modByMonic p q := CPolynomial.modByMonicByReversal_eq_modByMonic M p q +/-- Subproduct-tree-backed batch evaluation context. -/ +def subproduct (R : Type*) [Field R] [BEq R] [LawfulBEq R] + (M : CPolynomial.MulContext R) (D : CPolynomial.ModContext R) : + BatchEvalContext R where + evalBatchWith := CPolynomial.evalBatchSubproduct M D + correct := by + intro p xs + exact CPolynomial.evalBatchSubproduct_eq_evalBatch M D p xs -end ModContext +end BatchEvalContext end CPolynomial + end CompPoly diff --git a/CompPoly/Univariate/BatchEval/Correctness.lean b/CompPoly/Univariate/BatchEval/Correctness.lean index 51267229..c84bc85b 100644 --- a/CompPoly/Univariate/BatchEval/Correctness.lean +++ b/CompPoly/Univariate/BatchEval/Correctness.lean @@ -20,7 +20,7 @@ variable {R : Type*} private theorem eval_linearFactor [Field R] [BEq R] [LawfulBEq R] (x : R) : (linearFactor x).eval x = 0 := by rw [eval_toPoly] - simp [linearFactor, toPoly_sub, X_toPoly, C_toPoly] + simp [linearFactor, toPoly_add, X_toPoly, C_toPoly] private theorem eval_modContext_eq_self_of_eval_eq_zero [Field R] [BEq R] [LawfulBEq R] (D : ModContext R) (p q : CPolynomial R) diff --git a/CompPoly/Univariate/BatchEval/SubproductTree.lean b/CompPoly/Univariate/BatchEval/SubproductTree.lean index 5052ec3f..3db4df70 100644 --- a/CompPoly/Univariate/BatchEval/SubproductTree.lean +++ b/CompPoly/Univariate/BatchEval/SubproductTree.lean @@ -3,8 +3,8 @@ Copyright (c) 2026 CompPoly. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Authors: Valerii Huhnin -/ -import CompPoly.Univariate.BatchEval.Context import CompPoly.Univariate.BatchEval.Naive +import CompPoly.Univariate.Context /-! # Subproduct-Tree Batch Evaluation @@ -18,10 +18,6 @@ namespace CPolynomial variable {R : Type*} -/-- The linear factor `X - C x` used as a subproduct-tree leaf. -/ -def linearFactor [Field R] [BEq R] [LawfulBEq R] (x : R) : CPolynomial R := - X - C x - /-- A subproduct tree stores the product polynomial at every node. -/ inductive SubproductTree (R : Type*) [Zero R] where /-- A leaf for one evaluation point. The polynomial is `X - C x`. -/ diff --git a/CompPoly/Univariate/Context.lean b/CompPoly/Univariate/Context.lean new file mode 100644 index 00000000..028812b1 --- /dev/null +++ b/CompPoly/Univariate/Context.lean @@ -0,0 +1,109 @@ +/- +Copyright (c) 2026 CompPoly. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ +import CompPoly.Univariate.DivisionCorrectness +import CompPoly.Univariate.NTT.FastMul +import CompPoly.Univariate.NTT.FastMulLow +import CompPoly.Univariate.NTTFast.Correctness.Pipeline +import CompPoly.Univariate.NTTFast.FastMulLow +import CompPoly.Univariate.Raw.Context + +/-! +# Univariate Algorithm Contexts + +Algorithm dictionaries for reusable univariate polynomial operations, including +canonical, NTT, and NTTFast-backed implementations. +-/ + +namespace CompPoly +namespace CPolynomial + +variable {R : Type*} + +/-- Explicit multiplication backend for univariate polynomial algorithms. -/ +structure MulContext (R : Type*) [Semiring R] [BEq R] [LawfulBEq R] where + /-- Multiply two canonical polynomials. -/ + mul : CPolynomial R → CPolynomial R → CPolynomial R + /-- The backend agrees with canonical polynomial multiplication. -/ + mul_eq_mul : ∀ p q, mul p q = p * q + +/-- Explicit remainder backend for algorithms that only need reduction modulo monic divisors. -/ +structure ModContext (R : Type*) [Field R] [BEq R] [LawfulBEq R] where + /-- Reduce the first polynomial modulo the second, assuming the divisor is monic. -/ + modByMonic : CPolynomial R → CPolynomial R → CPolynomial R + /-- The backend agrees with the canonical monic-remainder operation. -/ + modByMonic_eq_modByMonic : ∀ p q, modByMonic p q = CPolynomial.modByMonic p q + +namespace MulContext + +/-- The default multiplication context, backed by canonical `CPolynomial` multiplication. -/ +def naive [Semiring R] [BEq R] [LawfulBEq R] : MulContext R where + mul p q := p * q + mul_eq_mul _ _ := rfl + +/-- +NTT-backed multiplication context with canonical multiplication for unsupported lengths. + +The context asks the selector for a domain that fits the current operands. If no +supported domain is available, it uses ordinary `CPolynomial` multiplication. +-/ +def ntt [Field R] [BEq R] [LawfulBEq R] + (bestDomainForLength? : (requiredLen : Nat) → + Option (NTT.FittingDomain R requiredLen)) : + MulContext R where + mul := NTT.FastMul.withFallback bestDomainForLength? + mul_eq_mul := NTT.FastMul.withFallback_eq_mul bestDomainForLength? + +/-- +NTTFast-backed multiplication context with canonical multiplication for unsupported lengths. + +The context asks the selector for a domain that fits the current operands. If no +supported domain is available, it uses ordinary `CPolynomial` multiplication. +-/ +def nttFast [Field R] [BEq R] [LawfulBEq R] + (bestDomainForLength? : (requiredLen : Nat) → + Option (NTT.FittingDomain R requiredLen)) : + MulContext R where + mul := NTTFast.withFallback bestDomainForLength? + mul_eq_mul := NTTFast.withFallback_eq_mul bestDomainForLength? + +end MulContext + +namespace ModContext + +/-- The default monic-remainder context, backed by `CPolynomial.modByMonic`. -/ +def naive [Field R] [BEq R] [LawfulBEq R] : ModContext R where + modByMonic p q := CPolynomial.modByMonic p q + modByMonic_eq_modByMonic _ _ := rfl + +/-- A remainder-only backend for monic remainders. -/ +def remainderOnly [Field R] [BEq R] [LawfulBEq R] : ModContext R where + modByMonic p q := CPolynomial.modByMonicRemainderOnly p q + modByMonic_eq_modByMonic p q := CPolynomial.modByMonicRemainderOnly_eq_modByMonic p q + +/-- A reversal-based monic-remainder backend parameterized by low-product multiplication. -/ +def reversal [Field R] [BEq R] [LawfulBEq R] + (M : Raw.MulLowContext R) : ModContext R where + modByMonic p q := CPolynomial.modByMonicByReversal M p q + modByMonic_eq_modByMonic p q := CPolynomial.modByMonicByReversal_eq_modByMonic M p q + +/-- Monic remainders by reversal, using an NTT low-product backend. -/ +def reversalNtt [Field R] [BEq R] [LawfulBEq R] + (bestDomainForLength? : (requiredLen : Nat) → + Option (NTT.FittingDomain R requiredLen)) : + ModContext R := + reversal (NTT.FastMulLow.withFallback bestDomainForLength?) + +/-- Monic remainders by reversal, using an NTTFast low-product backend. -/ +def reversalNttFast [Field R] [BEq R] [LawfulBEq R] + (bestDomainForLength? : (requiredLen : Nat) → + Option (NTT.FittingDomain R requiredLen)) : + ModContext R := + reversal (NTTFast.FastMulLow.withFallback bestDomainForLength?) + +end ModContext + +end CPolynomial +end CompPoly diff --git a/CompPoly/Univariate/EuclideanAlgorithm.lean b/CompPoly/Univariate/EuclideanAlgorithm.lean index 4a89c633..2844c2a8 100644 --- a/CompPoly/Univariate/EuclideanAlgorithm.lean +++ b/CompPoly/Univariate/EuclideanAlgorithm.lean @@ -1,7 +1,7 @@ /- Copyright (c) 2026 CompPoly. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. -Authors: Juan Conejero +Authors: Juan Conejero, Valerii Huhnin -/ import CompPoly.Univariate.Basic import CompPoly.Univariate.DivisionCorrectness @@ -175,6 +175,24 @@ def normXgcd [Field R] [BEq R] [LawfulBEq R] let c := res.1.leadingCoeff⁻¹ (c • res.1, c • res.2.1, c • res.2.2) +/-- The normalized extended-gcd output satisfies the Bezout identity. -/ +theorem normXgcd_bezout [Field R] [BEq R] [LawfulBEq R] + (p q : CPolynomial R) (threshold : ℕ) : + Bezout p q (normXgcd p q threshold) := by + unfold normXgcd + have h := xgcd_bezout p q threshold + simp only [Bezout] at h ⊢ + rw [h] + apply toPolyLinearEquiv.injective + change (((p.xgcd q threshold).2.1 * p + (p.xgcd q threshold).2.2 * q).leadingCoeff⁻¹ • + ((p.xgcd q threshold).2.1 * p + (p.xgcd q threshold).2.2 * q)).toPoly = + (((p.xgcd q threshold).2.1 * p + (p.xgcd q threshold).2.2 * q).leadingCoeff⁻¹ • + (p.xgcd q threshold).2.1 * p + + ((p.xgcd q threshold).2.1 * p + (p.xgcd q threshold).2.2 * q).leadingCoeff⁻¹ • + (p.xgcd q threshold).2.2 * q).toPoly + simp only [toPoly_smul, toPoly_add, toPoly_mul, Polynomial.smul_eq_C_mul] + ring + /-- The gcd component of CompPoly's `normXgcd` is the normalization of Mathlib's `EuclideanDomain.gcd` -/ theorem normXgcd_fst_toPoly @@ -187,6 +205,135 @@ theorem normXgcd_fst_toPoly rw [Polynomial.smul_eq_C_mul, normalize_apply, Polynomial.coe_normUnit_of_ne_zero h, _root_.mul_comm] +private theorem Raw.toPoly_smul [Semiring R] [BEq R] [LawfulBEq R] + (c : R) (p : CPolynomial.Raw R) : + (c • p).toPoly = c • p.toPoly := by + ext i + rw [Polynomial.coeff_smul] + rw [CPolynomial.Raw.coeff_toPoly, CPolynomial.Raw.coeff_toPoly] + exact CPolynomial.Raw.smul_coeff c p i + +/-- Monic normalization of computable polynomials agrees with Mathlib normalization. -/ +theorem monicNormalize_toPoly_eq_normalize + [Field R] [BEq R] [LawfulBEq R] [DecidableEq R] + (p : CPolynomial R) : + (CPolynomial.monicNormalize p).toPoly = normalize p.toPoly := by + unfold CPolynomial.monicNormalize CPolynomial.Raw.monicNormalize + rw [ofArray_toPoly, CPolynomial.trim_eq] + by_cases hpraw : ((p.val : CPolynomial.Raw R) == 0) + · have hp : p = 0 := CPolynomial.ext (LawfulBEq.eq_of_beq hpraw) + rw [if_pos hpraw, hp] + rw [CPolynomial.Raw.toPoly_zero, CPolynomial.toPoly_zero, normalize_zero] + · have hp : p ≠ 0 := by + intro hp + exact hpraw (by subst p; rfl) + rw [if_neg hpraw, Raw.toPoly_smul] + have hlead : CPolynomial.Raw.leadingCoeff p.val = p.leadingCoeff := by + simp [CPolynomial.Raw.leadingCoeff, CPolynomial.leadingCoeff, CPolynomial.trim_eq] + rw [hlead] + have hpoly : p.toPoly ≠ 0 := (toPoly_eq_zero_iff p).not.mpr hp + rw [Polynomial.smul_eq_C_mul, normalize_apply, + Polynomial.coe_normUnit_of_ne_zero hpoly, _root_.mul_comm] + rw [CPolynomial.leadingCoeff_toPoly] + change p.toPoly * Polynomial.C p.toPoly.leadingCoeff⁻¹ = + p.toPoly * Polynomial.C p.toPoly.leadingCoeff⁻¹ + rfl + +private theorem gcdMonicWithFuel_toPoly_eq_normalize_gcd + [Field R] [BEq R] [LawfulBEq R] [DecidableEq R] + (fuel : ℕ) (p q : CPolynomial R) + (hfuel : q.toPoly.degree < fuel) : + (CPolynomial.gcdMonicWithFuel fuel p q).toPoly = + normalize (EuclideanDomain.gcd p.toPoly q.toPoly) := by + induction fuel generalizing p q with + | zero => + have hqpoly : q.toPoly = 0 := + Polynomial.degree_eq_bot.mp (Nat.WithBot.lt_zero_iff.mp hfuel) + have hq : q = 0 := (toPoly_eq_zero_iff q).mp hqpoly + subst q + rw [CPolynomial.toPoly_zero] + change (CPolynomial.monicNormalize p).toPoly = + normalize (EuclideanDomain.gcd p.toPoly (0 : Polynomial R)) + rw [monicNormalize_toPoly_eq_normalize, EuclideanDomain.gcd_zero_right] + | succ fuel ih => + by_cases hq : q = 0 + · subst q + simp [CPolynomial.gcdMonicWithFuel, CPolynomial.Raw.gcdMonicWithFuel, + CPolynomial.trim_eq, CPolynomial.toPoly_zero] + have hzero : (↑(0 : CPolynomial R) : CPolynomial.Raw R) = (#[] : CPolynomial.Raw R) := rfl + rw [if_pos hzero] + change (CPolynomial.monicNormalize p).toPoly = normalize p.toPoly + exact monicNormalize_toPoly_eq_normalize p + · have hqraw : ¬((q.val : CPolynomial.Raw R) == 0) := by + intro h + exact hq (CPolynomial.ext (LawfulBEq.eq_of_beq h)) + rw [CPolynomial.gcdMonicWithFuel, CPolynomial.Raw.gcdMonicWithFuel, + CPolynomial.trim_eq, CPolynomial.trim_eq, if_neg hqraw] + change (CPolynomial.gcdMonicWithFuel fuel q (p % q)).toPoly = + normalize (EuclideanDomain.gcd p.toPoly q.toPoly) + rw [ih] + · rw [show (p % q).toPoly = q.leadingCoeff⁻¹ • (p.toPoly % q.toPoly) by + exact mod_toPoly_eq_smul_mod p q] + have hunit : IsUnit (Polynomial.C q.leadingCoeff⁻¹ : Polynomial R) := by + exact Polynomial.isUnit_C.mpr + (isUnit_iff_ne_zero.mpr (inv_ne_zero (CPolynomial.leadingCoeff_ne_zero hq))) + have hassoc : + Associated (q.leadingCoeff⁻¹ • (p.toPoly % q.toPoly)) + (p.toPoly % q.toPoly) := by + simpa [Polynomial.smul_eq_C_mul] using + associated_unit_mul_left (p.toPoly % q.toPoly) + (Polynomial.C q.leadingCoeff⁻¹) hunit + refine normalize_eq_normalize_iff_associated.mpr + (associated_of_dvd_dvd ?_ ?_) + · refine EuclideanDomain.dvd_gcd ?_ (EuclideanDomain.gcd_dvd_left _ _) + exact (EuclideanDomain.dvd_mod_iff (EuclideanDomain.gcd_dvd_left + q.toPoly (q.leadingCoeff⁻¹ • (p.toPoly % q.toPoly)))).mp + ((EuclideanDomain.gcd_dvd_right q.toPoly + (q.leadingCoeff⁻¹ • (p.toPoly % q.toPoly))).trans hassoc.dvd) + · refine EuclideanDomain.dvd_gcd (EuclideanDomain.gcd_dvd_right _ _) ?_ + exact ((EuclideanDomain.dvd_mod_iff + (EuclideanDomain.gcd_dvd_right p.toPoly q.toPoly)).mpr + (EuclideanDomain.gcd_dvd_left p.toPoly q.toPoly)).trans hassoc.symm.dvd + · have hqpoly : q.toPoly ≠ 0 := (toPoly_eq_zero_iff q).not.mpr hq + have hmod : + (p % q).toPoly.degree ≤ (p.toPoly % q.toPoly).degree := by + rw [show (p % q).toPoly = q.leadingCoeff⁻¹ • (p.toPoly % q.toPoly) by + exact mod_toPoly_eq_smul_mod p q] + exact Polynomial.degree_smul_le _ _ + exact lt_of_le_of_lt hmod + (lt_of_lt_of_le (Polynomial.degree_mod_lt _ hqpoly) + (Order.le_of_lt_succ hfuel)) + +/-- The specialized monic gcd has the normalized Mathlib gcd as its `toPoly` +image. -/ +theorem gcdMonic_toPoly_eq_normalize_gcd + [Field R] [BEq R] [LawfulBEq R] [DecidableEq R] + (p q : CPolynomial R) : + (CPolynomial.gcdMonic p q).toPoly = + normalize (EuclideanDomain.gcd p.toPoly q.toPoly) := by + simpa [CPolynomial.gcdMonic, CPolynomial.Raw.gcdMonic] using + gcdMonicWithFuel_toPoly_eq_normalize_gcd + (p.val.size + q.val.size + 1) p q (by + have hqdeg : q.toPoly.degree < q.val.size := by + rw [← degree_toPoly] + exact mem_degreeLT_iff_size_le.mpr le_rfl + have hleNat : q.val.size ≤ p.val.size + q.val.size + 1 := by + omega + have hle : (q.val.size : WithBot ℕ) ≤ + (p.val.size + q.val.size + 1 : ℕ) := + WithBot.coe_le_coe.2 hleNat + exact lt_of_lt_of_le hqdeg hle) + +/-- The specialized monic gcd agrees with the gcd component of normalized +extended gcd. -/ +theorem gcdMonic_eq_normXgcd_fst + [Field R] [BEq R] [LawfulBEq R] [DecidableEq R] + (p q : CPolynomial R) : + CPolynomial.gcdMonic p q = (CPolynomial.normXgcd p q).1 := by + apply toPolyLinearEquiv.injective + change (CPolynomial.gcdMonic p q).toPoly = (CPolynomial.normXgcd p q).1.toPoly + rw [gcdMonic_toPoly_eq_normalize_gcd, normXgcd_fst_toPoly] + /-- The Bezout component of `normXgcd` under `toPoly` is Mathlib's `EuclideanDomain.xgcd` scaled by the inverse leading coefficient of the gcd -/ diff --git a/CompPoly/Univariate/ManyEval/Correctness.lean b/CompPoly/Univariate/ManyEval/Correctness.lean index 31c5a7b9..3317ba59 100644 --- a/CompPoly/Univariate/ManyEval/Correctness.lean +++ b/CompPoly/Univariate/ManyEval/Correctness.lean @@ -4,6 +4,7 @@ Released under Apache 2.0 license as described in the file LICENSE. Authors: Valerii Huhnin -/ import CompPoly.Univariate.ManyEval.Basic +import CompPoly.Data.Array.Lemmas /-! # Many-Polynomial Evaluation Correctness @@ -112,18 +113,6 @@ private lemma powerTable_getD_eq_pow [Semiring R] (x : R) {limit i : Nat} (by simp) (by intro i hi; omega) (by simp) i (by simpa using hi) simpa using h -private lemma list_foldl_congr {α β : Type*} {f g : α → β → α} - (xs : List β) (acc : α) (h : ∀ acc' x, x ∈ xs → f acc' x = g acc' x) : - xs.foldl f acc = xs.foldl g acc := by - induction xs generalizing acc with - | nil => simp - | cons x xs ih => - simp only [List.foldl_cons] - rw [h acc x (by simp)] - apply ih - intro acc' y hy - exact h acc' y (by simp [hy]) - private lemma evalWithPowersLoop_eq_foldl_range [Semiring R] (coeffs powers : Array R) (limit : Nat) (hcoeffs : limit ≤ coeffs.size) (hpowers : limit ≤ powers.size) (i : Nat) (acc : R) : @@ -160,7 +149,7 @@ private lemma foldl_zipIdx_eq_range'_getD_aux [Semiring R] rw [List.range'_succ, List.foldl_cons] simp only [Nat.sub_self, List.getD_cons_zero] rw [ih] - apply list_foldl_congr + apply List.foldl_congr_of_mem intro _acc' j hj have hsub : j - offset = (j - (offset + 1)) + 1 := by have hjmem := List.mem_range'.mp hj @@ -172,12 +161,6 @@ private lemma array_getD_toList {α : Type*} (a : Array α) (i : Nat) (d : α) : cases a simp -private lemma array_foldl_zipIdx_eq_list [Semiring R] (coeffs : Array R) (x acc : R) : - Array.foldl (fun acc ai ↦ acc + ai.1 * x ^ ai.2) acc coeffs.zipIdx 0 coeffs.size = - List.foldl (fun acc ai ↦ acc + ai.1 * x ^ ai.2) acc coeffs.toList.zipIdx := by - cases coeffs - simp - private lemma evalWithPowers_eq_eval [Semiring R] (coeffs powers : Array R) (x : R) (hsize : coeffs.size ≤ powers.size) (hpowers : ∀ i, i < coeffs.size → powers.getD i 0 = x ^ i) : @@ -185,9 +168,9 @@ private lemma evalWithPowers_eq_eval [Semiring R] (coeffs powers : Array R) (x : coeffs.zipIdx.foldl (fun acc ai ↦ acc + ai.1 * x ^ ai.2) 0 := by simp [ManyEval.evalWithPowers, Nat.min_eq_left hsize] rw [evalWithPowersLoop_eq_foldl_range] - rw [array_foldl_zipIdx_eq_list] + rw [Array.foldl_zipIdx_eq_foldl_toList_zipIdx_size] rw [foldl_zipIdx_eq_range'_getD_aux] - apply list_foldl_congr + apply List.foldl_congr_of_mem intro _acc' i hi rw [← array_getD_toList] rw [hpowers i] diff --git a/CompPoly/Univariate/Modular.lean b/CompPoly/Univariate/Modular.lean new file mode 100644 index 00000000..5af28b95 --- /dev/null +++ b/CompPoly/Univariate/Modular.lean @@ -0,0 +1,76 @@ +/- +Copyright (c) 2026 CompPoly Contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ + +import CompPoly.Univariate.Context + +/-! +# Modular Operations on Univariate Polynomials + +Reusable executable modular arithmetic for canonical `CPolynomial`s over public +`CPolynomial.MulContext` and `CPolynomial.ModContext` backends. +-/ + +namespace CompPoly + +namespace CPolynomial + +/-- Multiply modulo a polynomial, treating zero modulus as no reduction. -/ +def mulModWith {F : Type*} [Field F] [BEq F] [LawfulBEq F] + (M : MulContext F) (D : ModContext F) + (modulus p q : CPolynomial F) : CPolynomial F := + let product := M.mul p q + if modulus == 0 then + product + else + D.modByMonic product (monicNormalize modulus) + +/-- Binary modular exponentiation accumulator. -/ +def powModBinaryAuxWith {F : Type*} [Field F] [BEq F] [LawfulBEq F] + (M : MulContext F) (D : ModContext F) + (modulus : CPolynomial F) : + Nat → CPolynomial F → CPolynomial F → CPolynomial F + | 0, acc, _ => acc + | n + 1, acc, current => + let acc' := + if (n + 1) % 2 == 1 then + mulModWith M D modulus acc current + else + acc + let current' := mulModWith M D modulus current current + powModBinaryAuxWith M D modulus ((n + 1) / 2) acc' current' +termination_by exp _ _ => exp +decreasing_by + exact Nat.div_lt_self (Nat.succ_pos n) (by decide) + +/-- Modular exponentiation by repeated squaring. -/ +def powModWith {F : Type*} [Field F] [BEq F] [LawfulBEq F] + (M : MulContext F) (D : ModContext F) + (modulus base : CPolynomial F) (exponent : Nat) : CPolynomial F := + let oneMod := + if modulus == 0 then + (1 : CPolynomial F) + else + D.modByMonic (1 : CPolynomial F) (monicNormalize modulus) + powModBinaryAuxWith M D modulus exponent oneMod base + +/-- `X mod modulus`, with zero modulus treated as no reduction. -/ +def xModWith {F : Type*} [Field F] [BEq F] [LawfulBEq F] + (D : ModContext F) : CPolynomial F → CPolynomial F + | modulus => + if modulus == 0 then + (CPolynomial.X : CPolynomial F) + else + D.modByMonic (CPolynomial.X : CPolynomial F) (monicNormalize modulus) + +/-- `(X^q mod modulus) - (X mod modulus)`, without materializing `X^q - X`. -/ +def xPowSubXModWith {F : Type*} [Field F] [BEq F] [LawfulBEq F] + (M : MulContext F) (D : ModContext F) + (q : Nat) (modulus : CPolynomial F) : CPolynomial F := + powModWith M D modulus (CPolynomial.X : CPolynomial F) q - xModWith D modulus + +end CPolynomial + +end CompPoly diff --git a/CompPoly/Univariate/NTT/KoalaBear.lean b/CompPoly/Univariate/NTT/KoalaBear.lean index a721a202..aeba091b 100644 --- a/CompPoly/Univariate/NTT/KoalaBear.lean +++ b/CompPoly/Univariate/NTT/KoalaBear.lean @@ -49,6 +49,55 @@ def domainOfLogN (logN : Nat) (hlogN : logN ≤ KoalaBear.twoAdicity) : KoalaBear.isPrimitiveRoot_twoAdicGenerator (bitsOfLogN logN hlogN) natCast_ne_zero := twoPowNatCast_ne_zero logN hlogN +/-- KoalaBear NTT domain lookup for dynamic multiplication contexts. -/ +def bestDomainForLength? (requiredLen : Nat) : + Option (FittingDomain KoalaBear.Field requiredLen) := + CPolynomial.NTT.bestDomainForLength? KoalaBear.twoAdicity + domainOfLogN (by intro _ _; rfl) requiredLen + +/-- Fast KoalaBear two-adic generators are primitive roots of the same orders. -/ +theorem fast_isPrimitiveRoot_twoAdicGenerator + (bits : Fin (KoalaBear.twoAdicity + 1)) : + IsPrimitiveRoot (KoalaBear.Fast.ofField KoalaBear.twoAdicGenerators[bits]) + (2 ^ (bits : Nat)) := by + have hbasic : IsPrimitiveRoot KoalaBear.twoAdicGenerators[bits] (2 ^ (bits : Nat)) := + KoalaBear.isPrimitiveRoot_twoAdicGenerator bits + have hfast : + IsPrimitiveRoot (KoalaBear.Fast.ringEquiv.symm KoalaBear.twoAdicGenerators[bits]) + (2 ^ (bits : Nat)) := by + exact hbasic.map_of_injective KoalaBear.Fast.ringEquiv.symm.injective + simpa using hfast + +/-- The fast KoalaBear NTT domain size is nonzero for supported two-adic sizes. -/ +theorem fast_twoPowNatCast_ne_zero + (logN : Nat) (hlogN : logN ≤ KoalaBear.twoAdicity) : + (((2 ^ logN : Nat) : KoalaBear.Fast.Field) ≠ 0) := by + intro hzero + exact twoPowNatCast_ne_zero logN hlogN (by + calc + (((2 ^ logN : Nat) : KoalaBear.Field)) = + KoalaBear.Fast.toField (((2 ^ logN : Nat) : KoalaBear.Fast.Field)) := by + rw [KoalaBear.Fast.toField_natCast] + _ = KoalaBear.Fast.toField 0 := congrArg KoalaBear.Fast.toField hzero + _ = 0 := KoalaBear.Fast.toField_zero) + +/-- Fast KoalaBear radix-2 NTT domain for a supported two-adic size. -/ +def fastDomainOfLogN (logN : Nat) (hlogN : logN ≤ KoalaBear.twoAdicity) : + Domain KoalaBear.Fast.Field where + logN := logN + omega := KoalaBear.Fast.ofField + KoalaBear.twoAdicGenerators[bitsOfLogN logN hlogN] + primitive := by + simpa [bitsOfLogN] using + fast_isPrimitiveRoot_twoAdicGenerator (bitsOfLogN logN hlogN) + natCast_ne_zero := fast_twoPowNatCast_ne_zero logN hlogN + +/-- Fast KoalaBear NTT domain lookup for dynamic multiplication contexts. -/ +def fastBestDomainForLength? (requiredLen : Nat) : + Option (FittingDomain KoalaBear.Fast.Field requiredLen) := + CPolynomial.NTT.bestDomainForLength? KoalaBear.twoAdicity + fastDomainOfLogN (by intro _ _; rfl) requiredLen + end KoalaBear end NTT end CPolynomial diff --git a/CompPoly/Univariate/Raw/Context.lean b/CompPoly/Univariate/Raw/Context.lean new file mode 100644 index 00000000..6f625739 --- /dev/null +++ b/CompPoly/Univariate/Raw/Context.lean @@ -0,0 +1,133 @@ +/- +Copyright (c) 2026 CompPoly. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ +import CompPoly.Univariate.DivisionCorrectness +import CompPoly.Univariate.NTT.FastMul +import CompPoly.Univariate.NTT.FastMulLow +import CompPoly.Univariate.NTTFast.Correctness.Pipeline +import CompPoly.Univariate.NTTFast.FastMulLow + +/-! +# Raw Univariate Algorithm Contexts + +Array-level execution dictionaries for reusable raw univariate polynomial +kernels. +-/ + +namespace CompPoly + +namespace CPolynomial + +namespace Raw + +variable {R : Type*} + +/-- Internal raw multiplication backend for array-level polynomial kernels. -/ +structure MulContext (R : Type*) [Semiring R] [BEq R] [LawfulBEq R] where + /-- Multiply two raw polynomials, returning the trimmed raw product. -/ + mul : CPolynomial.Raw R → CPolynomial.Raw R → CPolynomial.Raw R + /-- The backend agrees with raw polynomial multiplication. -/ + mul_eq_mul : ∀ p q, mul p q = p * q + +/-- Internal raw monic-remainder backend for array-level polynomial kernels. -/ +structure ModContext (R : Type*) [Field R] [BEq R] [LawfulBEq R] where + /-- Reduce the first raw polynomial modulo the second raw monic divisor. -/ + modByMonic : CPolynomial.Raw R → CPolynomial.Raw R → CPolynomial.Raw R + /-- The backend agrees with raw monic remainders for canonical raw inputs. -/ + modByMonic_eq_modByMonic : + ∀ p q, p.trim = p → q.trim = q → modByMonic p q = CPolynomial.Raw.modByMonic p q + +namespace MulContext + +/-- The default raw multiplication context, backed by raw polynomial multiplication. -/ +def naive [Semiring R] [BEq R] [LawfulBEq R] : MulContext R where + mul p q := p * q + mul_eq_mul _ _ := rfl + +/-- NTT-backed raw multiplication context with raw multiplication for unsupported lengths. -/ +def ntt [Field R] [BEq R] [LawfulBEq R] + (bestDomainForLength? : (requiredLen : Nat) → + Option (NTT.FittingDomain R requiredLen)) : + MulContext R where + mul p q := + let requiredLen := NTT.Domain.requiredLength p q + match bestDomainForLength? requiredLen with + | some ⟨D, _⟩ => (NTT.FastMul.Raw.fastMulImpl D p q).trim + | none => p * q + mul_eq_mul p q := by + let requiredLen := NTT.Domain.requiredLength p q + cases hdomain : bestDomainForLength? requiredLen with + | none => + simp [hdomain, requiredLen] + | some fitted => + rcases fitted with ⟨D, hfit⟩ + simp [hdomain, requiredLen, NTT.FastMul.Raw.fastMulImpl_trim_eq_mul D p q (by + simpa [NTT.Domain.fits] using hfit)] + +/-- NTTFast-backed raw multiplication context with raw multiplication for unsupported lengths. -/ +def nttFast [Field R] [BEq R] [LawfulBEq R] + (bestDomainForLength? : (requiredLen : Nat) → + Option (NTT.FittingDomain R requiredLen)) : + MulContext R where + mul p q := + let requiredLen := NTT.Domain.requiredLength p q + match bestDomainForLength? requiredLen with + | some ⟨D, _⟩ => (NTTFast.Raw.fastMulImpl D p q).trim + | none => p * q + mul_eq_mul p q := by + let requiredLen := NTT.Domain.requiredLength p q + cases hdomain : bestDomainForLength? requiredLen with + | none => + simp [hdomain, requiredLen] + | some fitted => + rcases fitted with ⟨D, hfit⟩ + simp [hdomain, requiredLen, NTTFast.Raw.fastMulImpl_trim_eq_mul D p q (by + simpa [NTT.Domain.fits] using hfit)] + +end MulContext + +namespace ModContext + +/-- The default raw monic-remainder context, backed by raw `modByMonic`. -/ +def naive [Field R] [BEq R] [LawfulBEq R] : ModContext R where + modByMonic p q := CPolynomial.Raw.modByMonic p q + modByMonic_eq_modByMonic _ _ _ _ := rfl + +/-- Raw remainder-only monic-remainder context. -/ +def remainderOnly [Field R] [BEq R] [LawfulBEq R] : ModContext R where + modByMonic p q := CPolynomial.Raw.modByMonicRemainderOnly p q + modByMonic_eq_modByMonic p q _ _ := CPolynomial.Raw.modByMonicRemainderOnly_eq_modByMonic p q + +/-- Raw reversal-based monic-remainder context parameterized by low-product multiplication. -/ +def reversal [Field R] [BEq R] [LawfulBEq R] + (M : Raw.MulLowContext R) : ModContext R where + modByMonic p q := CPolynomial.Raw.modByMonicByReversal M p q + modByMonic_eq_modByMonic p q hp hq := by + have h := congrArg Subtype.val + (CPolynomial.modByMonicByReversal_eq_modByMonic M + (CPolynomial.ofArray p) (CPolynomial.ofArray q)) + simpa [CPolynomial.ofArray, hp, hq] using h + +/-- Raw monic remainders by reversal, using an NTT low-product backend. -/ +def reversalNtt [Field R] [BEq R] [LawfulBEq R] + (bestDomainForLength? : (requiredLen : Nat) → + Option (NTT.FittingDomain R requiredLen)) : + ModContext R := + reversal (NTT.FastMulLow.withFallback bestDomainForLength?) + +/-- Raw monic remainders by reversal, using an NTTFast low-product backend. -/ +def reversalNttFast [Field R] [BEq R] [LawfulBEq R] + (bestDomainForLength? : (requiredLen : Nat) → + Option (NTT.FittingDomain R requiredLen)) : + ModContext R := + reversal (NTTFast.FastMulLow.withFallback bestDomainForLength?) + +end ModContext + +end Raw + +end CPolynomial + +end CompPoly diff --git a/CompPoly/Univariate/Raw/Division.lean b/CompPoly/Univariate/Raw/Division.lean index d8fcc46f..53bbb5e1 100644 --- a/CompPoly/Univariate/Raw/Division.lean +++ b/CompPoly/Univariate/Raw/Division.lean @@ -105,8 +105,8 @@ Remainder by a monic polynomial through reversal and truncated products. For canonical monic inputs this computes the quotient from the reversed divisor inverse modulo `X^k`, then subtracts only the low coefficients needed for the -remainder. Inputs outside that executable contract fall back to the -remainder-only implementation. +remainder. Inputs outside the fast-path guard use the simple monic-remainder +implementation. -/ @[inline, specialize] def modByMonicByReversal [Field R] [LawfulBEq R] (M : MulLowContext R) @@ -138,6 +138,30 @@ def mod [Field R] (p q : CPolynomial.Raw R) : CPolynomial.Raw R := instance [Field R] : Div (CPolynomial.Raw R) := ⟨div⟩ instance [Field R] : Mod (CPolynomial.Raw R) := ⟨mod⟩ +/-- Normalize a nonzero raw polynomial to monic form. The zero polynomial stays zero. -/ +def monicNormalize [Field R] (p : CPolynomial.Raw R) : CPolynomial.Raw R := + let p := p.trim + if p == 0 then + 0 + else + p.leadingCoeff⁻¹ • p + +/-- Raw Euclidean gcd with explicit fuel, normalized to a monic result. -/ +def gcdMonicWithFuel [Field R] : + Nat → CPolynomial.Raw R → CPolynomial.Raw R → CPolynomial.Raw R + | 0, p, _ => monicNormalize p + | fuel + 1, p, q => + let p := p.trim + let q := q.trim + if q == 0 then + monicNormalize p + else + gcdMonicWithFuel fuel q (p % q) + +/-- Raw monic Euclidean gcd. -/ +def gcdMonic [Field R] (p q : CPolynomial.Raw R) : CPolynomial.Raw R := + gcdMonicWithFuel (p.size + q.size + 1) p q + end Division end CPolynomial.Raw diff --git a/CompPoly/Univariate/Raw/Modular.lean b/CompPoly/Univariate/Raw/Modular.lean new file mode 100644 index 00000000..2c93b17d --- /dev/null +++ b/CompPoly/Univariate/Raw/Modular.lean @@ -0,0 +1,76 @@ +/- +Copyright (c) 2026 CompPoly Contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ + +import CompPoly.Univariate.Raw.Context + +/-! +# Raw Modular Operations on Univariate Polynomials + +Context-parametric modular multiplication and exponentiation over raw +univariate polynomials. +-/ + +namespace CompPoly + +namespace CPolynomial.Raw + +/-- Raw multiplication modulo a polynomial, treating zero modulus as no reduction. -/ +def mulModWith {F : Type*} [Field F] [BEq F] [LawfulBEq F] + (M : CPolynomial.Raw.MulContext F) (D : CPolynomial.Raw.ModContext F) + (modulus p q : CPolynomial.Raw F) : CPolynomial.Raw F := + let product := M.mul p q + if modulus.trim == 0 then + product + else + D.modByMonic product (monicNormalize modulus) + +/-- Raw binary modular exponentiation accumulator. -/ +def powModBinaryAuxWith {F : Type*} [Field F] [BEq F] [LawfulBEq F] + (M : CPolynomial.Raw.MulContext F) (D : CPolynomial.Raw.ModContext F) + (modulus : CPolynomial.Raw F) : + Nat → CPolynomial.Raw F → CPolynomial.Raw F → CPolynomial.Raw F + | 0, acc, _ => acc + | n + 1, acc, current => + let acc' := + if (n + 1) % 2 == 1 then + mulModWith M D modulus acc current + else + acc + let current' := mulModWith M D modulus current current + powModBinaryAuxWith M D modulus ((n + 1) / 2) acc' current' +termination_by exp _ _ => exp +decreasing_by + exact Nat.div_lt_self (Nat.succ_pos n) (by decide) + +/-- Raw modular exponentiation by repeated squaring. -/ +def powModWith {F : Type*} [Field F] [BEq F] [LawfulBEq F] + (M : CPolynomial.Raw.MulContext F) (D : CPolynomial.Raw.ModContext F) + (modulus base : CPolynomial.Raw F) (exponent : Nat) : CPolynomial.Raw F := + let oneMod := + if modulus.trim == 0 then + (1 : CPolynomial.Raw F) + else + D.modByMonic (1 : CPolynomial.Raw F) (monicNormalize modulus) + powModBinaryAuxWith M D modulus exponent oneMod base + +/-- Raw `X mod modulus`, with zero modulus treated as no reduction. -/ +def xModWith {F : Type*} [Field F] [BEq F] [LawfulBEq F] + (D : CPolynomial.Raw.ModContext F) : CPolynomial.Raw F → CPolynomial.Raw F + | modulus => + if modulus.trim == 0 then + (CPolynomial.Raw.X : CPolynomial.Raw F) + else + D.modByMonic (CPolynomial.Raw.X : CPolynomial.Raw F) (monicNormalize modulus) + +/-- Raw `(X^q mod modulus) - (X mod modulus)`, without materializing `X^q - X`. -/ +def xPowSubXModWith {F : Type*} [Field F] [BEq F] [LawfulBEq F] + (M : CPolynomial.Raw.MulContext F) (D : CPolynomial.Raw.ModContext F) + (q : Nat) (modulus : CPolynomial.Raw F) : CPolynomial.Raw F := + powModWith M D modulus (CPolynomial.Raw.X : CPolynomial.Raw F) q - xModWith D modulus + +end CPolynomial.Raw + +end CompPoly diff --git a/CompPoly/Univariate/Roots.lean b/CompPoly/Univariate/Roots.lean new file mode 100644 index 00000000..761c2d0c --- /dev/null +++ b/CompPoly/Univariate/Roots.lean @@ -0,0 +1,15 @@ +/- +Copyright (c) 2026 CompPoly Contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ + +import CompPoly.Univariate.Roots.Correctness +import CompPoly.Univariate.Roots.Enumeration +import CompPoly.Univariate.Roots.SmoothSubgroup + +/-! +# Univariate Root Finding + +Public univariate root-finding surface. +-/ diff --git a/CompPoly/Univariate/Roots/Backend.lean b/CompPoly/Univariate/Roots/Backend.lean new file mode 100644 index 00000000..3aa98d1f --- /dev/null +++ b/CompPoly/Univariate/Roots/Backend.lean @@ -0,0 +1,56 @@ +/- +Copyright (c) 2026 CompPoly Contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ + +import CompPoly.Univariate.Roots.Extraction +import CompPoly.Univariate.Roots.RootProduct + +/-! +# Finite-Field Root Backend + +Executable field-root extraction over finite fields. The public operation +handles zero, constant, and linear cases explicitly, computes the finite-field +root product modulo the input polynomial, splits the product into linear +factors, then validates and deduplicates candidates against the original input. +-/ + +namespace CompPoly + +namespace CPolynomial + +namespace Roots + +namespace FiniteField + +/-- Executable roots of a univariate polynomial over a finite field. -/ +def rootsInFiniteFieldWith {F : Type*} [Field F] [BEq F] [LawfulBEq F] + (M : CPolynomial.Raw.MulContext F) (D : CPolynomial.Raw.ModContext F) + (ctx : FiniteFieldContext F) (splitter : LinearFactorProductSplitter F) + (p : CPolynomial F) : Array F := + if p == 0 then + #[] + else if p.val.size ≤ 1 then + #[] + else if p.val.size = 2 && !(p.coeff 1 == 0) then + CPolynomial.rootsFromLinearFactors p #[p] + else + let rootProduct := finiteFieldRootProductWith M D ctx p + let factors := splitter.splitLinearFactors ctx.q rootProduct + CPolynomial.rootsFromLinearFactors p factors + +/-- Executable roots using the default raw multiplication and monic-remainder backends. -/ +def rootsInFiniteField {F : Type*} [Field F] [BEq F] [LawfulBEq F] + (ctx : FiniteFieldContext F) (splitter : LinearFactorProductSplitter F) + (p : CPolynomial F) : Array F := + rootsInFiniteFieldWith CPolynomial.Raw.MulContext.naive + CPolynomial.Raw.ModContext.naive ctx splitter p + +end FiniteField + +end Roots + +end CPolynomial + +end CompPoly diff --git a/CompPoly/Univariate/Roots/Context.lean b/CompPoly/Univariate/Roots/Context.lean new file mode 100644 index 00000000..5716c51d --- /dev/null +++ b/CompPoly/Univariate/Roots/Context.lean @@ -0,0 +1,77 @@ +/- +Copyright (c) 2026 CompPoly Contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ + +import CompPoly.Univariate.Basic +import Mathlib.FieldTheory.Finite.Basic + +/-! +# Finite-Field Root Contexts + +Explicit contexts for executable univariate root finding over finite fields. +The algorithms use the cardinality carried here; downstream proofs use the +finite-field and splitter contracts without unfolding concrete implementations. +-/ + +namespace CompPoly + +namespace CPolynomial + +namespace Roots + +namespace FiniteField + +/-- Finite-field facts needed by executable root extraction. -/ +structure FiniteFieldContext (F : Type*) [Field F] where + q : Nat + finite : Finite F + card_eq : Nat.card F = q + frobenius_fixed : ∀ a : F, a ^ q = a + +/-- A polynomial represented as a nonconstant linear factor. -/ +def IsLinearFactor {F : Type*} [Field F] (factor : CPolynomial F) : Prop := + factor.val.size ≤ 2 ∧ factor.coeff 1 ≠ 0 + +/-- A linear factor whose extracted root is `a`. -/ +def IsLinearRootFactorCandidate {F : Type*} [Field F] + (factor : CPolynomial F) (a : F) : Prop := + IsLinearFactor factor ∧ factor.coeff 0 + factor.coeff 1 * a = 0 + +/-- A deterministic splitter for squarefree products of linear factors. + +The executable function consumes the field cardinality and the current factor. +Completeness of the public root backend depends on splitter completeness for +every linear-factor product reached by the recursion. +-/ +structure LinearFactorProductSplitter (F : Type*) [Field F] [BEq F] [LawfulBEq F] where + splitLinearFactors : Nat → CPolynomial F → Array (CPolynomial F) + /-- + The precondition under which `complete` is claimed for the splitter input. + + Root backends should establish this predicate for the field-root product they + pass to the splitter. Executable splitters may remain defensive outside this + predicate, but completeness is only part of the contract under it. + -/ + validInput : Nat → CPolynomial F → Prop := fun _ _ ↦ True + sound : + ∀ q p factor, + factor ∈ (splitLinearFactors q p).toList → + IsLinearFactor factor + complete : + ∀ q p a, + validInput q p → + p ≠ 0 → + CPolynomial.eval a p = 0 → + ∃ factor, + factor ∈ (splitLinearFactors q p).toList ∧ + IsLinearRootFactorCandidate factor a + +end FiniteField + +end Roots + +end CPolynomial + +end CompPoly diff --git a/CompPoly/Univariate/Roots/Correctness.lean b/CompPoly/Univariate/Roots/Correctness.lean new file mode 100644 index 00000000..c6300d77 --- /dev/null +++ b/CompPoly/Univariate/Roots/Correctness.lean @@ -0,0 +1,1092 @@ +/- +Copyright (c) 2026 CompPoly Contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ + +import CompPoly.Univariate.Roots.Backend +import CompPoly.Univariate.Roots.Splitter +import CompPoly.Univariate.EuclideanAlgorithm +import CompPoly.Univariate.ToPoly.Impl + +/-! +# Finite-Field Root Correctness + +Theorem statements and certified context constructors for the executable +finite-field root backend. +-/ + +namespace CompPoly + +namespace CPolynomial + +private lemma mem_eraseDups_fold {α : Type*} [BEq α] [LawfulBEq α] + (a : α) : ∀ (xs : List α) (out : Array α), + a ∈ List.foldl (fun out x ↦ if x ∈ out then out else out.push x) out xs → + a ∈ out ∨ a ∈ xs := by + intro xs + induction xs with + | nil => + intro out h + exact Or.inl h + | cons x xs ih => + intro out h + simp only [List.foldl_cons] at h + by_cases hx : x ∈ out + · have h' := ih out (by simpa [hx] using h) + cases h' with + | inl hout => exact Or.inl hout + | inr hxs => exact Or.inr (by simp [hxs]) + · have h' := ih (out.push x) (by simpa [hx] using h) + cases h' with + | inl hout => + simp at hout + cases hout with + | inl hout => exact Or.inl hout + | inr hax => exact Or.inr (by simp [hax]) + | inr hxs => exact Or.inr (by simp [hxs]) + +private lemma mem_eraseDups {α : Type*} [BEq α] [LawfulBEq α] + {xs : Array α} {a : α} (h : a ∈ xs.eraseDups) : a ∈ xs := by + unfold Array.eraseDups at h + rcases xs with ⟨l⟩ + simp at h ⊢ + have hh := mem_eraseDups_fold a l #[] h + simpa using hh + +private lemma mem_eraseDups_fold_of_mem {α : Type*} [BEq α] [LawfulBEq α] + (a : α) : ∀ (xs : List α) (out : Array α), + a ∈ out ∨ a ∈ xs → + a ∈ List.foldl (fun out x ↦ if x ∈ out then out else out.push x) out xs := by + intro xs + induction xs with + | nil => + intro out h + simpa using h + | cons x xs ih => + intro out h + simp only [List.foldl_cons] + by_cases hx : x ∈ out + · simp [hx] + apply ih out + cases h with + | inl hout => exact Or.inl hout + | inr hmem => + simp at hmem + cases hmem with + | inl hax => exact Or.inl (by simpa [hax] using hx) + | inr hxs => exact Or.inr hxs + · simp [hx] + apply ih (out.push x) + cases h with + | inl hout => exact Or.inl (by simp [hout]) + | inr hmem => + simp at hmem + cases hmem with + | inl hax => exact Or.inl (by simp [hax]) + | inr hxs => exact Or.inr hxs + +private lemma mem_eraseDups_of_mem {α : Type*} [BEq α] [LawfulBEq α] + {xs : Array α} {a : α} (h : a ∈ xs) : a ∈ xs.eraseDups := by + unfold Array.eraseDups + rcases xs with ⟨l⟩ + simp at h ⊢ + exact mem_eraseDups_fold_of_mem a l #[] (Or.inr h) + +private theorem Raw.toPoly_smul {F : Type*} [Field F] [BEq F] [LawfulBEq F] + (c : F) (p : CPolynomial.Raw F) : + (CPolynomial.Raw.smul c p).toPoly = c • p.toPoly := by + ext i + rw [Polynomial.coeff_smul] + rw [CPolynomial.Raw.coeff_toPoly, CPolynomial.Raw.coeff_toPoly] + exact CPolynomial.Raw.smul_coeff c p i + +private theorem Raw.eval_smul {F : Type*} [Field F] [BEq F] [LawfulBEq F] + (c a : F) (p : CPolynomial.Raw F) : + (CPolynomial.Raw.smul c p).eval a = c * p.eval a := by + rw [← CPolynomial.Raw.eval_toPoly_eq_eval, Raw.toPoly_smul] + rw [Polynomial.smul_eq_C_mul, Polynomial.eval_mul, Polynomial.eval_C, + CPolynomial.Raw.eval_toPoly_eq_eval] + +private theorem Raw.eval_mul {F : Type*} [Field F] [BEq F] [LawfulBEq F] + (a : F) (p q : CPolynomial.Raw F) : + (p * q).eval a = p.eval a * q.eval a := by + rw [← CPolynomial.Raw.eval_toPoly_eq_eval, CPolynomial.Raw.toPoly_mul, + Polynomial.eval_mul, CPolynomial.Raw.eval_toPoly_eq_eval, + CPolynomial.Raw.eval_toPoly_eq_eval] + +private theorem Raw.eval_C {F : Type*} [Field F] (a c : F) : + (CPolynomial.Raw.C c).eval a = c := by + rw [← CPolynomial.Raw.eval_toPoly_eq_eval] + simp [CPolynomial.Raw.toPoly_C] + +private theorem Raw.eval_C_smul {F : Type*} [Field F] [BEq F] [LawfulBEq F] + (c a : F) (p : CPolynomial.Raw F) : + (CPolynomial.Raw.C c • p).eval a = c * p.eval a := by + rw [smul_eq_mul, Raw.eval_mul, Raw.eval_C] + +private theorem Raw.eval_monicNormalize_eq_zero_of_eval_eq_zero {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + {p : CPolynomial.Raw F} {a : F} (hp : p.eval a = 0) : + (CPolynomial.Raw.monicNormalize p).eval a = 0 := by + unfold CPolynomial.Raw.monicNormalize + by_cases hzero : p.trim = (#[] : CPolynomial.Raw F) + · simp [hzero, CPolynomial.Raw.eval, CPolynomial.Raw.eval₂] + · simp [hzero] + change CPolynomial.Raw.eval a (CPolynomial.Raw.smul p.trim.leadingCoeff⁻¹ p.trim) = 0 + rw [Raw.eval_smul, CPolynomial.Raw.eval_trim_eq_eval, hp] + simp + +private theorem Raw.eval_mod_eq_zero_of_left_right {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + {p q : CPolynomial.Raw F} {a : F} + (hp : p.eval a = 0) (hq : q.eval a = 0) : + (p % q).eval a = 0 := by + change (CPolynomial.Raw.mod p q).eval a = 0 + unfold CPolynomial.Raw.mod + have hqscaled : (CPolynomial.Raw.C (q.leadingCoeff)⁻¹ * q).eval a = 0 := by + rw [Raw.eval_mul, Raw.eval_C, hq] + simp + have hmod := CPolynomial.Raw.eval_modByMonic_eq_self_of_eval_eq_zero + (CPolynomial.Raw.C (q.leadingCoeff)⁻¹ • p) + (CPolynomial.Raw.C (q.leadingCoeff)⁻¹ * q) hqscaled + rw [hmod, Raw.eval_C_smul, hp] + simp + +private theorem Raw.eval_gcdMonicWithFuel_eq_zero_of_left_right {F : Type*} + [Field F] [BEq F] [LawfulBEq F] {a : F} : + ∀ fuel (p q : CPolynomial.Raw F), + p.eval a = 0 → q.eval a = 0 → + (CPolynomial.Raw.gcdMonicWithFuel fuel p q).eval a = 0 := by + intro fuel + induction fuel with + | zero => + intro p q hp _hq + unfold CPolynomial.Raw.gcdMonicWithFuel + exact Raw.eval_monicNormalize_eq_zero_of_eval_eq_zero hp + | succ fuel ih => + intro p q hp hq + unfold CPolynomial.Raw.gcdMonicWithFuel + by_cases hqzero : q.trim = (#[] : CPolynomial.Raw F) + · simp [hqzero] + exact Raw.eval_monicNormalize_eq_zero_of_eval_eq_zero (by + rw [CPolynomial.Raw.eval_trim_eq_eval] + exact hp) + · simp [hqzero] + apply ih + · rw [CPolynomial.Raw.eval_trim_eq_eval] + exact hq + · exact Raw.eval_mod_eq_zero_of_left_right + (by + rw [CPolynomial.Raw.eval_trim_eq_eval] + exact hp) + (by + rw [CPolynomial.Raw.eval_trim_eq_eval] + exact hq) + +/-- Monic normalization preserves roots of nonzero polynomials. -/ +theorem monicNormalize_root_iff {F : Type*} [Field F] [BEq F] [LawfulBEq F] + {p : CPolynomial F} {a : F} (hp : p ≠ 0) : + CPolynomial.eval a (monicNormalize p) = 0 ↔ CPolynomial.eval a p = 0 := by + unfold monicNormalize CPolynomial.Raw.monicNormalize + have hpempty : ¬ (p.val : CPolynomial.Raw F) = #[] := by + intro h + apply hp + apply CPolynomial.ext + simpa using h + have hsize : 0 < p.val.size := by + cases hs : p.val.size with + | zero => + have hval : p.val = (#[] : CPolynomial.Raw F) := + Array.eq_empty_of_size_eq_zero hs + exact (hpempty hval).elim + | succ _ => omega + have hgetLastD : + (p.val : CPolynomial.Raw F).getLastD 0 = + (p.val : CPolynomial.Raw F).getLast hsize := by + unfold Array.getLastD Array.getLast + simp [hsize] + have hlc : (p.val : CPolynomial.Raw F).getLastD 0 ≠ 0 := by + rw [hgetLastD] + exact p.property hsize + simp [CPolynomial.Raw.leadingCoeff, hpempty] + change + CPolynomial.Raw.eval a + (((Array.getLastD (p.val : CPolynomial.Raw F) 0)⁻¹ • p.val).trim) = + 0 ↔ + CPolynomial.Raw.eval a p.val = 0 + rw [CPolynomial.Raw.eval_trim_eq_eval] + change + CPolynomial.Raw.eval a + (CPolynomial.Raw.smul ((Array.getLastD (p.val : CPolynomial.Raw F) 0)⁻¹) + p.val) = + 0 ↔ + CPolynomial.Raw.eval a p.val = 0 + rw [CPolynomial.Raw.eval_smul] + constructor + · intro h + exact (mul_eq_zero.mp h).resolve_left (inv_ne_zero hlc) + · intro h + simp [h] + +/-- Monic normalization preserves roots, including the zero polynomial case. -/ +theorem monicNormalize_root_of_root {F : Type*} [Field F] [BEq F] [LawfulBEq F] + {p : CPolynomial F} {a : F} (hp : CPolynomial.eval a p = 0) : + CPolynomial.eval a (monicNormalize p) = 0 := by + unfold monicNormalize + change CPolynomial.Raw.eval a (CPolynomial.Raw.monicNormalize p.val).trim = 0 + rw [CPolynomial.Raw.eval_trim_eq_eval] + exact Raw.eval_monicNormalize_eq_zero_of_eval_eq_zero hp + +/-- The normalized polynomial divides the original polynomial. -/ +theorem toPoly_monicNormalize_dvd_self {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (p : CPolynomial F) : + (CPolynomial.monicNormalize p).toPoly ∣ p.toPoly := by + letI : DecidableEq F := instDecidableEqOfLawfulBEq + rw [CPolynomial.monicNormalize_toPoly_eq_normalize] + exact (normalize_associated p.toPoly).dvd + +/-- The monic gcd contains every common root. -/ +theorem gcdMonic_root_of_left_right {F : Type*} [Field F] [BEq F] [LawfulBEq F] + {p q : CPolynomial F} {a : F} + (hp : CPolynomial.eval a p = 0) (hq : CPolynomial.eval a q = 0) : + CPolynomial.eval a (gcdMonic p q) = 0 := by + unfold gcdMonic + change CPolynomial.Raw.eval a (CPolynomial.Raw.gcdMonic p.val q.val).trim = 0 + rw [CPolynomial.Raw.eval_trim_eq_eval] + unfold CPolynomial.Raw.gcdMonic + exact Raw.eval_gcdMonicWithFuel_eq_zero_of_left_right _ p.val q.val hp hq + +/-- The monic gcd divides its left operand. -/ +theorem toPoly_gcdMonic_dvd_left {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (p q : CPolynomial F) : + (CPolynomial.gcdMonic p q).toPoly ∣ p.toPoly := by + letI : DecidableEq F := instDecidableEqOfLawfulBEq + rw [CPolynomial.gcdMonic_toPoly_eq_normalize_gcd] + exact (normalize_associated (EuclideanDomain.gcd p.toPoly q.toPoly)).dvd.trans + (EuclideanDomain.gcd_dvd_left p.toPoly q.toPoly) + +/-- The monic gcd divides its right operand. -/ +theorem toPoly_gcdMonic_dvd_right {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (p q : CPolynomial F) : + (CPolynomial.gcdMonic p q).toPoly ∣ q.toPoly := by + letI : DecidableEq F := instDecidableEqOfLawfulBEq + rw [CPolynomial.gcdMonic_toPoly_eq_normalize_gcd] + exact (normalize_associated (EuclideanDomain.gcd p.toPoly q.toPoly)).dvd.trans + (EuclideanDomain.gcd_dvd_right p.toPoly q.toPoly) + +/-- The monic gcd vanishes exactly at common roots. -/ +theorem gcdMonic_root_iff_left_right {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [DecidableEq F] + {p q : CPolynomial F} {a : F} : + CPolynomial.eval a (gcdMonic p q) = 0 ↔ + CPolynomial.eval a p = 0 ∧ CPolynomial.eval a q = 0 := by + rw [CPolynomial.eval_toPoly, CPolynomial.gcdMonic_toPoly_eq_normalize_gcd] + rw [← Polynomial.IsRoot.def] + rw [normalize_apply] + by_cases h : EuclideanDomain.gcd p.toPoly q.toPoly = 0 + · rw [h] + rw [Polynomial.IsRoot.def] + rw [Polynomial.eval_mul, Polynomial.eval_zero] + ring_nf + have hleft : p.toPoly = 0 := (EuclideanDomain.gcd_eq_zero_iff.mp h).1 + have hright : q.toPoly = 0 := (EuclideanDomain.gcd_eq_zero_iff.mp h).2 + simp only [CPolynomial.eval_toPoly, hleft, hright, Polynomial.eval_zero, and_self] + · rw [Polynomial.coe_normUnit_of_ne_zero h] + rw [Polynomial.IsRoot.def] + rw [Polynomial.eval_mul, Polynomial.eval_C, mul_eq_zero] + have hlead : (EuclideanDomain.gcd p.toPoly q.toPoly).leadingCoeff ≠ 0 := + Polynomial.leadingCoeff_ne_zero.mpr h + have hinv : (EuclideanDomain.gcd p.toPoly q.toPoly).leadingCoeff⁻¹ ≠ 0 := + inv_ne_zero hlead + rw [or_iff_left hinv] + rw [← Polynomial.IsRoot.def] + have hiff := Polynomial.isRoot_gcd_iff_isRoot_left_right + (f := p.toPoly) (g := q.toPoly) (α := a) + simpa [Polynomial.IsRoot, CPolynomial.eval_toPoly] using hiff + +/-- A root of a dividend is a root of the exact quotient when the divisor does +not vanish at that point. -/ +theorem eval_div_eq_zero_of_dvd_of_root_of_ne_root {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + {p q : CPolynomial F} {a : F} + (hdiv : q.toPoly ∣ p.toPoly) + (hp : CPolynomial.eval a p = 0) + (hq : CPolynomial.eval a q ≠ 0) : + CPolynomial.eval a (p / q) = 0 := by + rcases hdiv with ⟨r, hr⟩ + have hqPoly : q.toPoly ≠ 0 := by + intro hqPoly + apply hq + rw [CPolynomial.eval_toPoly, hqPoly, Polynomial.eval_zero] + rw [CPolynomial.eval_toPoly] + change Polynomial.eval a (CPolynomial.div p q).toPoly = 0 + rw [CPolynomial.div_toPoly_eq_div] + have hdivPoly : p.toPoly / q.toPoly = r := by + exact (EuclideanDomain.eq_div_of_mul_eq_right hqPoly hr.symm).symm + rw [hdivPoly] + have hpEval : Polynomial.eval a p.toPoly = 0 := by + simpa [CPolynomial.eval_toPoly] using hp + rw [hr, Polynomial.eval_mul] at hpEval + exact (mul_eq_zero.mp hpEval).resolve_left (by + simpa [CPolynomial.eval_toPoly] using hq) + +/-- Monic normalization of an exact quotient preserves the quotient root from +`eval_div_eq_zero_of_dvd_of_root_of_ne_root`. -/ +theorem monicNormalize_div_root_of_dvd_of_root_of_ne_root {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + {p q : CPolynomial F} {a : F} + (hdiv : q.toPoly ∣ p.toPoly) + (hp : CPolynomial.eval a p = 0) + (hq : CPolynomial.eval a q ≠ 0) : + CPolynomial.eval a (CPolynomial.monicNormalize (p / q)) = 0 := + monicNormalize_root_of_root + (eval_div_eq_zero_of_dvd_of_root_of_ne_root hdiv hp hq) + +/-- The normalized extended gcd contains every common root. -/ +theorem normXgcd_root_of_left_right {F : Type*} [Field F] [BEq F] [LawfulBEq F] + {p q : CPolynomial F} {a : F} + (hp : CPolynomial.eval a p = 0) (hq : CPolynomial.eval a q = 0) : + CPolynomial.eval a (normXgcd p q).1 = 0 := by + have hbez := normXgcd_bezout p q 0 + simp only [Bezout] at hbez + rw [hbez] + rw [CPolynomial.eval_toPoly, CPolynomial.toPoly_add, CPolynomial.toPoly_mul, + CPolynomial.toPoly_mul, Polynomial.eval_add, Polynomial.eval_mul, Polynomial.eval_mul, + ← CPolynomial.eval_toPoly, ← CPolynomial.eval_toPoly, + ← CPolynomial.eval_toPoly, ← CPolynomial.eval_toPoly] + simp [hp, hq] + +/-- The specialized monic gcd and normalized extended gcd have the same root +predicate. -/ +theorem eval_gcdMonic_eq_zero_iff_normXgcd_fst_eq_zero + {F : Type*} [Field F] [BEq F] [LawfulBEq F] [DecidableEq F] + (a : F) (p q : CPolynomial F) : + CPolynomial.eval a (CPolynomial.gcdMonic p q) = 0 ↔ + CPolynomial.eval a ((CPolynomial.normXgcd p q).1) = 0 := by + rw [CPolynomial.gcdMonic_eq_normXgcd_fst] + +/-- The normalized extended-gcd first component contains every common root. -/ +theorem normXgcd_fst_root_of_left_right + {F : Type*} [Field F] [BEq F] [LawfulBEq F] [DecidableEq F] + {a : F} {p q : CPolynomial F} + (hp : CPolynomial.eval a p = 0) + (hq : CPolynomial.eval a q = 0) : + CPolynomial.eval a ((CPolynomial.normXgcd p q).1) = 0 := by + exact normXgcd_root_of_left_right hp hq + +/-- Roots extracted from a linear factor satisfy that factor. -/ +theorem linearRootOfFactor?_sound {F : Type*} [Field F] [BEq F] [LawfulBEq F] + {factor : CPolynomial F} {a : F} + (h : linearRootOfFactor? factor = some a) : + CPolynomial.eval a factor = 0 := by + rcases factor with ⟨⟨xs⟩, hcanon⟩ + cases xs with + | nil => simp [linearRootOfFactor?] at h + | cons x xs => + cases xs with + | nil => simp [linearRootOfFactor?] at h + | cons y xs => + cases xs with + | nil => + simp [linearRootOfFactor?, CPolynomial.eval] at h ⊢ + rcases h with ⟨hy, ha⟩ + rw [← ha] + field_simp [hy] + ring + | cons z xs => simp [linearRootOfFactor?] at h + +/-- Validation makes returned candidates sound for the original polynomial. -/ +theorem mem_validateRootCandidates_eval_eq_zero {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + {p : CPolynomial F} {candidates : Array F} {a : F} + (h : a ∈ (validateRootCandidates p candidates).toList) : + CPolynomial.eval a p = 0 := by + rw [validateRootCandidates] at h + simp at h + simpa [CPolynomial.eval_horner_eq_eval] using h.2 + +private theorem mem_rootsFromLinearFactors_eval_eq_zero {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + {p : CPolynomial F} {factors : Array (CPolynomial F)} {a : F} + (h : a ∈ (rootsFromLinearFactors p factors).toList) : + CPolynomial.eval a p = 0 := by + rw [rootsFromLinearFactors] at h + have h' : + a ∈ (validateRootCandidates p + (List.filterMap linearRootOfFactor? factors.toList).toArray).toList := by + have hm : + a ∈ validateRootCandidates p + (List.filterMap linearRootOfFactor? factors.toList).toArray := + mem_eraseDups (by simpa using h) + simpa using hm + exact mem_validateRootCandidates_eval_eq_zero h' + +namespace Roots + +namespace FiniteField + +private theorem linearRootOfFactor?_eq_some_of_candidate {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + {factor : CPolynomial F} {a : F} + (h : IsLinearRootFactorCandidate factor a) : + CPolynomial.linearRootOfFactor? factor = some a := by + rw [CPolynomial.linearRootOfFactor?] + have hcond : factor.val.size ≤ 2 ∧ factor.coeff 1 ≠ 0 := h.1 + rw [if_pos] + · congr + change -(factor.coeff 0) / factor.coeff 1 = a + apply (div_eq_iff hcond.2).2 + rw [neg_eq_iff_add_eq_zero] + rw [_root_.mul_comm a (factor.coeff 1)] + exact h.2 + · simp [hcond] + intro hbad + exact hcond.2 (by simpa [CPolynomial.coeff, CPolynomial.Raw.coeff] using hbad) + +private theorem mem_rootsFromLinearFactors_of_candidate {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + {p : CPolynomial F} {factors : Array (CPolynomial F)} {factor : CPolynomial F} {a : F} + (hmem : factor ∈ factors.toList) (hcand : IsLinearRootFactorCandidate factor a) + (hroot : CPolynomial.eval a p = 0) : + a ∈ (CPolynomial.rootsFromLinearFactors p factors).toList := by + rw [CPolynomial.rootsFromLinearFactors] + have hvalid : a ∈ CPolynomial.validateRootCandidates p + (List.filterMap CPolynomial.linearRootOfFactor? factors.toList).toArray := by + rw [CPolynomial.validateRootCandidates] + simp [hroot, CPolynomial.eval_horner_eq_eval] + exact ⟨factor, by simpa using hmem, linearRootOfFactor?_eq_some_of_candidate hcand⟩ + have herase := mem_eraseDups_of_mem hvalid + simpa using herase + +private theorem raw_eval_mulMod_naive_eq_mul {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + {modulus p q : CPolynomial.Raw F} {a : F} + (hmod : modulus.eval a = 0) : + (CPolynomial.Raw.mulModWith CPolynomial.Raw.MulContext.naive + CPolynomial.Raw.ModContext.naive modulus p q).eval a = + p.eval a * q.eval a := by + unfold CPolynomial.Raw.mulModWith CPolynomial.Raw.MulContext.naive + CPolynomial.Raw.ModContext.naive + by_cases hzero : modulus.trim = (#[] : CPolynomial.Raw F) + · simp [hzero, CPolynomial.Raw.eval_mul] + · simp [hzero] + have hroot : (CPolynomial.Raw.monicNormalize modulus).eval a = 0 := + CPolynomial.Raw.eval_monicNormalize_eq_zero_of_eval_eq_zero hmod + have hmodBy := CPolynomial.Raw.eval_modByMonic_eq_self_of_eval_eq_zero + (p * q) (CPolynomial.Raw.monicNormalize modulus) hroot + rw [hmodBy, CPolynomial.Raw.eval_mul] + +private theorem raw_eval_powModBinaryAux_naive {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + {modulus acc current : CPolynomial.Raw F} {a : F} + (hmod : modulus.eval a = 0) : + ∀ n, + (CPolynomial.Raw.powModBinaryAuxWith CPolynomial.Raw.MulContext.naive + CPolynomial.Raw.ModContext.naive modulus n acc current).eval a = + acc.eval a * current.eval a ^ n := by + intro n + induction n using Nat.strongRecOn generalizing acc current with + | ind n ih => + cases n with + | zero => + simp [CPolynomial.Raw.powModBinaryAuxWith] + | succ n => + rw [CPolynomial.Raw.powModBinaryAuxWith] + have ih' := ih ((n + 1) / 2) + (Nat.div_lt_self (Nat.succ_pos n) (by decide)) + (acc := + if (n + 1) % 2 == 1 then + CPolynomial.Raw.mulModWith CPolynomial.Raw.MulContext.naive + CPolynomial.Raw.ModContext.naive modulus acc current + else + acc) + (current := + CPolynomial.Raw.mulModWith CPolynomial.Raw.MulContext.naive + CPolynomial.Raw.ModContext.naive modulus current current) + rw [ih'] + rw [raw_eval_mulMod_naive_eq_mul hmod] + by_cases hodd : (n + 1) % 2 == 1 + · simp [hodd, raw_eval_mulMod_naive_eq_mul hmod] + have hoddNat : (n + 1) % 2 = 1 := by + simpa using hodd + have hpow : + (Raw.eval a current * Raw.eval a current) ^ ((n + 1) / 2) = + Raw.eval a current ^ n := by + rw [mul_pow, ← pow_add] + congr 1 + omega + simp [hpow, pow_succ] + ring + · simp [hodd] + have hoddNat : (n + 1) % 2 = 0 := by + simpa using hodd + have hpow : + (Raw.eval a current * Raw.eval a current) ^ ((n + 1) / 2) = + Raw.eval a current ^ (n + 1) := by + rw [mul_pow, ← pow_add] + congr 1 + omega + simp [hpow] + +private theorem raw_eval_one {F : Type*} [Field F] (a : F) : + (1 : CPolynomial.Raw F).eval a = 1 := by + rw [← CPolynomial.Raw.eval_toPoly_eq_eval] + simp + +private theorem raw_eval_X {F : Type*} [Field F] (a : F) : + (CPolynomial.Raw.X : CPolynomial.Raw F).eval a = a := by + rw [← CPolynomial.Raw.eval_toPoly_eq_eval] + simp [CPolynomial.Raw.toPoly_X] + +private theorem raw_eval_powMod_naive_X {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + {modulus : CPolynomial.Raw F} {a : F} (hmod : modulus.eval a = 0) (q : Nat) : + (CPolynomial.Raw.powModWith CPolynomial.Raw.MulContext.naive + CPolynomial.Raw.ModContext.naive modulus CPolynomial.Raw.X q).eval a = + a ^ q := by + unfold CPolynomial.Raw.powModWith + by_cases hzero : modulus.trim = (#[] : CPolynomial.Raw F) + · simp [hzero] + rw [raw_eval_powModBinaryAux_naive hmod, raw_eval_one, raw_eval_X] + simp + · simp [hzero] + have hroot : (CPolynomial.Raw.monicNormalize modulus).eval a = 0 := + CPolynomial.Raw.eval_monicNormalize_eq_zero_of_eval_eq_zero hmod + have hmodBy := CPolynomial.Raw.eval_modByMonic_eq_self_of_eval_eq_zero + (1 : CPolynomial.Raw F) (CPolynomial.Raw.monicNormalize modulus) hroot + rw [raw_eval_powModBinaryAux_naive hmod] + simp [CPolynomial.Raw.ModContext.naive, hmodBy, raw_eval_one, raw_eval_X] + +private theorem raw_eval_xMod_naive {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + {modulus : CPolynomial.Raw F} {a : F} (hmod : modulus.eval a = 0) : + (CPolynomial.Raw.xModWith CPolynomial.Raw.ModContext.naive modulus).eval a = a := by + unfold CPolynomial.Raw.xModWith CPolynomial.Raw.ModContext.naive + by_cases hzero : modulus.trim = (#[] : CPolynomial.Raw F) + · simp [hzero, raw_eval_X] + · simp [hzero] + have hroot : (CPolynomial.Raw.monicNormalize modulus).eval a = 0 := + CPolynomial.Raw.eval_monicNormalize_eq_zero_of_eval_eq_zero hmod + have hmodBy := CPolynomial.Raw.eval_modByMonic_eq_self_of_eval_eq_zero + (CPolynomial.Raw.X : CPolynomial.Raw F) (CPolynomial.Raw.monicNormalize modulus) hroot + rw [hmodBy, raw_eval_X] + +private theorem raw_eval_sub {F : Type*} [Field F] [BEq F] [LawfulBEq F] + (a : F) (p q : CPolynomial.Raw F) : + (p - q).eval a = p.eval a - q.eval a := by + rw [← CPolynomial.Raw.eval_toPoly_eq_eval, CPolynomial.Raw.toPoly_sub, + Polynomial.eval_sub, CPolynomial.Raw.eval_toPoly_eq_eval, + CPolynomial.Raw.eval_toPoly_eq_eval] + +private theorem raw_eval_xPowSubXMod_naive {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (ctx : FiniteFieldContext F) {modulus : CPolynomial.Raw F} {a : F} + (hmod : modulus.eval a = 0) : + (CPolynomial.Raw.xPowSubXModWith CPolynomial.Raw.MulContext.naive + CPolynomial.Raw.ModContext.naive ctx.q modulus).eval a = 0 := by + unfold CPolynomial.Raw.xPowSubXModWith + rw [raw_eval_sub, raw_eval_powMod_naive_X hmod, raw_eval_xMod_naive hmod, + ctx.frobenius_fixed a] + simp + +private theorem raw_monicNormalize_trim {F : Type*} [Field F] [BEq F] [LawfulBEq F] + (p : CPolynomial.Raw F) : + (CPolynomial.Raw.monicNormalize p).trim = CPolynomial.Raw.monicNormalize p := by + unfold CPolynomial.Raw.monicNormalize + let q := p.trim + by_cases hzero : q == (0 : CPolynomial.Raw F) + · have hzeroEq : q = 0 := by + simpa using hzero + have hpzeroEq : p.trim = (#[] : CPolynomial.Raw F) := by + simpa [q] using hzeroEq + simp [hpzeroEq, CPolynomial.Raw.Trim.canonical_empty] + · have hzeroNe : q ≠ (0 : CPolynomial.Raw F) := by + intro hq + exact hzero (by simp [hq]) + have hpzeroNe : ¬p.trim = (#[] : CPolynomial.Raw F) := by + intro hp + exact hzeroNe (by simpa [q] using hp) + simp [hpzeroNe] + change (CPolynomial.Raw.mk + (Array.map (fun r ↦ (p.trim).leadingCoeff⁻¹ * r) p.trim)).trim = + CPolynomial.Raw.mk (Array.map (fun r ↦ (p.trim).leadingCoeff⁻¹ * r) p.trim) + apply CPolynomial.Raw.Trim.non_zero_map (fun r ↦ q.leadingCoeff⁻¹ * r) + · intro r hr + apply mul_eq_zero.mp at hr + rcases hr with hinv | hr + · have hlead0 : q.leadingCoeff = 0 := by + exact inv_eq_zero.mp hinv + have hcanon : q.trim = q := by + simpa [q] using CPolynomial.Raw.Trim.trim_twice p + have hcrit := (CPolynomial.Raw.Trim.trim_eq_iff_size_eq_zero_or_getLastD_ne_zero + (p := q)).mp hcanon + rcases hcrit with hsize | hlast + · have hqempty : q = (#[] : CPolynomial.Raw F) := by + apply Array.eq_empty_of_size_eq_zero + exact hsize + exact False.elim (hzero (by simp [hqempty])) + · unfold CPolynomial.Raw.leadingCoeff at hlead0 + rw [hcanon] at hlead0 + exact (hlast hlead0).elim + · exact hr + · simpa [q] using CPolynomial.Raw.Trim.trim_twice p + +private theorem raw_eval_mulModWith_eq_mul {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (M : CPolynomial.Raw.MulContext F) (D : CPolynomial.Raw.ModContext F) + {modulus p q : CPolynomial.Raw F} {a : F} + (hmod : modulus.eval a = 0) : + (CPolynomial.Raw.mulModWith M D modulus p q).eval a = + p.eval a * q.eval a := by + unfold CPolynomial.Raw.mulModWith + by_cases hzero : modulus.trim = (#[] : CPolynomial.Raw F) + · simp [hzero, M.mul_eq_mul, CPolynomial.Raw.eval_mul] + · simp [hzero] + have hproductTrim : (M.mul p q).trim = M.mul p q := by + rw [M.mul_eq_mul] + exact CPolynomial.Raw.mul_is_trimmed p q + rw [D.modByMonic_eq_modByMonic _ _ hproductTrim (raw_monicNormalize_trim modulus)] + have hroot : (CPolynomial.Raw.monicNormalize modulus).eval a = 0 := + CPolynomial.Raw.eval_monicNormalize_eq_zero_of_eval_eq_zero hmod + have hmodBy := CPolynomial.Raw.eval_modByMonic_eq_self_of_eval_eq_zero + (M.mul p q) (CPolynomial.Raw.monicNormalize modulus) hroot + rw [hmodBy, M.mul_eq_mul, CPolynomial.Raw.eval_mul] + +private theorem raw_eval_powModBinaryAuxWith {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (M : CPolynomial.Raw.MulContext F) (D : CPolynomial.Raw.ModContext F) + {modulus acc current : CPolynomial.Raw F} {a : F} + (hmod : modulus.eval a = 0) : + ∀ n, + (CPolynomial.Raw.powModBinaryAuxWith M D modulus n acc current).eval a = + acc.eval a * current.eval a ^ n := by + intro n + induction n using Nat.strongRecOn generalizing acc current with + | ind n ih => + cases n with + | zero => + simp [CPolynomial.Raw.powModBinaryAuxWith] + | succ n => + rw [CPolynomial.Raw.powModBinaryAuxWith] + have ih' := ih ((n + 1) / 2) + (Nat.div_lt_self (Nat.succ_pos n) (by decide)) + (acc := + if (n + 1) % 2 == 1 then + CPolynomial.Raw.mulModWith M D modulus acc current + else + acc) + (current := CPolynomial.Raw.mulModWith M D modulus current current) + rw [ih'] + rw [raw_eval_mulModWith_eq_mul M D hmod] + by_cases hodd : (n + 1) % 2 == 1 + · simp [hodd, raw_eval_mulModWith_eq_mul M D hmod] + have hoddNat : (n + 1) % 2 = 1 := by + simpa using hodd + have hpow : + (Raw.eval a current * Raw.eval a current) ^ ((n + 1) / 2) = + Raw.eval a current ^ n := by + rw [mul_pow, ← pow_add] + congr 1 + omega + simp [hpow, pow_succ] + ring + · simp [hodd] + have hoddNat : (n + 1) % 2 = 0 := by + simpa using hodd + have hpow : + (Raw.eval a current * Raw.eval a current) ^ ((n + 1) / 2) = + Raw.eval a current ^ (n + 1) := by + rw [mul_pow, ← pow_add] + congr 1 + omega + simp [hpow] + +private theorem raw_eval_powModWith_X {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (M : CPolynomial.Raw.MulContext F) (D : CPolynomial.Raw.ModContext F) + {modulus : CPolynomial.Raw F} {a : F} (hmod : modulus.eval a = 0) (q : Nat) : + (CPolynomial.Raw.powModWith M D modulus CPolynomial.Raw.X q).eval a = + a ^ q := by + unfold CPolynomial.Raw.powModWith + by_cases hzero : modulus.trim = (#[] : CPolynomial.Raw F) + · simp [hzero] + rw [raw_eval_powModBinaryAuxWith M D hmod, raw_eval_one, raw_eval_X] + simp + · simp [hzero] + have hroot : (CPolynomial.Raw.monicNormalize modulus).eval a = 0 := + CPolynomial.Raw.eval_monicNormalize_eq_zero_of_eval_eq_zero hmod + have hmodBy := CPolynomial.Raw.eval_modByMonic_eq_self_of_eval_eq_zero + (1 : CPolynomial.Raw F) (CPolynomial.Raw.monicNormalize modulus) hroot + rw [raw_eval_powModBinaryAuxWith M D hmod] + have hOneTrim : (1 : CPolynomial.Raw F).trim = 1 := by + change CPolynomial.Raw.trim (#[] |>.push (1 : F)) = (#[] |>.push (1 : F)) + apply CPolynomial.Raw.Trim.push_trim + simp + rw [D.modByMonic_eq_modByMonic _ _ hOneTrim (raw_monicNormalize_trim modulus)] + rw [hmodBy, raw_eval_one, raw_eval_X] + simp + +/-- Raw modular exponentiation evaluates as ordinary exponentiation at roots of +the modulus. -/ +theorem raw_eval_powModWith_eq_pow {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (M : CPolynomial.Raw.MulContext F) (D : CPolynomial.Raw.ModContext F) + {modulus base : CPolynomial.Raw F} {a : F} + (hmod : modulus.eval a = 0) (q : Nat) : + (CPolynomial.Raw.powModWith M D modulus base q).eval a = + base.eval a ^ q := by + unfold CPolynomial.Raw.powModWith + by_cases hzero : modulus.trim = (#[] : CPolynomial.Raw F) + · simp [hzero] + rw [raw_eval_powModBinaryAuxWith M D hmod, raw_eval_one] + simp + · simp [hzero] + have hroot : (CPolynomial.Raw.monicNormalize modulus).eval a = 0 := + CPolynomial.Raw.eval_monicNormalize_eq_zero_of_eval_eq_zero hmod + have hmodBy := CPolynomial.Raw.eval_modByMonic_eq_self_of_eval_eq_zero + (1 : CPolynomial.Raw F) (CPolynomial.Raw.monicNormalize modulus) hroot + rw [raw_eval_powModBinaryAuxWith M D hmod] + have hOneTrim : (1 : CPolynomial.Raw F).trim = 1 := by + change CPolynomial.Raw.trim (#[] |>.push (1 : F)) = (#[] |>.push (1 : F)) + apply CPolynomial.Raw.Trim.push_trim + simp + rw [D.modByMonic_eq_modByMonic _ _ hOneTrim (raw_monicNormalize_trim modulus)] + rw [hmodBy, raw_eval_one] + simp + +private theorem raw_eval_xModWith {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (D : CPolynomial.Raw.ModContext F) + {modulus : CPolynomial.Raw F} {a : F} (hmod : modulus.eval a = 0) : + (CPolynomial.Raw.xModWith D modulus).eval a = a := by + unfold CPolynomial.Raw.xModWith + by_cases hzero : modulus.trim = (#[] : CPolynomial.Raw F) + · simp [hzero, raw_eval_X] + · simp [hzero] + have hroot : (CPolynomial.Raw.monicNormalize modulus).eval a = 0 := + CPolynomial.Raw.eval_monicNormalize_eq_zero_of_eval_eq_zero hmod + have hmodBy := CPolynomial.Raw.eval_modByMonic_eq_self_of_eval_eq_zero + (CPolynomial.Raw.X : CPolynomial.Raw F) (CPolynomial.Raw.monicNormalize modulus) hroot + have hXTrim : (CPolynomial.Raw.X : CPolynomial.Raw F).trim = CPolynomial.Raw.X := by + exact CPolynomial.Raw.X_canonical + rw [D.modByMonic_eq_modByMonic _ _ hXTrim (raw_monicNormalize_trim modulus)] + rw [hmodBy, raw_eval_X] + +private theorem raw_eval_xPowSubXModWith {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (M : CPolynomial.Raw.MulContext F) (D : CPolynomial.Raw.ModContext F) + (ctx : FiniteFieldContext F) {modulus : CPolynomial.Raw F} {a : F} + (hmod : modulus.eval a = 0) : + (CPolynomial.Raw.xPowSubXModWith M D ctx.q modulus).eval a = 0 := by + unfold CPolynomial.Raw.xPowSubXModWith + rw [raw_eval_sub, raw_eval_powModWith_X M D hmod, raw_eval_xModWith D hmod, + ctx.frobenius_fixed a] + simp + +/-- Every root of `p` is a root of the finite-field root product. -/ +theorem finiteFieldRootProductWith_complete {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (M : CPolynomial.Raw.MulContext F) (D : CPolynomial.Raw.ModContext F) + (ctx : FiniteFieldContext F) {p : CPolynomial F} {a : F} + (hp : p ≠ 0) (hroot : CPolynomial.eval a p = 0) : + CPolynomial.eval a (finiteFieldRootProductWith M D ctx p) = 0 := by + unfold finiteFieldRootProductWith + change + CPolynomial.Raw.eval a + (CPolynomial.Raw.Roots.FiniteField.finiteFieldRootProductWith M D + ctx p.val).trim = + 0 + rw [CPolynomial.Raw.eval_trim_eq_eval] + unfold CPolynomial.Raw.Roots.FiniteField.finiteFieldRootProductWith + by_cases hpempty : p.val = (#[] : CPolynomial.Raw F) + · have hp0 : p = 0 := by + apply CPolynomial.ext + simpa using hpempty + exact (hp hp0).elim + · simp [hpempty, CPolynomial.trim_eq] + have hmonicRoot : (CPolynomial.Raw.monicNormalize p.val).eval a = 0 := + CPolynomial.Raw.eval_monicNormalize_eq_zero_of_eval_eq_zero hroot + unfold CPolynomial.Raw.gcdMonic + exact Raw.eval_gcdMonicWithFuel_eq_zero_of_left_right + _ (CPolynomial.Raw.monicNormalize p.val) + (CPolynomial.Raw.xPowSubXModWith M D ctx.q (CPolynomial.Raw.monicNormalize p.val)) + hmonicRoot (raw_eval_xPowSubXModWith M D ctx hmonicRoot) + +/-- Every root of `p` is a root of the finite-field root product. -/ +theorem finiteFieldRootProduct_complete {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (ctx : FiniteFieldContext F) {p : CPolynomial F} {a : F} + (hp : p ≠ 0) (hroot : CPolynomial.eval a p = 0) : + CPolynomial.eval a (finiteFieldRootProduct ctx p) = 0 := by + unfold finiteFieldRootProduct finiteFieldRootProductWith + change + CPolynomial.Raw.eval a + (CPolynomial.Raw.Roots.FiniteField.finiteFieldRootProductWith + CPolynomial.Raw.MulContext.naive CPolynomial.Raw.ModContext.naive + ctx p.val).trim = + 0 + rw [CPolynomial.Raw.eval_trim_eq_eval] + unfold CPolynomial.Raw.Roots.FiniteField.finiteFieldRootProductWith + by_cases hpempty : p.val = (#[] : CPolynomial.Raw F) + · have hp0 : p = 0 := by + apply CPolynomial.ext + simpa using hpempty + exact (hp hp0).elim + · simp [hpempty, CPolynomial.trim_eq] + have hmonicRoot : (CPolynomial.Raw.monicNormalize p.val).eval a = 0 := + CPolynomial.Raw.eval_monicNormalize_eq_zero_of_eval_eq_zero hroot + unfold CPolynomial.Raw.gcdMonic + exact Raw.eval_gcdMonicWithFuel_eq_zero_of_left_right + _ (CPolynomial.Raw.monicNormalize p.val) + (CPolynomial.Raw.xPowSubXModWith CPolynomial.Raw.MulContext.naive + CPolynomial.Raw.ModContext.naive ctx.q (CPolynomial.Raw.monicNormalize p.val)) + hmonicRoot (raw_eval_xPowSubXMod_naive ctx hmonicRoot) + +/-- Every validated root extracted from the root product is a root of `p`. -/ +theorem finiteFieldRootProduct_validated_sound {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (_ctx : FiniteFieldContext F) {p : CPolynomial F} + {candidates : Array F} {a : F} + (h : a ∈ (CPolynomial.validateRootCandidates p candidates).toList) : + CPolynomial.eval a p = 0 := by + exact CPolynomial.mem_validateRootCandidates_eval_eq_zero h + +/-- Returned finite-field roots are roots of the original polynomial. -/ +theorem rootsInFiniteFieldWith_sound {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (M : CPolynomial.Raw.MulContext F) (D : CPolynomial.Raw.ModContext F) + (ctx : FiniteFieldContext F) (splitter : LinearFactorProductSplitter F) + {p : CPolynomial F} {a : F} + (h : a ∈ (rootsInFiniteFieldWith M D ctx splitter p).toList) : + CPolynomial.eval a p = 0 := by + rw [rootsInFiniteFieldWith] at h + split at h + · simp at h + · split at h + · simp at h + · split at h + · exact mem_rootsFromLinearFactors_eval_eq_zero h + · exact mem_rootsFromLinearFactors_eval_eq_zero h + +/-- Returned finite-field roots are roots of the original polynomial. -/ +theorem rootsInFiniteField_sound {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (ctx : FiniteFieldContext F) (splitter : LinearFactorProductSplitter F) + {p : CPolynomial F} {a : F} + (h : a ∈ (rootsInFiniteField ctx splitter p).toList) : + CPolynomial.eval a p = 0 := by + exact rootsInFiniteFieldWith_sound + (M := CPolynomial.Raw.MulContext.naive) (D := CPolynomial.Raw.ModContext.naive) + ctx splitter h + +private theorem eq_zero_of_size_le_one_root {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + {p : CPolynomial F} {a : F} + (hsize : p.val.size ≤ 1) (hroot : CPolynomial.eval a p = 0) : + p = 0 := by + rcases p with ⟨⟨xs⟩, hcanon⟩ + cases xs with + | nil => + rfl + | cons x xs => + cases xs with + | nil => + simp [CPolynomial.eval] at hroot + have hxne := hcanon (by simp) + simp [Array.getLast] at hxne + exact (hxne hroot).elim + | cons _ _ => + simp at hsize + +private theorem linear_candidate_self_of_size_two_root {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + {p : CPolynomial F} {a : F} + (hsize : p.val.size = 2) (hcoeff : p.coeff 1 ≠ 0) + (hroot : CPolynomial.eval a p = 0) : + IsLinearRootFactorCandidate p a := by + rcases p with ⟨⟨xs⟩, hcanon⟩ + cases xs with + | nil => + simp at hsize + | cons x xs => + cases xs with + | nil => + simp at hsize + | cons y xs => + cases xs with + | nil => + simp [IsLinearRootFactorCandidate, IsLinearFactor, CPolynomial.eval, + CPolynomial.coeff, CPolynomial.Raw.coeff] at hroot hcoeff ⊢ + exact ⟨hcoeff, hroot⟩ + | cons _ _ => + simp at hsize + +/-- Every explicit `X - a` factor is represented as a nonconstant linear factor. -/ +theorem linearFactor_isLinearFactor {F : Type*} [Field F] [BEq F] [LawfulBEq F] + (a : F) : IsLinearFactor (CPolynomial.linearFactor a) := by + constructor + · rw [CPolynomial.linearFactor] + calc + ((CPolynomial.C (-a) : CPolynomial F) + CPolynomial.X).val.size + = (CPolynomial.Raw.addRaw (CPolynomial.C (-a)).val CPolynomial.X.val).trim.size := by + rfl + _ ≤ (CPolynomial.Raw.addRaw (CPolynomial.C (-a)).val CPolynomial.X.val).size := + CPolynomial.Raw.Trim.size_le_size _ + _ = max (CPolynomial.C (-a)).val.size CPolynomial.X.val.size := + CPolynomial.Raw.add_size + _ ≤ 2 := by + have hC : (CPolynomial.C (-a) : CPolynomial F).val.size ≤ 1 := by + unfold CPolynomial.C CPolynomial.Raw.C + exact CPolynomial.Raw.Trim.size_le_size _ + have hX : (CPolynomial.X : CPolynomial F).val.size ≤ 2 := by + rfl + exact max_le (le_trans hC (by omega)) hX + · rw [CPolynomial.linearFactor, CPolynomial.coeff_add, CPolynomial.coeff_C] + simp [CPolynomial.X, CPolynomial.Raw.X, CPolynomial.coeff, CPolynomial.Raw.coeff] + +/-- The represented-linear recognizer accepts every explicit `X - a` factor. -/ +theorem linearFactor_isRepresentedLinearFactor {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (a : F) : isRepresentedLinearFactor (CPolynomial.linearFactor a) = true := by + unfold isRepresentedLinearFactor + have hlin := linearFactor_isLinearFactor a + have hcoeff : ¬(CPolynomial.linearFactor a).val[1]?.getD 0 = 0 := by + simpa [CPolynomial.coeff, CPolynomial.Raw.coeff] using hlin.2 + simp [hlin.1, hcoeff, CPolynomial.coeff, CPolynomial.Raw.coeff] + +/-- The explicit `X - a` factor represents the root `a`. -/ +theorem linearFactor_isRootFactorCandidate {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (a : F) : IsLinearRootFactorCandidate (CPolynomial.linearFactor a) a := by + constructor + · exact linearFactor_isLinearFactor a + · have h0 : (CPolynomial.linearFactor a).coeff 0 = -a := by + rw [CPolynomial.linearFactor, CPolynomial.coeff_add, CPolynomial.coeff_C] + simp [CPolynomial.X, CPolynomial.Raw.X, CPolynomial.coeff, CPolynomial.Raw.coeff] + have h1 : (CPolynomial.linearFactor a).coeff 1 = 1 := by + rw [CPolynomial.linearFactor, CPolynomial.coeff_add, CPolynomial.coeff_C] + simp [CPolynomial.X, CPolynomial.Raw.X, CPolynomial.coeff, CPolynomial.Raw.coeff] + rw [h0, h1] + simp + +/-- If the represented-linear recognizer accepts `p`, then any root of `p` is its root candidate. -/ +theorem representedLinearFactor_candidate_of_root {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + {p : CPolynomial F} {a : F} + (hlin : isRepresentedLinearFactor p = true) + (hroot : CPolynomial.eval a p = 0) : + IsLinearRootFactorCandidate p a := by + rcases p with ⟨⟨xs⟩, hcanon⟩ + cases xs with + | nil => + simp [isRepresentedLinearFactor, CPolynomial.coeff, CPolynomial.Raw.coeff] at hlin + | cons x xs => + cases xs with + | nil => + simp [isRepresentedLinearFactor, CPolynomial.coeff, CPolynomial.Raw.coeff] at hlin + | cons y xs => + cases xs with + | nil => + simp [isRepresentedLinearFactor, IsLinearRootFactorCandidate, IsLinearFactor, + CPolynomial.eval, CPolynomial.coeff, CPolynomial.Raw.coeff] at hroot hlin ⊢ + exact ⟨hlin, hroot⟩ + | cons _ _ => + simp [isRepresentedLinearFactor] at hlin + +/-- Evaluation of a modular `X^n` at a root of the modulus agrees with `a^n`. -/ +theorem eval_xPowModWith_eq_pow {F : Type*} [Field F] [BEq F] [LawfulBEq F] + (M : CPolynomial.Raw.MulContext F) (D : CPolynomial.Raw.ModContext F) + {p : CPolynomial F} {a : F} (hroot : CPolynomial.eval a p = 0) (n : Nat) : + CPolynomial.eval a (xPowModWith M D p n) = a ^ n := by + unfold xPowModWith + change CPolynomial.Raw.eval a + (CPolynomial.Raw.powModWith M D p.val CPolynomial.Raw.X n).trim = a ^ n + rw [CPolynomial.Raw.eval_trim_eq_eval] + exact raw_eval_powModWith_X M D hroot n + +/-- Every root of a nonzero polynomial is returned by the finite-field backend. -/ +theorem rootsInFiniteFieldWith_complete {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (M : CPolynomial.Raw.MulContext F) (D : CPolynomial.Raw.ModContext F) + (ctx : FiniteFieldContext F) (splitter : LinearFactorProductSplitter F) + (splitterValid : + ∀ {p : CPolynomial F}, p ≠ 0 → + splitter.validInput ctx.q (finiteFieldRootProductWith M D ctx p)) + {p : CPolynomial F} {a : F} + (hp : p ≠ 0) (hroot : CPolynomial.eval a p = 0) : + a ∈ (rootsInFiniteFieldWith M D ctx splitter p).toList := by + rw [rootsInFiniteFieldWith] + split + · rename_i hzero + have hp0 : p = 0 := by + simpa using hzero + exact (hp hp0).elim + · split + · rename_i _ hsmall + exact (hp (eq_zero_of_size_le_one_root hsmall hroot)).elim + · split + · rename_i _ _ hlinear + simp at hlinear + have hcoeff : p.coeff 1 ≠ 0 := by + simpa [CPolynomial.coeff, CPolynomial.Raw.coeff] using hlinear.2 + exact mem_rootsFromLinearFactors_of_candidate (p := p) (factors := #[p]) + (factor := p) (by simp) + (linear_candidate_self_of_size_two_root hlinear.1 hcoeff hroot) hroot + · have hprodRoot : CPolynomial.eval a (finiteFieldRootProductWith M D ctx p) = 0 := + finiteFieldRootProductWith_complete M D ctx hp hroot + have hprodNe : finiteFieldRootProductWith M D ctx p ≠ 0 := + finiteFieldRootProductWith_ne_zero_of_ne_zero M D ctx hp + have hprodValid : + splitter.validInput ctx.q (finiteFieldRootProductWith M D ctx p) := by + exact splitterValid hp + rcases splitter.complete ctx.q (finiteFieldRootProductWith M D ctx p) a + hprodValid hprodNe hprodRoot with + ⟨factor, hmem, hcand⟩ + exact mem_rootsFromLinearFactors_of_candidate (p := p) + (factors := splitter.splitLinearFactors ctx.q (finiteFieldRootProductWith M D ctx p)) + (factor := factor) hmem hcand hroot + +/-- Every root of a nonzero polynomial is returned by the finite-field backend. -/ +theorem rootsInFiniteField_complete {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (ctx : FiniteFieldContext F) (splitter : LinearFactorProductSplitter F) + (splitterValid : + ∀ {p : CPolynomial F}, p ≠ 0 → + splitter.validInput ctx.q (finiteFieldRootProduct ctx p)) + {p : CPolynomial F} {a : F} + (hp : p ≠ 0) (hroot : CPolynomial.eval a p = 0) : + a ∈ (rootsInFiniteField ctx splitter p).toList := by + exact rootsInFiniteFieldWith_complete + (M := CPolynomial.Raw.MulContext.naive) (D := CPolynomial.Raw.ModContext.naive) + ctx splitter (by + intro p hp + exact splitterValid hp) hp hroot + +/-- The complete executable finite-field root pipeline is sound and complete +for nonzero inputs under the finite-field and splitter contracts. -/ +theorem rootsInFiniteField_spec {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (ctx : FiniteFieldContext F) (splitter : LinearFactorProductSplitter F) + (splitterValid : + ∀ {p : CPolynomial F}, p ≠ 0 → + splitter.validInput ctx.q (finiteFieldRootProduct ctx p)) + {p : CPolynomial F} {a : F} (hp : p ≠ 0) : + a ∈ (rootsInFiniteField ctx splitter p).toList ↔ + CPolynomial.eval a p = 0 := by + constructor + · intro h + exact rootsInFiniteField_sound ctx splitter h + · intro h + exact rootsInFiniteField_complete ctx splitter splitterValid hp h + +end FiniteField + +end Roots + +end CPolynomial + +end CompPoly diff --git a/CompPoly/Univariate/Roots/Enumeration.lean b/CompPoly/Univariate/Roots/Enumeration.lean new file mode 100644 index 00000000..15490940 --- /dev/null +++ b/CompPoly/Univariate/Roots/Enumeration.lean @@ -0,0 +1,124 @@ +/- +Copyright (c) 2026 CompPoly Contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ + +import CompPoly.Univariate.Roots.Correctness + +/-! +# Exhaustive Finite-Field Root Enumeration + +Reusable lazy field-enumeration contexts for finite-field root search. The +context stores an indexing function rather than an array of all elements; array +inputs are adapted through `fieldEnumerationOfArray` for tests and small +callers. +-/ + +namespace CompPoly + +namespace CPolynomial + +namespace Roots + +namespace FiniteField + +/-- A lazy complete enumeration of a finite field. -/ +structure FieldEnumeration (F : Type*) where + size : Nat + elem : Fin size → F + complete : ∀ a : F, ∃ i : Fin size, elem i = a + +/-- An array contains every field element. Duplicate entries are allowed. -/ +def ContainsAllFieldElements {F : Type*} (elements : Array F) : Prop := + ∀ a : F, a ∈ elements.toList + +/-- Adapt an explicit element array to a lazy enumeration context. -/ +def fieldEnumerationOfArray {F : Type*} (elements : Array F) + (hElements : ContainsAllFieldElements elements) : + FieldEnumeration F where + size := elements.size + elem i := elements[i] + complete := by + intro a + rcases List.mem_iff_get.mp (hElements a) with ⟨i, hi⟩ + refine ⟨⟨i.val, by simpa only [Array.length_toList] using i.isLt⟩, ?_⟩ + simpa using hi + +/-- Roots by exhaustive evaluation over a lazy field enumeration. -/ +def rootsInFieldByEnumeration {F : Type*} [Semiring F] [BEq F] + (enumeration : FieldEnumeration F) (p : CPolynomial F) : Array F := + (Array.ofFn enumeration.elem).filter fun a ↦ CPolynomial.evalHorner a p == 0 + +/-- Exhaustive enumeration only returns actual roots. -/ +theorem rootsInFieldByEnumeration_sound {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + {enumeration : FieldEnumeration F} {p : CPolynomial F} {a : F} + (h : a ∈ (rootsInFieldByEnumeration enumeration p).toList) : + CPolynomial.eval a p = 0 := by + rw [rootsInFieldByEnumeration] at h + simp at h + simpa [CPolynomial.eval_horner_eq_eval] using h.2 + +/-- Complete enumeration finds every root. -/ +theorem rootsInFieldByEnumeration_complete {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (enumeration : FieldEnumeration F) {p : CPolynomial F} {a : F} + (hroot : CPolynomial.eval a p = 0) : + a ∈ (rootsInFieldByEnumeration enumeration p).toList := by + rw [rootsInFieldByEnumeration] + rcases enumeration.complete a with ⟨i, hi⟩ + simpa [CPolynomial.eval_horner_eq_eval, hroot] using ⟨i, hi⟩ + +/-- Linear factors for every enumerated root of `p`. -/ +def enumeratedLinearFactors {F : Type*} [Field F] [BEq F] [LawfulBEq F] + (enumeration : FieldEnumeration F) (p : CPolynomial F) : + Array (CPolynomial F) := + (rootsInFieldByEnumeration enumeration p).map CPolynomial.linearFactor + +/-- Every factor emitted by exhaustive enumeration is represented linear. -/ +theorem enumeratedLinearFactors_sound {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + {enumeration : FieldEnumeration F} {p factor : CPolynomial F} + (h : factor ∈ (enumeratedLinearFactors enumeration p).toList) : + IsLinearFactor factor := by + rw [enumeratedLinearFactors] at h + simp at h + rcases h with ⟨a, _hmem, rfl⟩ + exact linearFactor_isLinearFactor a + +/-- Exhaustive enumeration emits the linear factor for every root. -/ +theorem enumeratedLinearFactors_complete {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (enumeration : FieldEnumeration F) {p : CPolynomial F} {a : F} + (hroot : CPolynomial.eval a p = 0) : + ∃ factor, + factor ∈ (enumeratedLinearFactors enumeration p).toList ∧ + IsLinearRootFactorCandidate factor a := by + refine ⟨CPolynomial.linearFactor a, ?_, linearFactor_isRootFactorCandidate a⟩ + rw [enumeratedLinearFactors] + simpa using + (List.mem_map.mpr + ⟨a, rootsInFieldByEnumeration_complete enumeration hroot, rfl⟩) + +/-- Exhaustive enumeration packaged as a linear-factor product splitter. -/ +def enumeratingLinearFactorProductSplitter {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (enumeration : FieldEnumeration F) : + LinearFactorProductSplitter F where + splitLinearFactors := fun _ p ↦ enumeratedLinearFactors enumeration p + validInput := fun _ _ ↦ True + sound := by + intro _q p factor h + exact enumeratedLinearFactors_sound h + complete := by + intro _q p a _hvalid _hp hroot + exact enumeratedLinearFactors_complete enumeration hroot + +end FiniteField + +end Roots + +end CPolynomial + +end CompPoly diff --git a/CompPoly/Univariate/Roots/Extraction.lean b/CompPoly/Univariate/Roots/Extraction.lean new file mode 100644 index 00000000..1dc3a51e --- /dev/null +++ b/CompPoly/Univariate/Roots/Extraction.lean @@ -0,0 +1,41 @@ +/- +Copyright (c) 2026 CompPoly Contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ + +import CompPoly.Univariate.Basic + +/-! +# Root Extraction Helpers + +Candidate extraction, validation, and deduplication helpers for univariate root +finding. +-/ + +namespace CompPoly + +namespace CPolynomial + +/-- Extract the root of a represented nonconstant linear factor. -/ +def linearRootOfFactor? {F : Type*} [Field F] [BEq F] + (factor : CPolynomial F) : Option F := + if factor.val.size ≤ 2 && !(factor.coeff 1 == 0) then + some (-(factor.coeff 0) / factor.coeff 1) + else + none + +/-- Keep only candidates that are actual roots of the original polynomial. -/ +def validateRootCandidates {F : Type*} [Field F] [BEq F] + (p : CPolynomial F) (candidates : Array F) : Array F := + candidates.filter fun a ↦ CPolynomial.evalHorner a p == 0 + +/-- Extract, validate, and deduplicate roots from a list of linear factors. -/ +def rootsFromLinearFactors {F : Type*} [Field F] [BEq F] + (p : CPolynomial F) (factors : Array (CPolynomial F)) : Array F := + let candidates := (factors.toList.filterMap linearRootOfFactor?).toArray + (validateRootCandidates p candidates).eraseDups + +end CPolynomial + +end CompPoly diff --git a/CompPoly/Univariate/Roots/RootProduct.lean b/CompPoly/Univariate/Roots/RootProduct.lean new file mode 100644 index 00000000..794034be --- /dev/null +++ b/CompPoly/Univariate/Roots/RootProduct.lean @@ -0,0 +1,643 @@ +/- +Copyright (c) 2026 CompPoly Contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ + +import CompPoly.Univariate.Raw.Modular +import CompPoly.Univariate.EuclideanAlgorithm +import CompPoly.Univariate.Roots.Context + +/-! +# Finite-Field Root Products + +Executable construction of `gcd(p, X^q - X)` as +`gcd(p, (X^q mod p) - (X mod p))`, so large finite fields never materialize the +dense polynomial `X^q - X`. +-/ + +namespace CompPoly + +namespace CPolynomial + +namespace Raw + +namespace Roots + +namespace FiniteField + +/-- Raw squarefree product of the linear factors of `p` whose roots lie in the field. -/ +def finiteFieldRootProductWith {F : Type*} [Field F] [BEq F] [LawfulBEq F] + (M : CPolynomial.Raw.MulContext F) (D : CPolynomial.Raw.ModContext F) + (ctx : CPolynomial.Roots.FiniteField.FiniteFieldContext F) + (p : CPolynomial.Raw F) : CPolynomial.Raw F := + if p.trim == 0 then + 0 + else + let pMonic := CPolynomial.Raw.monicNormalize p + CPolynomial.Raw.gcdMonic pMonic (CPolynomial.Raw.xPowSubXModWith M D ctx.q pMonic) + +/-- Raw squarefree product using the default raw backends. -/ +def finiteFieldRootProduct {F : Type*} [Field F] [BEq F] [LawfulBEq F] + (ctx : CPolynomial.Roots.FiniteField.FiniteFieldContext F) + (p : CPolynomial.Raw F) : CPolynomial.Raw F := + finiteFieldRootProductWith CPolynomial.Raw.MulContext.naive + CPolynomial.Raw.ModContext.naive ctx p + +end FiniteField + +end Roots + +end Raw + +namespace Roots + +namespace FiniteField + +/-- The squarefree product of the linear factors of `p` whose roots lie in the field. -/ +def finiteFieldRootProductWith {F : Type*} [Field F] [BEq F] [LawfulBEq F] + (M : CPolynomial.Raw.MulContext F) (D : CPolynomial.Raw.ModContext F) + (ctx : FiniteFieldContext F) (p : CPolynomial F) : CPolynomial F := + CPolynomial.ofArray (CPolynomial.Raw.Roots.FiniteField.finiteFieldRootProductWith M D ctx p.val) + +/-- The squarefree product of the linear factors of `p` using the default raw backends. -/ +def finiteFieldRootProduct {F : Type*} [Field F] [BEq F] [LawfulBEq F] + (ctx : FiniteFieldContext F) (p : CPolynomial F) : CPolynomial F := + finiteFieldRootProductWith CPolynomial.Raw.MulContext.naive + CPolynomial.Raw.ModContext.naive ctx p + +private theorem raw_monicNormalize_ne_zero_of_trim_ne_zero {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + {p : CPolynomial.Raw F} (hp : p.trim ≠ (0 : CPolynomial.Raw F)) : + CPolynomial.Raw.monicNormalize p ≠ 0 := by + unfold CPolynomial.Raw.monicNormalize + by_cases hzero : p.trim == (0 : CPolynomial.Raw F) + · have hzeroEq : p.trim = 0 := by + simpa using hzero + exact (hp hzeroEq).elim + · have hpzeroNe : ¬p.trim = (#[] : CPolynomial.Raw F) := by + intro hpzero + exact hzero (by simp [hpzero]) + rw [if_neg hzero] + intro hsmul + have hsize_smul : + (CPolynomial.Raw.smul (p.trim.leadingCoeff)⁻¹ p.trim).size = p.trim.size := by + simp [CPolynomial.Raw.smul] + change CPolynomial.Raw.smul (p.trim.leadingCoeff)⁻¹ p.trim = 0 at hsmul + rw [hsmul] at hsize_smul + have hsize : p.trim.size = 0 := by + simpa using hsize_smul.symm + have hpempty : p.trim = (#[] : CPolynomial.Raw F) := by + apply Array.eq_empty_of_size_eq_zero + exact hsize + exact hp (by simpa using hpempty) + +private theorem raw_monicNormalize_trim {F : Type*} [Field F] [BEq F] [LawfulBEq F] + (p : CPolynomial.Raw F) : + (CPolynomial.Raw.monicNormalize p).trim = CPolynomial.Raw.monicNormalize p := by + unfold CPolynomial.Raw.monicNormalize + let q := p.trim + by_cases hzero : q == (0 : CPolynomial.Raw F) + · have hzeroEq : q = 0 := by + simpa using hzero + have hpzeroEq : p.trim = (#[] : CPolynomial.Raw F) := by + simpa [q] using hzeroEq + simp [hpzeroEq, CPolynomial.Raw.Trim.canonical_empty] + · have hzeroNe : q ≠ (0 : CPolynomial.Raw F) := by + intro hq + exact hzero (by simp [hq]) + have hpzeroNe : ¬p.trim = (#[] : CPolynomial.Raw F) := by + intro hp + exact hzeroNe (by simpa [q] using hp) + simp [hpzeroNe] + change (CPolynomial.Raw.mk + (Array.map (fun r ↦ (p.trim).leadingCoeff⁻¹ * r) p.trim)).trim = + CPolynomial.Raw.mk (Array.map (fun r ↦ (p.trim).leadingCoeff⁻¹ * r) p.trim) + apply CPolynomial.Raw.Trim.non_zero_map (fun r ↦ q.leadingCoeff⁻¹ * r) + · intro r hr + apply mul_eq_zero.mp at hr + rcases hr with hinv | hr + · have hlead0 : q.leadingCoeff = 0 := by + exact inv_eq_zero.mp hinv + have hcanon : q.trim = q := by + simpa [q] using CPolynomial.Raw.Trim.trim_twice p + have hcrit := (CPolynomial.Raw.Trim.trim_eq_iff_size_eq_zero_or_getLastD_ne_zero + (p := q)).mp hcanon + rcases hcrit with hsize | hlast + · have hqempty : q = (#[] : CPolynomial.Raw F) := by + apply Array.eq_empty_of_size_eq_zero + exact hsize + exact False.elim (hzero (by simp [hqempty])) + · unfold CPolynomial.Raw.leadingCoeff at hlead0 + rw [hcanon] at hlead0 + exact (hlast hlead0).elim + · exact hr + · simpa [q] using CPolynomial.Raw.Trim.trim_twice p + +private theorem raw_xPowSubXModWith_trim {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (M : CPolynomial.Raw.MulContext F) (D : CPolynomial.Raw.ModContext F) + (q : Nat) (modulus : CPolynomial.Raw F) : + (CPolynomial.Raw.xPowSubXModWith M D q modulus).trim = + CPolynomial.Raw.xPowSubXModWith M D q modulus := by + unfold CPolynomial.Raw.xPowSubXModWith + exact CPolynomial.Raw.Trim.trim_twice _ + +/-- The finite-field root product agrees with the normalized Mathlib gcd of the +monic input and its represented Frobenius witness. -/ +theorem finiteFieldRootProductWith_toPoly_eq_normalize_gcd {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [DecidableEq F] + (M : CPolynomial.Raw.MulContext F) (D : CPolynomial.Raw.ModContext F) + (ctx : FiniteFieldContext F) {p : CPolynomial F} + (hp : p ≠ 0) : + let pMonic := CPolynomial.monicNormalize p + (finiteFieldRootProductWith M D ctx p).toPoly = + normalize (EuclideanDomain.gcd pMonic.toPoly + (CPolynomial.ofArray + (CPolynomial.Raw.xPowSubXModWith M D ctx.q pMonic.val)).toPoly) := by + dsimp + unfold finiteFieldRootProductWith + unfold CPolynomial.Raw.Roots.FiniteField.finiteFieldRootProductWith + have hpraw : p.val.trim ≠ (0 : CPolynomial.Raw F) := by + intro hpraw0 + apply hp + apply CPolynomial.ext + rw [CPolynomial.trim_eq] at hpraw0 + simpa using hpraw0 + have hpzero : ¬(p.val.trim == (0 : CPolynomial.Raw F)) := by + intro hzero + exact hpraw (LawfulBEq.eq_of_beq hzero) + rw [if_neg hpzero] + have hpMonicVal : + (CPolynomial.monicNormalize p).val = CPolynomial.Raw.monicNormalize p.val := by + unfold CPolynomial.monicNormalize CPolynomial.ofArray + change (CPolynomial.Raw.monicNormalize p.val).trim = + CPolynomial.Raw.monicNormalize p.val + exact raw_monicNormalize_trim p.val + rw [← hpMonicVal] + let witnessRaw := + CPolynomial.Raw.xPowSubXModWith M D ctx.q (CPolynomial.monicNormalize p).val + have hwitnessTrim : witnessRaw.trim = witnessRaw := by + dsimp [witnessRaw] + exact raw_xPowSubXModWith_trim M D ctx.q (CPolynomial.monicNormalize p).val + simpa [CPolynomial.gcdMonic, CPolynomial.ofArray, witnessRaw, hwitnessTrim] using + CPolynomial.gcdMonic_toPoly_eq_normalize_gcd + (CPolynomial.monicNormalize p) (CPolynomial.ofArray witnessRaw) + +private theorem raw_monicNormalize_trim_arg {F : Type*} [Field F] [BEq F] [LawfulBEq F] + (p : CPolynomial.Raw F) : + CPolynomial.Raw.monicNormalize p.trim = CPolynomial.Raw.monicNormalize p := by + unfold CPolynomial.Raw.monicNormalize + rw [CPolynomial.Raw.Trim.trim_twice] + +private theorem raw_monicNormalize_toPoly_eq_normalize {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [DecidableEq F] + (p : CPolynomial.Raw F) : + (CPolynomial.ofArray (CPolynomial.Raw.monicNormalize p)).toPoly = + normalize (CPolynomial.ofArray p).toPoly := by + have h := CPolynomial.monicNormalize_toPoly_eq_normalize (CPolynomial.ofArray p) + unfold CPolynomial.monicNormalize at h + unfold CPolynomial.ofArray at h + rw [raw_monicNormalize_trim_arg] at h + exact h + +private theorem raw_monicNormalize_toPoly_monic {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [DecidableEq F] + {p : CPolynomial.Raw F} (hp : p.trim ≠ 0) : + (CPolynomial.ofArray (CPolynomial.Raw.monicNormalize p)).toPoly.Monic := by + rw [raw_monicNormalize_toPoly_eq_normalize] + have hpC : CPolynomial.ofArray p ≠ 0 := by + intro h + have hval := congrArg Subtype.val h + unfold CPolynomial.ofArray at hval + exact hp (by simpa using hval) + have hpPoly : (CPolynomial.ofArray p).toPoly ≠ 0 := + (CPolynomial.toPoly_eq_zero_iff (CPolynomial.ofArray p)).not.mpr hpC + exact Polynomial.monic_normalize hpPoly + +private theorem raw_modContext_toPoly_eq_modByMonic {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (D : CPolynomial.Raw.ModContext F) {p q : CPolynomial.Raw F} + (hpTrim : p.trim = p) (hqTrim : q.trim = q) + (hqMonic : (CPolynomial.ofArray q).monic) : + (CPolynomial.ofArray (D.modByMonic p q)).toPoly = + (CPolynomial.ofArray p).toPoly %ₘ (CPolynomial.ofArray q).toPoly := by + rw [D.modByMonic_eq_modByMonic p q hpTrim hqTrim] + have hpval : (CPolynomial.ofArray p).val = p := by + unfold CPolynomial.ofArray + exact hpTrim + have hqval : (CPolynomial.ofArray q).val = q := by + unfold CPolynomial.ofArray + exact hqTrim + have h := CPolynomial.modByMonic_toPoly_eq_modByMonic + (CPolynomial.ofArray p) (CPolynomial.ofArray q) hqMonic + simpa [CPolynomial.modByMonic, hpval, hqval, CPolynomial.ofArray_toPoly] using h + +private theorem raw_mulModWith_toPoly_eq_modByMonic {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [DecidableEq F] + (M : CPolynomial.Raw.MulContext F) (D : CPolynomial.Raw.ModContext F) + {modulus p q : CPolynomial.Raw F} (hmod : modulus.trim ≠ 0) : + (CPolynomial.ofArray (CPolynomial.Raw.mulModWith M D modulus p q)).toPoly = + ((CPolynomial.ofArray p).toPoly * (CPolynomial.ofArray q).toPoly) %ₘ + (CPolynomial.ofArray (CPolynomial.Raw.monicNormalize modulus)).toPoly := by + unfold CPolynomial.Raw.mulModWith + have hzero : ¬modulus.trim == (0 : CPolynomial.Raw F) := by + intro h + exact hmod (LawfulBEq.eq_of_beq h) + rw [if_neg hzero] + have hproductTrim : (M.mul p q).trim = M.mul p q := by + rw [M.mul_eq_mul] + exact CPolynomial.Raw.mul_is_trimmed p q + have hqMonic : (CPolynomial.ofArray (CPolynomial.Raw.monicNormalize modulus)).monic := + (CPolynomial.monic_toPoly_iff _).mpr (raw_monicNormalize_toPoly_monic hmod) + rw [raw_modContext_toPoly_eq_modByMonic D hproductTrim + (raw_monicNormalize_trim modulus) hqMonic] + rw [M.mul_eq_mul, CPolynomial.ofArray_toPoly, CPolynomial.ofArray_toPoly, + CPolynomial.Raw.toPoly_mul] + rw [CPolynomial.ofArray_toPoly, CPolynomial.ofArray_toPoly] + +private theorem polynomial_modByMonic_idem {F : Type*} [Field F] + {p q : Polynomial F} (hq : q.Monic) : + (p %ₘ q) %ₘ q = p %ₘ q := by + refine Polynomial.modByMonic_eq_of_dvd_sub hq ?_ + refine ⟨-(p /ₘ q), ?_⟩ + calc + p %ₘ q - p = p %ₘ q - (p %ₘ q + q * (p /ₘ q)) := by + rw [Polynomial.modByMonic_add_div p] + _ = q * -(p /ₘ q) := by + ring + +private theorem polynomial_mul_modByMonic_congr {F : Type*} [Field F] + {a b c d m : Polynomial F} + (ha : a %ₘ m = b %ₘ m) (hc : c %ₘ m = d %ₘ m) : + (a * c) %ₘ m = (b * d) %ₘ m := by + calc + (a * c) %ₘ m = (a %ₘ m * (c %ₘ m)) %ₘ m := by + rw [Polynomial.mul_modByMonic] + _ = (b %ₘ m * (d %ₘ m)) %ₘ m := by + rw [ha, hc] + _ = (b * d) %ₘ m := by + exact (Polynomial.mul_modByMonic b d m).symm + +private theorem polynomial_pow_modByMonic_congr {F : Type*} [Field F] + {a b m : Polynomial F} (h : a %ₘ m = b %ₘ m) : + ∀ n, a ^ n %ₘ m = b ^ n %ₘ m := by + intro n + induction n with + | zero => simp + | succ n ih => + rw [pow_succ, pow_succ] + exact polynomial_mul_modByMonic_congr ih h + +private theorem raw_powModBinaryAuxWith_toPoly_modByMonic {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [DecidableEq F] + (M : CPolynomial.Raw.MulContext F) (D : CPolynomial.Raw.ModContext F) + {modulus acc current : CPolynomial.Raw F} (hmod : modulus.trim ≠ 0) : + ∀ n, + (CPolynomial.ofArray + (CPolynomial.Raw.powModBinaryAuxWith M D modulus n acc current)).toPoly %ₘ + (CPolynomial.ofArray (CPolynomial.Raw.monicNormalize modulus)).toPoly = + ((CPolynomial.ofArray acc).toPoly * (CPolynomial.ofArray current).toPoly ^ n) %ₘ + (CPolynomial.ofArray (CPolynomial.Raw.monicNormalize modulus)).toPoly := by + intro n + induction n using Nat.strongRecOn generalizing acc current with + | ind n ih => + cases n with + | zero => + rw [CPolynomial.Raw.powModBinaryAuxWith, pow_zero] + congr 1 + ring + | succ n => + rw [CPolynomial.Raw.powModBinaryAuxWith] + let m := (CPolynomial.ofArray (CPolynomial.Raw.monicNormalize modulus)).toPoly + let acc' := + if (n + 1) % 2 == 1 then + CPolynomial.Raw.mulModWith M D modulus acc current + else + acc + let current' := CPolynomial.Raw.mulModWith M D modulus current current + have ih' := ih ((n + 1) / 2) + (Nat.div_lt_self (Nat.succ_pos n) (by decide)) + (acc := acc') (current := current') + change + (CPolynomial.ofArray + (CPolynomial.Raw.powModBinaryAuxWith M D modulus + ((n + 1) / 2) acc' current')).toPoly %ₘ + m = + ((CPolynomial.ofArray acc).toPoly * + (CPolynomial.ofArray current).toPoly ^ (n + 1)) %ₘ m + rw [ih'] + have hcurrent : + (CPolynomial.ofArray current').toPoly %ₘ m = + ((CPolynomial.ofArray current).toPoly * + (CPolynomial.ofArray current).toPoly) %ₘ m := by + dsimp [current', m] + rw [raw_mulModWith_toPoly_eq_modByMonic M D hmod] + exact polynomial_modByMonic_idem (raw_monicNormalize_toPoly_monic hmod) + by_cases hodd : (n + 1) % 2 == 1 + · have hacc : + (CPolynomial.ofArray acc').toPoly %ₘ m = + ((CPolynomial.ofArray acc).toPoly * + (CPolynomial.ofArray current).toPoly) %ₘ m := by + dsimp [acc', m] + rw [if_pos hodd] + rw [raw_mulModWith_toPoly_eq_modByMonic M D hmod] + exact polynomial_modByMonic_idem (raw_monicNormalize_toPoly_monic hmod) + have hpow : + ((CPolynomial.ofArray current).toPoly * + (CPolynomial.ofArray current).toPoly) ^ ((n + 1) / 2) = + (CPolynomial.ofArray current).toPoly ^ n := by + rw [mul_pow, ← pow_add] + congr 1 + have hoddNat : (n + 1) % 2 = 1 := by + simpa using hodd + omega + have hcurrentPow : + (CPolynomial.ofArray current').toPoly ^ ((n + 1) / 2) %ₘ m = + (CPolynomial.ofArray current).toPoly ^ n %ₘ m := by + have h := polynomial_pow_modByMonic_congr hcurrent ((n + 1) / 2) + rwa [hpow] at h + calc + ((CPolynomial.ofArray acc').toPoly * + (CPolynomial.ofArray current').toPoly ^ ((n + 1) / 2)) %ₘ m = + (((CPolynomial.ofArray acc).toPoly * + (CPolynomial.ofArray current).toPoly) * + (CPolynomial.ofArray current).toPoly ^ n) %ₘ m := by + exact polynomial_mul_modByMonic_congr hacc hcurrentPow + _ = ((CPolynomial.ofArray acc).toPoly * + (CPolynomial.ofArray current).toPoly ^ (n + 1)) %ₘ m := by + rw [pow_succ] + ring_nf + · have hacc : + (CPolynomial.ofArray acc').toPoly %ₘ m = + (CPolynomial.ofArray acc).toPoly %ₘ m := by + dsimp [acc'] + rw [if_neg hodd] + have hpow : + ((CPolynomial.ofArray current).toPoly * + (CPolynomial.ofArray current).toPoly) ^ ((n + 1) / 2) = + (CPolynomial.ofArray current).toPoly ^ (n + 1) := by + rw [mul_pow, ← pow_add] + congr 1 + have hoddNat : (n + 1) % 2 = 0 := by + simpa using hodd + omega + have hcurrentPow : + (CPolynomial.ofArray current').toPoly ^ ((n + 1) / 2) %ₘ m = + (CPolynomial.ofArray current).toPoly ^ (n + 1) %ₘ m := by + have h := polynomial_pow_modByMonic_congr hcurrent ((n + 1) / 2) + rwa [hpow] at h + exact polynomial_mul_modByMonic_congr hacc hcurrentPow + +private theorem raw_powModWith_X_toPoly_modByMonic {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [DecidableEq F] + (M : CPolynomial.Raw.MulContext F) (D : CPolynomial.Raw.ModContext F) + {modulus : CPolynomial.Raw F} (hmod : modulus.trim ≠ 0) (q : Nat) : + (CPolynomial.ofArray + (CPolynomial.Raw.powModWith M D modulus CPolynomial.Raw.X q)).toPoly %ₘ + (CPolynomial.ofArray (CPolynomial.Raw.monicNormalize modulus)).toPoly = + ((Polynomial.X : Polynomial F) ^ q) %ₘ + (CPolynomial.ofArray (CPolynomial.Raw.monicNormalize modulus)).toPoly := by + unfold CPolynomial.Raw.powModWith + have hzero : ¬modulus.trim == (0 : CPolynomial.Raw F) := by + intro h + exact hmod (LawfulBEq.eq_of_beq h) + rw [if_neg hzero] + let m := (CPolynomial.ofArray (CPolynomial.Raw.monicNormalize modulus)).toPoly + let oneMod := D.modByMonic (1 : CPolynomial.Raw F) (CPolynomial.Raw.monicNormalize modulus) + change + (CPolynomial.ofArray + (CPolynomial.Raw.powModBinaryAuxWith M D modulus q oneMod CPolynomial.Raw.X)).toPoly %ₘ + m = + ((Polynomial.X : Polynomial F) ^ q) %ₘ m + rw [raw_powModBinaryAuxWith_toPoly_modByMonic M D hmod] + have hOneTrim : (1 : CPolynomial.Raw F).trim = 1 := by + change CPolynomial.Raw.trim (#[] |>.push (1 : F)) = (#[] |>.push (1 : F)) + apply CPolynomial.Raw.Trim.push_trim + simp + have hone : + (CPolynomial.ofArray oneMod).toPoly %ₘ m = + (1 : Polynomial F) %ₘ m := by + dsimp [oneMod, m] + rw [raw_modContext_toPoly_eq_modByMonic D hOneTrim + (raw_monicNormalize_trim modulus) + ((CPolynomial.monic_toPoly_iff _).mpr (raw_monicNormalize_toPoly_monic hmod))] + rw [CPolynomial.ofArray_toPoly, CPolynomial.Raw.toPoly_one] + exact polynomial_modByMonic_idem (raw_monicNormalize_toPoly_monic hmod) + have hX : + (CPolynomial.ofArray CPolynomial.Raw.X).toPoly ^ q %ₘ m = + ((Polynomial.X : Polynomial F) ^ q) %ₘ m := by + rw [CPolynomial.ofArray_toPoly, CPolynomial.Raw.toPoly_X] + have hmul := polynomial_mul_modByMonic_congr hone hX + simpa [one_mul] using hmul + +private theorem raw_xModWith_toPoly_modByMonic {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [DecidableEq F] + (D : CPolynomial.Raw.ModContext F) + {modulus : CPolynomial.Raw F} (hmod : modulus.trim ≠ 0) : + (CPolynomial.ofArray (CPolynomial.Raw.xModWith D modulus)).toPoly %ₘ + (CPolynomial.ofArray (CPolynomial.Raw.monicNormalize modulus)).toPoly = + (Polynomial.X : Polynomial F) %ₘ + (CPolynomial.ofArray (CPolynomial.Raw.monicNormalize modulus)).toPoly := by + unfold CPolynomial.Raw.xModWith + have hzero : ¬modulus.trim == (0 : CPolynomial.Raw F) := by + intro h + exact hmod (LawfulBEq.eq_of_beq h) + rw [if_neg hzero] + let m := (CPolynomial.ofArray (CPolynomial.Raw.monicNormalize modulus)).toPoly + have hXTrim : (CPolynomial.Raw.X : CPolynomial.Raw F).trim = CPolynomial.Raw.X := by + exact CPolynomial.Raw.X_canonical + rw [raw_modContext_toPoly_eq_modByMonic D hXTrim + (raw_monicNormalize_trim modulus) + ((CPolynomial.monic_toPoly_iff _).mpr (raw_monicNormalize_toPoly_monic hmod))] + rw [CPolynomial.ofArray_toPoly, CPolynomial.Raw.toPoly_X] + exact polynomial_modByMonic_idem (raw_monicNormalize_toPoly_monic hmod) + +private theorem raw_xPowSubXModWith_toPoly_modByMonic {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [DecidableEq F] + (M : CPolynomial.Raw.MulContext F) (D : CPolynomial.Raw.ModContext F) + {modulus : CPolynomial.Raw F} (hmod : modulus.trim ≠ 0) (q : Nat) : + (CPolynomial.ofArray + (CPolynomial.Raw.xPowSubXModWith M D q modulus)).toPoly %ₘ + (CPolynomial.ofArray (CPolynomial.Raw.monicNormalize modulus)).toPoly = + (((Polynomial.X : Polynomial F) ^ q - Polynomial.X) %ₘ + (CPolynomial.ofArray (CPolynomial.Raw.monicNormalize modulus)).toPoly) := by + unfold CPolynomial.Raw.xPowSubXModWith + rw [CPolynomial.ofArray_toPoly, CPolynomial.Raw.toPoly_sub, Polynomial.sub_modByMonic] + rw [← CPolynomial.ofArray_toPoly + (CPolynomial.Raw.powModWith M D modulus CPolynomial.Raw.X q), + ← CPolynomial.ofArray_toPoly (CPolynomial.Raw.xModWith D modulus)] + rw [raw_powModWith_X_toPoly_modByMonic M D hmod q, + raw_xModWith_toPoly_modByMonic D hmod] + rw [Polynomial.sub_modByMonic] + +/-- The finite-field root product divides the finite-field Frobenius polynomial. + +This is the modular-gcd bridge: the executable root product uses the congruent +modular witness `(X^q mod p) - (X mod p)` instead of materializing `X^q - X`. +-/ +theorem finiteFieldRootProductWith_dvd_frobenius_of_context {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (M : CPolynomial.Raw.MulContext F) (D : CPolynomial.Raw.ModContext F) + (ctx : FiniteFieldContext F) {p : CPolynomial F} (_hp : p ≠ 0) : + (finiteFieldRootProductWith M D ctx p).toPoly ∣ + ((Polynomial.X : Polynomial F) ^ ctx.q - Polynomial.X) := by + letI : DecidableEq F := instDecidableEqOfLawfulBEq + rw [finiteFieldRootProductWith_toPoly_eq_normalize_gcd M D ctx _hp] + apply dvd_trans (normalize_associated _).dvd + let pMonic := CPolynomial.monicNormalize p + let witness := + (CPolynomial.ofArray + (CPolynomial.Raw.xPowSubXModWith M D ctx.q pMonic.val)).toPoly + have hgLeft : EuclideanDomain.gcd pMonic.toPoly witness ∣ pMonic.toPoly := + EuclideanDomain.gcd_dvd_left _ _ + have hgRight : EuclideanDomain.gcd pMonic.toPoly witness ∣ witness := + EuclideanDomain.gcd_dvd_right _ _ + have hpPoly : p.toPoly ≠ 0 := + (CPolynomial.toPoly_eq_zero_iff p).not.mpr _hp + have hpMonicPoly : pMonic.toPoly.Monic := by + dsimp [pMonic] + rw [CPolynomial.monicNormalize_toPoly_eq_normalize] + exact Polynomial.monic_normalize hpPoly + have hpMonicNe : pMonic ≠ 0 := by + intro hzero + dsimp [pMonic] at hzero + unfold CPolynomial.monicNormalize at hzero + have hval := congrArg Subtype.val hzero + simp [CPolynomial.ofArray] at hval + have hpraw : p.val.trim ≠ (0 : CPolynomial.Raw F) := by + intro hpraw0 + apply _hp + apply CPolynomial.ext + rw [CPolynomial.trim_eq] at hpraw0 + simpa using hpraw0 + rw [raw_monicNormalize_trim] at hval + exact raw_monicNormalize_ne_zero_of_trim_ne_zero hpraw hval + have hmod : pMonic.val.trim ≠ 0 := by + intro htrim + apply hpMonicNe + apply CPolynomial.ext + rw [CPolynomial.trim_eq] at htrim + simpa using htrim + have hnormModulus : + (CPolynomial.ofArray (CPolynomial.Raw.monicNormalize pMonic.val)).toPoly = + pMonic.toPoly := by + rw [raw_monicNormalize_toPoly_eq_normalize] + rw [CPolynomial.ofArray_toPoly] + exact hpMonicPoly.normalize_eq_self + have hwitnessMod : + witness %ₘ pMonic.toPoly = + (((Polynomial.X : Polynomial F) ^ ctx.q - Polynomial.X) %ₘ pMonic.toPoly) := by + dsimp [witness] + have h := raw_xPowSubXModWith_toPoly_modByMonic M D hmod ctx.q + rwa [hnormModulus] at h + have hpMonic : + pMonic.toPoly ∣ (Polynomial.X : Polynomial F) ^ ctx.q - Polynomial.X - witness := by + rw [← Polynomial.modByMonic_eq_zero_iff_dvd hpMonicPoly] + rw [Polynomial.sub_modByMonic] + rw [← hwitnessMod] + simp + simpa [sub_eq_add_neg, add_assoc, add_left_comm, add_comm] using + dvd_add (hgLeft.trans hpMonic) hgRight + +private theorem raw_gcdMonicWithFuel_trim_ne_zero_of_left {F : Type*} + [Field F] [BEq F] [LawfulBEq F] : + ∀ fuel (p q : CPolynomial.Raw F), p.trim ≠ (0 : CPolynomial.Raw F) → + (CPolynomial.Raw.gcdMonicWithFuel fuel p q).trim ≠ 0 := by + intro fuel + induction fuel with + | zero => + intro p q hp + unfold CPolynomial.Raw.gcdMonicWithFuel + rw [raw_monicNormalize_trim] + exact raw_monicNormalize_ne_zero_of_trim_ne_zero hp + | succ fuel ih => + intro p q hp + rw [CPolynomial.Raw.gcdMonicWithFuel] + by_cases hqzero : q.trim == (0 : CPolynomial.Raw F) + · rw [if_pos hqzero] + rw [raw_monicNormalize_trim] + apply raw_monicNormalize_ne_zero_of_trim_ne_zero + simpa [CPolynomial.Raw.Trim.trim_twice] using hp + · have hqzeroNe : ¬q.trim = (0 : CPolynomial.Raw F) := by + intro hqtrim0 + exact hqzero (by simp [hqtrim0]) + rw [if_neg hqzero] + apply ih + intro hqtrim0 + rw [CPolynomial.Raw.Trim.trim_twice q] at hqtrim0 + exact hqzeroNe hqtrim0 + +/-- Monic normalization preserves nonzeroness. -/ +theorem monicNormalize_ne_zero_of_ne_zero {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + {p : CPolynomial F} (hp : p ≠ 0) : + CPolynomial.monicNormalize p ≠ 0 := by + intro hzero + unfold CPolynomial.monicNormalize at hzero + have hval := congrArg Subtype.val hzero + simp [CPolynomial.ofArray] at hval + have hpraw : p.val.trim ≠ (0 : CPolynomial.Raw F) := by + intro hpraw0 + apply hp + apply CPolynomial.ext + rw [CPolynomial.trim_eq] at hpraw0 + simpa using hpraw0 + rw [raw_monicNormalize_trim] at hval + exact raw_monicNormalize_ne_zero_of_trim_ne_zero hpraw hval + +/-- The monic gcd of a nonzero left operand is nonzero. -/ +theorem gcdMonic_ne_zero_of_left {F : Type*} [Field F] [BEq F] [LawfulBEq F] + {p q : CPolynomial F} (hp : p ≠ 0) : + CPolynomial.gcdMonic p q ≠ 0 := by + intro hzero + unfold CPolynomial.gcdMonic at hzero + have hval := congrArg Subtype.val hzero + simp [CPolynomial.ofArray] at hval + have hpraw : p.val.trim ≠ (0 : CPolynomial.Raw F) := by + intro hpraw0 + apply hp + apply CPolynomial.ext + rw [CPolynomial.trim_eq] at hpraw0 + simpa using hpraw0 + unfold CPolynomial.Raw.gcdMonic at hval + exact raw_gcdMonicWithFuel_trim_ne_zero_of_left _ p.val q.val hpraw hval + +/-- The finite-field root product of a nonzero polynomial is nonzero. -/ +theorem finiteFieldRootProductWith_ne_zero_of_ne_zero {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (M : CPolynomial.Raw.MulContext F) (D : CPolynomial.Raw.ModContext F) + (ctx : FiniteFieldContext F) {p : CPolynomial F} + (hp : p ≠ 0) : + finiteFieldRootProductWith M D ctx p ≠ 0 := by + intro hzeroProduct + unfold finiteFieldRootProductWith at hzeroProduct + have hval := congrArg Subtype.val hzeroProduct + simp [CPolynomial.ofArray] at hval + have hpraw : p.val.trim ≠ (0 : CPolynomial.Raw F) := by + intro hpraw0 + apply hp + apply CPolynomial.ext + rw [CPolynomial.trim_eq] at hpraw0 + simpa using hpraw0 + unfold CPolynomial.Raw.Roots.FiniteField.finiteFieldRootProductWith at hval + by_cases hpempty : p.val = (#[] : CPolynomial.Raw F) + · apply hp + apply CPolynomial.ext + simpa using hpempty + · simp [hpempty, CPolynomial.trim_eq] at hval + unfold CPolynomial.Raw.gcdMonic at hval + exact raw_gcdMonicWithFuel_trim_ne_zero_of_left + _ (CPolynomial.Raw.monicNormalize p.val) + (CPolynomial.Raw.xPowSubXModWith M D ctx.q (CPolynomial.Raw.monicNormalize p.val)) + (by + rw [raw_monicNormalize_trim] + exact raw_monicNormalize_ne_zero_of_trim_ne_zero hpraw) hval + +end FiniteField + +end Roots + +end CPolynomial + +end CompPoly diff --git a/CompPoly/Univariate/Roots/SmoothSubgroup.lean b/CompPoly/Univariate/Roots/SmoothSubgroup.lean new file mode 100644 index 00000000..8e88e42a --- /dev/null +++ b/CompPoly/Univariate/Roots/SmoothSubgroup.lean @@ -0,0 +1,14 @@ +/- +Copyright (c) 2026 CompPoly Contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ + +import CompPoly.Univariate.Roots.SmoothSubgroup.Basic +import CompPoly.Univariate.Roots.SmoothSubgroup.Correctness + +/-! +# Smooth Multiplicative-Subgroup Root Splitting + +Public module for the smooth cyclic subgroup splitter. +-/ diff --git a/CompPoly/Univariate/Roots/SmoothSubgroup/Basic.lean b/CompPoly/Univariate/Roots/SmoothSubgroup/Basic.lean new file mode 100644 index 00000000..8b4bb4cb --- /dev/null +++ b/CompPoly/Univariate/Roots/SmoothSubgroup/Basic.lean @@ -0,0 +1,225 @@ +/- +Copyright (c) 2026 CompPoly Contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ + +import CompPoly.Univariate.BatchEval.Context +import CompPoly.Univariate.Roots.Splitter +import Mathlib.GroupTheory.OrderOfElement + +/-! +# Smooth Multiplicative-Subgroup Linear-Factor Splitting + +Executable splitter for field-root products over finite fields whose +multiplicative group admits a smooth cyclic refinement schedule, following the +subgroup-refinement root-finding method of [MOV92]. + +The generic root pipeline consumes a `LinearFactorProductSplitter`. This module +provides a contract-bearing smooth context plus an adapter from that context to +the shared splitter interface. + +## References + +* [Menezes, A. J., van Oorschot, P. C., and Vanstone, S. A., *Subgroup + Refinement Algorithms for Root Finding in GF(q)*][MOV92] +-/ + +namespace CompPoly + +namespace CPolynomial + +namespace Roots + +namespace FiniteField + +/-- +Splitter-input predicate for a smooth cyclic splitter. + +This predicate records the mathematical input contract for the splitter. The +field-root pipeline proves it for `gcd(p, X^q - X)` values before using the +splitter completeness theorem. +-/ +def smoothSplitterInput {F : Type*} [Field F] + (q : Nat) (_generator : F) (_schedule : Array Nat) (p : CPolynomial F) : Prop := + p ≠ 0 ∧ ∀ a : F, CPolynomial.eval a p = 0 → a ^ q = a + +/-- Package the executable smooth splitter with its field and schedule facts. -/ +structure SmoothCyclicRootContext (F : Type*) [Field F] [BEq F] [LawfulBEq F] where + q : Nat + generator : F + schedule : Array Nat + leafEvaluator : BatchEvalContext F + validInput : CPolynomial F → Prop + card_eq : Nat.card F = q + generator_order : orderOf generator = q - 1 + schedule_complete : + schedule.toList.foldl (fun order ell ↦ order / ell) (q - 1) = 1 + splitLinearFactorsWith : + CPolynomial.Raw.MulContext F → + CPolynomial.Raw.ModContext F → + CPolynomial F → Array (CPolynomial F) + sound : + ∀ M D p factor, + factor ∈ (splitLinearFactorsWith M D p).toList → + IsLinearFactor factor + complete : + ∀ M D p a, + validInput p → + p ≠ 0 → + CPolynomial.eval a p = 0 → + ∃ factor, + factor ∈ (splitLinearFactorsWith M D p).toList ∧ + IsLinearRootFactorCandidate factor a + +/-- Ordered elements of the coset `alpha * ` with the supplied order. -/ +def smoothCosetPoints {F : Type*} [Field F] + (alpha gamma : F) (order : Nat) : Array F := + Array.ofFn fun j : Fin order ↦ alpha * gamma ^ j.val + +/-- Emit linear factors for the points whose evaluated value is zero. -/ +def linearFactorsFromLeafValues {F : Type*} [Field F] [BEq F] [LawfulBEq F] + (points values : Array F) : Array (CPolynomial F) := + points.zipIdx.foldl + (fun factors ⟨point, idx⟩ ↦ + if values.getD idx 1 == 0 then + factors.push (CPolynomial.linearFactor point) + else + factors) + #[] + +/-- Leaf extraction by evaluating all elements of the current coset. -/ +def smoothLeafLinearFactors {F : Type*} [Field F] [BEq F] [LawfulBEq F] + (E : BatchEvalContext F) (alpha gamma : F) (order : Nat) + (p : CPolynomial F) : Array (CPolynomial F) := + let points := smoothCosetPoints alpha gamma order + linearFactorsFromLeafValues points (E.evalBatchWith p points) + +/-- Schedule-driven nonzero-root refinement inside one multiplicative coset. -/ +def smoothCosetLinearFactorsWithSchedule {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (M : CPolynomial.Raw.MulContext F) (D : CPolynomial.Raw.ModContext F) + (E : BatchEvalContext F) : + List Nat → Nat → F → F → CPolynomial F → Array (CPolynomial F) + | [], order, alpha, gamma, p => + let p := CPolynomial.monicNormalize p + if p == 0 || p == 1 then + #[] + else if isRepresentedLinearFactor p then + #[p] + else + smoothLeafLinearFactors E alpha gamma order p + | ell :: rest, order, alpha, gamma, p => + let p := CPolynomial.monicNormalize p + if p == 0 || p == 1 then + #[] + else if isRepresentedLinearFactor p then + #[p] + else if ell ≤ 1 then + smoothCosetLinearFactorsWithSchedule M D E rest order alpha gamma p + else if ell = order then + smoothLeafLinearFactors E alpha gamma order p + else + let childOrder := order / ell + let tau := gamma ^ childOrder + let xPow := xPowModWith M D p childOrder + (List.range ell).foldl + (fun factors j ↦ + let beta := alpha ^ childOrder * tau ^ j + let witness := xPow - CPolynomial.C beta + let child := CPolynomial.monicNormalize (CPolynomial.gcdMonic p witness) + if child == 0 || child == 1 then + factors + else + factors ++ + smoothCosetLinearFactorsWithSchedule M D E rest childOrder + (alpha * gamma ^ j) (gamma ^ ell) child) + #[] + +/-- Nonzero root extraction by smooth multiplicative-subgroup refinement. -/ +def smoothNonzeroLinearFactorsWith {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (M : CPolynomial.Raw.MulContext F) (D : CPolynomial.Raw.ModContext F) + (E : BatchEvalContext F) (q : Nat) (generator : F) (schedule : Array Nat) + (p : CPolynomial F) : Array (CPolynomial F) := + smoothCosetLinearFactorsWithSchedule M D E schedule.toList (q - 1) + (1 : F) generator p + +/-- Smooth linear-factor splitting algorithm, including separate handling of root `0`. -/ +def smoothLinearFactorsAlgorithmWith {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (M : CPolynomial.Raw.MulContext F) (D : CPolynomial.Raw.ModContext F) + (E : BatchEvalContext F) (q : Nat) (generator : F) (schedule : Array Nat) + (p : CPolynomial F) : Array (CPolynomial F) := + let p := CPolynomial.monicNormalize p + if p == 0 || p == 1 then + #[] + else + let zeroSplit : Array (CPolynomial F) × CPolynomial F := + if p.coeff 0 == 0 then + (#[CPolynomial.linearFactor (0 : F)], CPolynomial.monicNormalize (CPolynomial.divX p)) + else + (#[], p) + zeroSplit.1 ++ + smoothNonzeroLinearFactorsWith M D E q generator schedule zeroSplit.2 + +/-- Build a smooth cyclic root context from executable constants and proof fields. -/ +def smoothCyclicRootContextOf {F : Type*} [Field F] [BEq F] [LawfulBEq F] + (q : Nat) (generator : F) (schedule : Array Nat) + (leafEvaluator : BatchEvalContext F) + (validInput : CPolynomial F → Prop) + (card_eq : Nat.card F = q) + (generator_order : orderOf generator = q - 1) + (schedule_complete : + schedule.toList.foldl (fun order ell ↦ order / ell) (q - 1) = 1) + (sound : + ∀ M D p factor, + factor ∈ + (smoothLinearFactorsAlgorithmWith M D leafEvaluator q generator schedule p).toList → + IsLinearFactor factor) + (complete : + ∀ M D p a, + validInput p → + p ≠ 0 → + CPolynomial.eval a p = 0 → + ∃ factor, + factor ∈ + (smoothLinearFactorsAlgorithmWith M D leafEvaluator q generator schedule + p).toList ∧ + IsLinearRootFactorCandidate factor a) : + SmoothCyclicRootContext F where + q := q + generator := generator + schedule := schedule + leafEvaluator := leafEvaluator + validInput := validInput + card_eq := card_eq + generator_order := generator_order + schedule_complete := schedule_complete + splitLinearFactorsWith := fun M D ↦ + smoothLinearFactorsAlgorithmWith M D leafEvaluator q generator schedule + sound := sound + complete := complete + +/-- Adapt a smooth cyclic context to the generic splitter interface. -/ +def smoothLinearFactorProductSplitterWith {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (M : CPolynomial.Raw.MulContext F) (D : CPolynomial.Raw.ModContext F) + (ctx : SmoothCyclicRootContext F) : + LinearFactorProductSplitter F where + splitLinearFactors := fun _qArg p ↦ ctx.splitLinearFactorsWith M D p + validInput := fun _qArg p ↦ ctx.validInput p + sound := by + intro _qArg p factor h + exact ctx.sound M D p factor h + complete := by + intro _qArg p a hvalid hp hroot + exact ctx.complete M D p a hvalid hp hroot + +end FiniteField + +end Roots + +end CPolynomial + +end CompPoly diff --git a/CompPoly/Univariate/Roots/SmoothSubgroup/Correctness.lean b/CompPoly/Univariate/Roots/SmoothSubgroup/Correctness.lean new file mode 100644 index 00000000..33834bed --- /dev/null +++ b/CompPoly/Univariate/Roots/SmoothSubgroup/Correctness.lean @@ -0,0 +1,774 @@ +/- +Copyright (c) 2026 CompPoly Contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ + +import CompPoly.Univariate.Roots.Correctness +import CompPoly.Univariate.Roots.RootProduct +import CompPoly.Univariate.Roots.SmoothSubgroup.Basic +import Mathlib.Algebra.Group.Subgroup.Finite + +/-! +# Smooth Multiplicative-Subgroup Splitter Correctness + +Correctness theorems for the smooth cyclic splitter, including executable +contracts and adapter theorems. +-/ + +namespace CompPoly + +namespace CPolynomial + +namespace Roots + +namespace FiniteField + +/-- Field-root products satisfy the generic smooth-splitter input predicate. -/ +theorem finiteFieldRootProductWith_smoothSplitterInput {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (M : CPolynomial.Raw.MulContext F) (D : CPolynomial.Raw.ModContext F) + (ctx : FiniteFieldContext F) (generator : F) (schedule : Array Nat) + {p : CPolynomial F} (hp : p ≠ 0) : + smoothSplitterInput ctx.q generator schedule + (finiteFieldRootProductWith M D ctx p) := by + constructor + · exact CPolynomial.Roots.FiniteField.finiteFieldRootProductWith_ne_zero_of_ne_zero + M D ctx hp + · intro a _ha + exact ctx.frobenius_fixed a + +/-- Soundness theorem for a smooth context adapted to the splitter interface. -/ +theorem smoothLinearFactorProductSplitterWith_sound {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (M : CPolynomial.Raw.MulContext F) (D : CPolynomial.Raw.ModContext F) + (ctx : SmoothCyclicRootContext F) {q : Nat} {p factor : CPolynomial F} + (h : factor ∈ + ((smoothLinearFactorProductSplitterWith M D ctx).splitLinearFactors q p).toList) : + IsLinearFactor factor := by + exact (smoothLinearFactorProductSplitterWith M D ctx).sound q p factor h + +/-- Completeness theorem for a smooth context adapted to the splitter interface. -/ +theorem smoothLinearFactorProductSplitterWith_complete {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (M : CPolynomial.Raw.MulContext F) (D : CPolynomial.Raw.ModContext F) + (ctx : SmoothCyclicRootContext F) {q : Nat} {p : CPolynomial F} {a : F} + (hvalid : (smoothLinearFactorProductSplitterWith M D ctx).validInput q p) + (hp : p ≠ 0) (hroot : CPolynomial.eval a p = 0) : + ∃ factor, + factor ∈ + ((smoothLinearFactorProductSplitterWith M D ctx).splitLinearFactors q p).toList ∧ + IsLinearRootFactorCandidate factor a := by + exact (smoothLinearFactorProductSplitterWith M D ctx).complete q p a hvalid hp hroot + +/-- Zero-root extraction is sound for the emitted `X` factor. -/ +theorem smooth_zero_root_factor_sound {F : Type*} [Field F] [BEq F] [LawfulBEq F] : + IsLinearRootFactorCandidate (CPolynomial.linearFactor (0 : F)) (0 : F) := by + have hC0 : CPolynomial.C (0 : F) = 0 := by + apply (CPolynomial.eq_iff_coeff).2 + intro i + simp + rw [CPolynomial.linearFactor, neg_zero, hC0, CPolynomial.zero_add] + simp [IsLinearRootFactorCandidate, IsLinearFactor, CPolynomial.X, CPolynomial.Raw.X, + CPolynomial.coeff, CPolynomial.Raw.coeff] + +/-- Leaf extraction emits only represented nonconstant linear factors. -/ +private theorem linearFactorsFromLeafValues_sound {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (points values : Array F) {factor : CPolynomial F} + (h : factor ∈ (linearFactorsFromLeafValues points values).toList) : + IsLinearFactor factor := by + unfold linearFactorsFromLeafValues at h + rw [Array.foldl_zipIdx_eq_foldl_toList_zipIdx] at h + have haux : ∀ (xs : List (F × Nat)) (acc : Array (CPolynomial F)), + (∀ factor, factor ∈ acc.toList → IsLinearFactor factor) → + factor ∈ (xs.foldl + (fun factors x ↦ + if values.getD x.2 1 == 0 then + factors.push (CPolynomial.linearFactor x.1) + else + factors) acc).toList → + IsLinearFactor factor := by + intro xs + induction xs with + | nil => + intro acc hacc hmem + exact hacc factor hmem + | cons x xs ih => + intro acc hacc hmem + simp only [List.foldl_cons] at hmem + refine ih (if values.getD x.2 1 == 0 then + acc.push (CPolynomial.linearFactor x.1) + else + acc) ?_ hmem + intro fac hfac + by_cases hcond : (values.getD x.2 1 == 0) = true + · rw [if_pos hcond] at hfac + by_cases hlast : fac = CPolynomial.linearFactor x.1 + · rw [hlast] + exact linearFactor_isLinearFactor x.1 + · apply hacc + simpa [hlast] using hfac + · rw [if_neg hcond] at hfac + exact hacc fac hfac + exact haux points.toList.zipIdx #[] (by simp) h + +/-- A point whose batch value is zero contributes its explicit linear factor. -/ +private theorem mem_linearFactorsFromLeafValues_of_get_eq_zero {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (points values : Array F) {idx : Nat} (hidx : idx < points.size) + (hval : values.getD idx 1 = 0) : + CPolynomial.linearFactor points[idx] ∈ + (linearFactorsFromLeafValues points values).toList := by + unfold linearFactorsFromLeafValues + rw [Array.foldl_zipIdx_eq_foldl_toList_zipIdx] + have htarget : (points[idx], idx) ∈ points.toList.zipIdx := by + have hex : ∃ x ∈ points.toList.zipIdx, x = (points[idx], idx) := by + rw [List.exists_mem_zipIdx'] + refine ⟨idx, ?_, ?_⟩ + · simpa using hidx + · simp [Array.getElem_toList] + rcases hex with ⟨x, hxmem, hxeq⟩ + simpa [hxeq] using hxmem + have haux : ∀ (xs : List (F × Nat)) (acc : Array (CPolynomial F)), + CPolynomial.linearFactor points[idx] ∈ acc.toList ∨ (points[idx], idx) ∈ xs → + CPolynomial.linearFactor points[idx] ∈ (xs.foldl + (fun factors x ↦ + if values.getD x.2 1 == 0 then + factors.push (CPolynomial.linearFactor x.1) + else + factors) acc).toList := by + intro xs + induction xs with + | nil => + intro acc h + simpa using h + | cons x xs ih => + intro acc h + simp only [List.foldl_cons] + apply ih + rcases h with hacc | hx + · left + by_cases hcond : (values.getD x.2 1 == 0) = true + · rw [if_pos hcond] + simp [hacc] + · rw [if_neg hcond] + exact hacc + · simp at hx + rcases hx with hx | hx + · subst x + left + have hbeq : (values.getD idx 1 == 0) = true := by + simp [hval] + rw [if_pos hbeq] + simp + · right + exact hx + exact haux points.toList.zipIdx #[] (Or.inr htarget) + +/-- Smooth leaf extraction emits only represented nonconstant linear factors. -/ +private theorem smoothLeafLinearFactors_sound {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (E : BatchEvalContext F) (alpha gamma : F) (order : Nat) (p : CPolynomial F) + {factor : CPolynomial F} + (h : factor ∈ (smoothLeafLinearFactors E alpha gamma order p).toList) : + IsLinearFactor factor := by + unfold smoothLeafLinearFactors at h + exact linearFactorsFromLeafValues_sound _ _ h + +/-- Smooth leaf extraction is complete for roots in the enumerated coset. -/ +theorem smoothLeafLinearFactors_complete {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (E : BatchEvalContext F) {alpha gamma a : F} {order : Nat} {p : CPolynomial F} + (hcoset : ∃ k : Nat, k < order ∧ a = alpha * gamma ^ k) + (hroot : CPolynomial.eval a p = 0) : + ∃ factor, + factor ∈ (smoothLeafLinearFactors E alpha gamma order p).toList ∧ + IsLinearRootFactorCandidate factor a := by + rcases hcoset with ⟨k, hk, ha⟩ + refine ⟨CPolynomial.linearFactor a, ?_, linearFactor_isRootFactorCandidate a⟩ + unfold smoothLeafLinearFactors + let points := smoothCosetPoints alpha gamma order + have hsize : k < points.size := by + simp [points, smoothCosetPoints] + exact hk + have hpoint : points[k] = a := by + simp [points, smoothCosetPoints, ha] + have hval : (E.evalBatchWith p points).getD k 1 = 0 := by + rw [E.correct] + unfold CPolynomial.evalBatch + rw [Array.getD_map_of_lt points (fun x ↦ CPolynomial.eval x p) 1 hsize] + rw [hpoint] + exact hroot + have hmem := mem_linearFactorsFromLeafValues_of_get_eq_zero points (E.evalBatchWith p points) + hsize hval + simpa [hpoint] using hmem + +/-- Schedule-driven smooth coset recursion emits only represented nonconstant linear factors. -/ +theorem smoothCosetLinearFactorsWithSchedule_sound {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (M : CPolynomial.Raw.MulContext F) (D : CPolynomial.Raw.ModContext F) + (E : BatchEvalContext F) : + ∀ (schedule : List Nat) (order : Nat) (alpha gamma : F) (p factor : CPolynomial F), + factor ∈ + (smoothCosetLinearFactorsWithSchedule M D E schedule order alpha gamma p).toList → + IsLinearFactor factor := by + intro schedule + induction schedule with + | nil => + intro order alpha gamma p factor h + unfold smoothCosetLinearFactorsWithSchedule at h + by_cases hzero : (CPolynomial.monicNormalize p == 0 || + CPolynomial.monicNormalize p == 1) = true + · rw [if_pos hzero] at h + simp at h + · rw [if_neg hzero] at h + by_cases hlin : isRepresentedLinearFactor (CPolynomial.monicNormalize p) = true + · rw [if_pos hlin] at h + simp at h + rcases h with rfl + exact isRepresentedLinearFactor_sound hlin + · rw [if_neg hlin] at h + exact smoothLeafLinearFactors_sound E alpha gamma order (CPolynomial.monicNormalize p) h + | cons ell rest ih => + intro order alpha gamma p factor h + unfold smoothCosetLinearFactorsWithSchedule at h + by_cases hzero : (CPolynomial.monicNormalize p == 0 || + CPolynomial.monicNormalize p == 1) = true + · rw [if_pos hzero] at h + simp at h + · rw [if_neg hzero] at h + by_cases hlin : isRepresentedLinearFactor (CPolynomial.monicNormalize p) = true + · rw [if_pos hlin] at h + simp at h + rcases h with rfl + exact isRepresentedLinearFactor_sound hlin + · rw [if_neg hlin] at h + by_cases hellsmall : ell ≤ 1 + · rw [if_pos hellsmall] at h + exact ih order alpha gamma (CPolynomial.monicNormalize p) factor h + · rw [if_neg hellsmall] at h + by_cases helleq : ell = order + · rw [if_pos helleq] at h + exact smoothLeafLinearFactors_sound E alpha gamma order + (CPolynomial.monicNormalize p) h + · rw [if_neg helleq] at h + let childOrder := order / ell + let tau := gamma ^ childOrder + let xPow := xPowModWith M D (CPolynomial.monicNormalize p) childOrder + have hfold : ∀ (js : List Nat) (acc : Array (CPolynomial F)), + (∀ factor, factor ∈ acc.toList → IsLinearFactor factor) → + factor ∈ (js.foldl + (fun factors j ↦ + let beta := alpha ^ childOrder * tau ^ j + let witness := xPow - CPolynomial.C beta + let child := CPolynomial.monicNormalize + (CPolynomial.gcdMonic (CPolynomial.monicNormalize p) witness) + if child == 0 || child == 1 then + factors + else + factors ++ smoothCosetLinearFactorsWithSchedule M D E rest childOrder + (alpha * gamma ^ j) (gamma ^ ell) child) acc).toList → + IsLinearFactor factor := by + intro js + induction js with + | nil => + intro acc hacc hmem + exact hacc factor hmem + | cons j js ihjs => + intro acc hacc hmem + simp only [List.foldl_cons] at hmem + let beta := alpha ^ childOrder * tau ^ j + let witness := xPow - CPolynomial.C beta + let child := CPolynomial.monicNormalize + (CPolynomial.gcdMonic (CPolynomial.monicNormalize p) witness) + refine ihjs + (if child == 0 || child == 1 then + acc + else + acc ++ smoothCosetLinearFactorsWithSchedule M D E rest childOrder + (alpha * gamma ^ j) (gamma ^ ell) child) ?_ hmem + intro fac hfac + by_cases hskip : (child == 0 || child == 1) = true + · rw [if_pos hskip] at hfac + exact hacc fac hfac + · rw [if_neg hskip] at hfac + simp at hfac + rcases hfac with haccmem | hrec + · exact hacc fac (by simpa using haccmem) + · exact ih childOrder (alpha * gamma ^ j) (gamma ^ ell) child fac + (by simpa using hrec) + exact hfold (List.range ell) #[] (by simp) h + +/-- The top-level smooth splitter emits only represented nonconstant linear factors. -/ +theorem smoothLinearFactorsAlgorithmWith_sound {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (M : CPolynomial.Raw.MulContext F) (D : CPolynomial.Raw.ModContext F) + (E : BatchEvalContext F) (q : Nat) (generator : F) (schedule : Array Nat) + {p factor : CPolynomial F} + (h : factor ∈ (smoothLinearFactorsAlgorithmWith M D E q generator schedule p).toList) : + IsLinearFactor factor := by + unfold smoothLinearFactorsAlgorithmWith at h + by_cases hzero : (CPolynomial.monicNormalize p == 0 || + CPolynomial.monicNormalize p == 1) = true + · rw [if_pos hzero] at h + simp at h + · rw [if_neg hzero] at h + by_cases hconst : (((CPolynomial.monicNormalize p).coeff 0 == 0) = true) + · rw [if_pos hconst] at h + unfold smoothNonzeroLinearFactorsWith at h + simp at h + rcases h with hzeroFactor | hnonzero + · rcases hzeroFactor with rfl + exact linearFactor_isLinearFactor 0 + · exact smoothCosetLinearFactorsWithSchedule_sound M D E schedule.toList (q - 1) + 1 generator (CPolynomial.monicNormalize (CPolynomial.divX (CPolynomial.monicNormalize p))) + factor (by simpa using hnonzero) + · rw [if_neg hconst] at h + unfold smoothNonzeroLinearFactorsWith at h + simp at h + exact smoothCosetLinearFactorsWithSchedule_sound M D E schedule.toList (q - 1) + 1 generator (CPolynomial.monicNormalize p) factor (by simpa using h) + +/-- A smooth coset split maps the residue class `k % ell` to the child-coset equation. -/ +theorem smooth_coset_split_root_partition_mod {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + {alpha gamma : F} {order ell k : Nat} + (hgamma_pow : gamma ^ order = 1) + (hell_dvd : ell ∣ order) : + (alpha * gamma ^ k) ^ (order / ell) = + alpha ^ (order / ell) * (gamma ^ (order / ell)) ^ (k % ell) := by + let n := order / ell + have hellmul : ell * n = order := by + dsimp [n] + rw [Nat.mul_comm] + exact Nat.div_mul_cancel hell_dvd + have hgamma_reduce : gamma ^ (k * n) = gamma ^ ((k % ell) * n) := by + have hkdecomp : k = ell * (k / ell) + k % ell := (Nat.div_add_mod k ell).symm + calc + gamma ^ (k * n) = gamma ^ ((ell * (k / ell) + k % ell) * n) := by + rw [← hkdecomp] + _ = gamma ^ ((k / ell) * order + (k % ell) * n) := by + congr 1 + have hterm : (ell * (k / ell)) * n = (k / ell) * order := by + calc + (ell * (k / ell)) * n = (k / ell) * (ell * n) := by ac_rfl + _ = (k / ell) * order := by rw [hellmul] + rw [Nat.add_mul, hterm] + _ = gamma ^ ((k % ell) * n) := by + rw [pow_add] + rw [Nat.mul_comm (k / ell) order] + rw [pow_mul, hgamma_pow] + simp + calc + (alpha * gamma ^ k) ^ n = alpha ^ n * (gamma ^ k) ^ n := by rw [mul_pow] + _ = alpha ^ n * gamma ^ (k * n) := by rw [pow_mul] + _ = alpha ^ n * gamma ^ ((k % ell) * n) := by rw [hgamma_reduce] + _ = alpha ^ n * gamma ^ (n * (k % ell)) := by rw [Nat.mul_comm (k % ell) n] + _ = alpha ^ n * (gamma ^ n) ^ (k % ell) := by rw [pow_mul] + +/-- A smooth coset split partitions roots according to the child-coset equation. -/ +theorem smooth_coset_split_root_partition {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + {alpha gamma x : F} {order ell : Nat} + (hgamma_order : orderOf gamma = order) + (hell_dvd : ell ∣ order) (hell_pos : 0 < ell) + (hx : ∃ k : Nat, k < order ∧ x = alpha * gamma ^ k) : + ∃ j : Nat, j < ell ∧ + x ^ (order / ell) = alpha ^ (order / ell) * (gamma ^ (order / ell)) ^ j := by + rcases hx with ⟨k, _hklt, rfl⟩ + refine ⟨k % ell, Nat.mod_lt k hell_pos, ?_⟩ + apply smooth_coset_split_root_partition_mod + · rw [← hgamma_order] + exact pow_orderOf_eq_one gamma + · exact hell_dvd + +/-- A finite-field generator of order `#F - 1` enumerates all nonzero elements. -/ +theorem exists_generator_pow_of_order_eq_card_sub_one {F : Type*} + [Field F] [Finite F] [DecidableEq F] + {q : Nat} {generator a : F} + (hcard : Nat.card F = q) + (hgen : orderOf generator = q - 1) (ha : a ≠ 0) : + ∃ k : Nat, k < q - 1 ∧ a = generator ^ k := by + have hq_gt_one : 1 < q := by + rw [← hcard] + exact Finite.one_lt_card + have hgen_ne_zero : generator ≠ 0 := by + intro hzero + have hpow : generator ^ (q - 1) = 1 := by + rw [← hgen] + exact pow_orderOf_eq_one generator + rw [hzero] at hpow + have hqsub : 0 < q - 1 := Nat.sub_pos_of_lt hq_gt_one + rcases Nat.exists_eq_add_of_lt hqsub with ⟨m, hm⟩ + rw [hm] at hpow + simp at hpow + let gu : Fˣ := Units.mk0 generator hgen_ne_zero + let au : Fˣ := Units.mk0 a ha + have hgu_order : orderOf (gu : Fˣ) = Nat.card Fˣ := by + rw [← orderOf_units (y := gu)] + simp [gu, hgen, Nat.card_units, hcard] + have htop : Subgroup.zpowers gu = ⊤ := by + rw [← Subgroup.card_eq_iff_eq_top (Subgroup.zpowers gu)] + rw [Nat.card_zpowers, hgu_order] + have hau_z : au ∈ Subgroup.zpowers gu := by + rw [htop] + exact Subgroup.mem_top au + have hau_range := (mem_zpowers_iff_mem_range_orderOf (x := gu) (y := au)).mp hau_z + rcases Finset.mem_image.mp hau_range with ⟨k, hk, hkpow⟩ + refine ⟨k, ?_, ?_⟩ + · have hklt : k < orderOf (gu : Fˣ) := by + simpa using Finset.mem_range.mp hk + rwa [hgu_order, Nat.card_units, hcard] at hklt + · have hval := congrArg (fun u : Fˣ ↦ (u : F)) hkpow + simpa [gu, au] using hval.symm + +/-- Roots of `p` are contained in the explicit coset `alpha * ` of order `order`. -/ +def SmoothCosetInvariant {F : Type*} [Field F] + (alpha gamma : F) (order : Nat) (p : CPolynomial F) : Prop := + alpha ≠ 0 ∧ + 0 < order ∧ + orderOf gamma = order ∧ + ∀ x : F, CPolynomial.eval x p = 0 → + ∃ k : Nat, k < order ∧ x = alpha * gamma ^ k + +/-- Schedule recursion preserves the smooth coset invariant. -/ +theorem smooth_schedule_recursion_preserves_coset_invariant {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (M : CPolynomial.Raw.MulContext F) (D : CPolynomial.Raw.ModContext F) + {order ell j : Nat} {alpha gamma : F} {p : CPolynomial F} + (hchild_alpha_ne_zero : alpha * gamma ^ j ≠ 0) + (hchild_order_pos : 0 < order / ell) + (hchild_generator_order : orderOf (gamma ^ ell) = order / ell) + (hchild_roots : + ∀ x : F, + CPolynomial.eval x + (CPolynomial.monicNormalize + (CPolynomial.gcdMonic p + (xPowModWith M D p (order / ell) - + CPolynomial.C + (alpha ^ (order / ell) * (gamma ^ (order / ell)) ^ j)))) = 0 → + ∃ k : Nat, k < order / ell ∧ + x = (alpha * gamma ^ j) * (gamma ^ ell) ^ k) : + SmoothCosetInvariant + (alpha * gamma ^ j) + (gamma ^ ell) + (order / ell) + (CPolynomial.monicNormalize + (CPolynomial.gcdMonic p + (xPowModWith M D p (order / ell) - + CPolynomial.C (alpha ^ (order / ell) * (gamma ^ (order / ell)) ^ j)))) := by + exact ⟨hchild_alpha_ne_zero, hchild_order_pos, hchild_generator_order, hchild_roots⟩ + +/-- Schedule factors divide the current coset order along executable smooth recursion. -/ +def SmoothScheduleDivides : List Nat → Nat → Prop + | [], _ => True + | ell :: rest, order => + if ell ≤ 1 then + SmoothScheduleDivides rest order + else if ell = order then + True + else + ell ∣ order ∧ SmoothScheduleDivides rest (order / ell) + +instance instDecidableSmoothScheduleDivides (schedule : List Nat) (order : Nat) : + Decidable (SmoothScheduleDivides schedule order) := by + induction schedule generalizing order with + | nil => + unfold SmoothScheduleDivides + infer_instance + | cons ell rest ih => + unfold SmoothScheduleDivides + by_cases hellsmall : ell ≤ 1 + · rw [if_pos hellsmall] + exact ih order + · rw [if_neg hellsmall] + by_cases helleq : ell = order + · rw [if_pos helleq] + infer_instance + · rw [if_neg helleq] + infer_instance + +/-- The declared smooth schedule reaches singleton cosets. -/ +theorem smooth_schedule_reaches_singleton {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (ctx : SmoothCyclicRootContext F) : + ctx.schedule.toList.foldl (fun order ell ↦ order / ell) (ctx.q - 1) = 1 := + ctx.schedule_complete + +/-- Path completeness for the schedule-driven smooth coset recursion. -/ +theorem smoothCosetLinearFactorsWithSchedule_complete {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (M : CPolynomial.Raw.MulContext F) (D : CPolynomial.Raw.ModContext F) + (E : BatchEvalContext F) : + ∀ (schedule : List Nat) (order : Nat) (alpha gamma : F) (p : CPolynomial F) (a : F), + SmoothScheduleDivides schedule order → + gamma ^ order = 1 → + p ≠ 0 → + CPolynomial.eval a p = 0 → + (∃ k : Nat, k < order ∧ a = alpha * gamma ^ k) → + ∃ factor, + factor ∈ + (smoothCosetLinearFactorsWithSchedule M D E schedule order alpha gamma + p).toList ∧ + IsLinearRootFactorCandidate factor a := by + intro schedule + induction schedule with + | nil => + intro order alpha gamma p a _hsched _hgamma hp hroot hcoset + unfold smoothCosetLinearFactorsWithSchedule + let p' := CPolynomial.monicNormalize p + have hp' : p' ≠ 0 := monicNormalize_ne_zero_of_ne_zero hp + have hroot' : CPolynomial.eval a p' = 0 := (monicNormalize_root_iff hp).2 hroot + by_cases hzero : (p' == 0 || p' == 1) = true + · have hcases : p' = 0 ∨ p' = 1 := by + simpa [p'] using hzero + rcases hcases with h0 | h1 + · exact (hp' h0).elim + · rw [h1] at hroot' + rw [eval_one a] at hroot' + exact (one_ne_zero hroot').elim + · rw [if_neg hzero] + by_cases hlin : isRepresentedLinearFactor p' = true + · rw [if_pos hlin] + refine ⟨p', ?_, representedLinearFactor_candidate_of_root hlin hroot'⟩ + simp [p'] + · rw [if_neg hlin] + exact smoothLeafLinearFactors_complete E hcoset hroot' + | cons ell rest ih => + intro order alpha gamma p a hsched hgamma hp hroot hcoset + unfold smoothCosetLinearFactorsWithSchedule + let p' := CPolynomial.monicNormalize p + have hp' : p' ≠ 0 := monicNormalize_ne_zero_of_ne_zero hp + have hroot' : CPolynomial.eval a p' = 0 := (monicNormalize_root_iff hp).2 hroot + by_cases hzero : (p' == 0 || p' == 1) = true + · have hcases : p' = 0 ∨ p' = 1 := by + simpa [p'] using hzero + rcases hcases with h0 | h1 + · exact (hp' h0).elim + · rw [h1] at hroot' + rw [eval_one a] at hroot' + exact (one_ne_zero hroot').elim + · rw [if_neg hzero] + by_cases hlin : isRepresentedLinearFactor p' = true + · rw [if_pos hlin] + refine ⟨p', ?_, representedLinearFactor_candidate_of_root hlin hroot'⟩ + simp [p'] + · rw [if_neg hlin] + by_cases hellsmall : ell ≤ 1 + · rw [if_pos hellsmall] + have hschedRest : SmoothScheduleDivides rest order := by + simpa [SmoothScheduleDivides, hellsmall] using hsched + exact ih order alpha gamma p' a hschedRest hgamma hp' hroot' hcoset + · rw [if_neg hellsmall] + by_cases helleq : ell = order + · rw [if_pos helleq] + exact smoothLeafLinearFactors_complete E hcoset hroot' + · rw [if_neg helleq] + have hschedRest : ell ∣ order ∧ SmoothScheduleDivides rest (order / ell) := by + simpa [SmoothScheduleDivides, hellsmall, helleq] using hsched + rcases hschedRest with ⟨hell_dvd, hschedChild⟩ + rcases hcoset with ⟨k, hk, haeq⟩ + have hell_gt_one : 1 < ell := by omega + have hell_pos : 0 < ell := by omega + let childOrder := order / ell + let tau := gamma ^ childOrder + let xPow := xPowModWith M D p' childOrder + let j := k % ell + have hjlt : j < ell := Nat.mod_lt k hell_pos + have hjmem : j ∈ List.range ell := by + simpa [j] using hjlt + have hellmul : ell * childOrder = order := by + dsimp [childOrder] + rw [Nat.mul_comm] + exact Nat.div_mul_cancel hell_dvd + have hchildGamma : (gamma ^ ell) ^ childOrder = 1 := by + rw [← pow_mul, hellmul, hgamma] + have hchildCoset : ∃ t : Nat, t < childOrder ∧ + a = (alpha * gamma ^ j) * (gamma ^ ell) ^ t := by + refine ⟨k / ell, ?_, ?_⟩ + · dsimp [childOrder] + rw [Nat.div_lt_iff_lt_mul hell_pos] + rw [Nat.mul_comm] + exact lt_of_lt_of_eq hk hellmul.symm + · rw [haeq] + dsimp [j] + have hkdecomp : k % ell + ell * (k / ell) = k := + Nat.mod_add_div k ell + calc + alpha * gamma ^ k = + alpha * gamma ^ (k % ell + ell * (k / ell)) := by + rw [hkdecomp] + _ = alpha * (gamma ^ (k % ell) * gamma ^ (ell * (k / ell))) := by + rw [pow_add] + _ = alpha * gamma ^ (k % ell) * (gamma ^ ell) ^ (k / ell) := by + rw [pow_mul] + ring + let beta := alpha ^ childOrder * tau ^ j + let witness := xPow - CPolynomial.C beta + let child := CPolynomial.monicNormalize (CPolynomial.gcdMonic p' witness) + have hpoweq : a ^ childOrder = beta := by + rw [haeq] + dsimp [beta, tau, j, childOrder] + exact smooth_coset_split_root_partition_mod hgamma hell_dvd + have hwitnessRoot : CPolynomial.eval a witness = 0 := by + dsimp [witness, xPow, beta] + rw [eval_sub, eval_xPowModWith_eq_pow M D hroot' childOrder, eval_C, hpoweq] + ring + have hgcdRoot : CPolynomial.eval a (CPolynomial.gcdMonic p' witness) = 0 := + gcdMonic_root_of_left_right hroot' hwitnessRoot + have hgcdNe : CPolynomial.gcdMonic p' witness ≠ 0 := + gcdMonic_ne_zero_of_left hp' + have hchildNe : child ≠ 0 := monicNormalize_ne_zero_of_ne_zero hgcdNe + have hchildRoot : CPolynomial.eval a child = 0 := + (monicNormalize_root_iff hgcdNe).2 hgcdRoot + have hchildNotOne : child ≠ 1 := by + intro h1 + rw [h1] at hchildRoot + rw [eval_one a] at hchildRoot + exact (one_ne_zero hchildRoot).elim + have hskip : ¬ (child == 0 || child == 1) = true := by + intro hskipTrue + have hcases : child = 0 ∨ child = 1 := by + simpa [child] using hskipTrue + rcases hcases with h0 | h1 + · exact hchildNe h0 + · exact hchildNotOne h1 + have hrec := ih childOrder (alpha * gamma ^ j) (gamma ^ ell) child a + hschedChild hchildGamma hchildNe hchildRoot hchildCoset + have hfold : ∀ (js : List Nat) (acc : Array (CPolynomial F)), + (∃ factor, factor ∈ acc.toList ∧ IsLinearRootFactorCandidate factor a) ∨ + j ∈ js → + ∃ factor, + factor ∈ (js.foldl + (fun factors y ↦ + let beta := alpha ^ childOrder * tau ^ y + let witness := xPow - CPolynomial.C beta + let child := CPolynomial.monicNormalize (CPolynomial.gcdMonic p' witness) + if child == 0 || child == 1 then + factors + else + factors ++ smoothCosetLinearFactorsWithSchedule M D E rest childOrder + (alpha * gamma ^ y) (gamma ^ ell) child) acc).toList ∧ + IsLinearRootFactorCandidate factor a := by + intro js + induction js with + | nil => + intro acc h + rcases h with hacc | hjnil + · exact hacc + · simp at hjnil + | cons y ys ihys => + intro acc h + simp only [List.foldl_cons] + apply ihys + rcases h with hacc | hy + · left + rcases hacc with ⟨factor, hmem, hcand⟩ + refine ⟨factor, ?_, hcand⟩ + by_cases hskipY : (CPolynomial.monicNormalize + (CPolynomial.gcdMonic p' + (xPow - CPolynomial.C (alpha ^ childOrder * tau ^ y))) == 0 || + CPolynomial.monicNormalize + (CPolynomial.gcdMonic p' + (xPow - CPolynomial.C (alpha ^ childOrder * tau ^ y))) == 1) = true + · rw [if_pos hskipY] + exact hmem + · rw [if_neg hskipY] + let childY := CPolynomial.monicNormalize + (CPolynomial.gcdMonic p' + (xPow - CPolynomial.C (alpha ^ childOrder * tau ^ y))) + let tail := smoothCosetLinearFactorsWithSchedule M D E rest childOrder + (alpha * gamma ^ y) (gamma ^ ell) childY + simpa [childY, tail] using Array.mem_append_left tail + (by simpa using hmem) + · simp at hy + rcases hy with hyj | hyrest + · subst y + left + rcases hrec with ⟨factor, hmem, hcand⟩ + refine ⟨factor, ?_, hcand⟩ + rw [if_neg hskip] + simpa [child, witness, beta] using + Array.mem_append_right acc (by simpa using hmem) + · right + exact hyrest + exact hfold (List.range ell) #[] (Or.inr hjmem) + +/-- Completeness of the top-level smooth linear-factor splitter. -/ +theorem smoothLinearFactorsAlgorithmWith_complete {F : Type*} + [Field F] [Finite F] [DecidableEq F] [BEq F] [LawfulBEq F] + (M : CPolynomial.Raw.MulContext F) (D : CPolynomial.Raw.ModContext F) + (E : BatchEvalContext F) (q : Nat) (generator : F) (schedule : Array Nat) + (hcard : Nat.card F = q) + (hgenerator : orderOf generator = q - 1) + (hschedule : SmoothScheduleDivides schedule.toList (q - 1)) + {p : CPolynomial F} {a : F} + (hp : p ≠ 0) (hroot : CPolynomial.eval a p = 0) : + ∃ factor, + factor ∈ (smoothLinearFactorsAlgorithmWith M D E q generator schedule p).toList ∧ + IsLinearRootFactorCandidate factor a := by + unfold smoothLinearFactorsAlgorithmWith + let p' := CPolynomial.monicNormalize p + have hp' : p' ≠ 0 := monicNormalize_ne_zero_of_ne_zero hp + have hroot' : CPolynomial.eval a p' = 0 := (monicNormalize_root_iff hp).2 hroot + by_cases hzero : (p' == 0 || p' == 1) = true + · have hcases : p' = 0 ∨ p' = 1 := by + simpa [p'] using hzero + rcases hcases with h0 | h1 + · exact (hp' h0).elim + · rw [h1] at hroot' + rw [eval_one a] at hroot' + exact (one_ne_zero hroot').elim + · rw [if_neg hzero] + by_cases ha0 : a = 0 + · subst a + have hcoeff : p'.coeff 0 = 0 := by + simpa [eval_zero_eq_coeff_zero] using hroot' + have hconst : ((p'.coeff 0 == 0) = true) := by + simp [hcoeff] + rw [if_pos hconst] + refine ⟨CPolynomial.linearFactor (0 : F), ?_, linearFactor_isRootFactorCandidate 0⟩ + unfold smoothNonzeroLinearFactorsWith + simp + · have hgenPow : generator ^ (q - 1) = 1 := by + rw [← hgenerator] + exact pow_orderOf_eq_one generator + have hcoset : ∃ k : Nat, k < q - 1 ∧ a = (1 : F) * generator ^ k := by + rcases exists_generator_pow_of_order_eq_card_sub_one hcard hgenerator ha0 with + ⟨k, hk, ha⟩ + exact ⟨k, hk, by simp [ha]⟩ + by_cases hconst : ((p'.coeff 0 == 0) = true) + · rw [if_pos hconst] + have hcoeff : p'.coeff 0 = 0 := by simpa using hconst + have hdivRoot : CPolynomial.eval a (CPolynomial.divX p') = 0 := + eval_divX_eq_zero_of_ne_zero_root ha0 hcoeff hroot' + have hdivNe : CPolynomial.divX p' ≠ 0 := + divX_ne_zero_of_ne_zero_coeff_zero hp' hcoeff + have hchildRoot : + CPolynomial.eval a (CPolynomial.monicNormalize (CPolynomial.divX p')) = 0 := + (monicNormalize_root_iff hdivNe).2 hdivRoot + have hchildNe : CPolynomial.monicNormalize (CPolynomial.divX p') ≠ 0 := + monicNormalize_ne_zero_of_ne_zero hdivNe + rcases smoothCosetLinearFactorsWithSchedule_complete M D E schedule.toList (q - 1) + (1 : F) generator (CPolynomial.monicNormalize (CPolynomial.divX p')) a + hschedule hgenPow hchildNe hchildRoot hcoset with ⟨factor, hmem, hcand⟩ + refine ⟨factor, ?_, hcand⟩ + unfold smoothNonzeroLinearFactorsWith + simpa [p'] using Array.mem_append_right (#[CPolynomial.linearFactor (0 : F)]) + (by simpa using hmem) + · rw [if_neg hconst] + rcases smoothCosetLinearFactorsWithSchedule_complete M D E schedule.toList (q - 1) + (1 : F) generator p' a hschedule hgenPow hp' hroot' hcoset with + ⟨factor, hmem, hcand⟩ + refine ⟨factor, ?_, hcand⟩ + unfold smoothNonzeroLinearFactorsWith + simpa using hmem + +end FiniteField + +end Roots + +end CPolynomial + +end CompPoly diff --git a/CompPoly/Univariate/Roots/Splitter.lean b/CompPoly/Univariate/Roots/Splitter.lean new file mode 100644 index 00000000..97c99b05 --- /dev/null +++ b/CompPoly/Univariate/Roots/Splitter.lean @@ -0,0 +1,57 @@ +/- +Copyright (c) 2026 CompPoly Contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ + +import CompPoly.Univariate.Raw.Modular +import CompPoly.Univariate.Roots.Context + +/-! +# Linear-Factor Splitter Helpers + +Shared executable helpers for finite-field linear-factor splitters. +-/ + +namespace CompPoly + +namespace CPolynomial + +namespace Roots + +namespace FiniteField + +/-- Boolean recognizer for represented nonconstant linear factors. -/ +def isRepresentedLinearFactor {F : Type*} [Field F] [BEq F] + (p : CPolynomial F) : Bool := + decide (p.val.size ≤ 2) && !(p.coeff 1 == 0) + +/-- The represented-linear Boolean recognizer is sound. -/ +theorem isRepresentedLinearFactor_sound {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + {p : CPolynomial F} (h : isRepresentedLinearFactor p = true) : + IsLinearFactor p := by + unfold isRepresentedLinearFactor at h + simp at h + simpa [IsLinearFactor, CPolynomial.coeff, CPolynomial.Raw.coeff] using h + +/-- Return `p` as a singleton array if it is represented as a nonconstant linear factor. -/ +def representedLinearFactorArray {F : Type*} [Field F] [BEq F] + (p : CPolynomial F) : Array (CPolynomial F) := + if isRepresentedLinearFactor p then #[p] else #[] + +/-- `X^exponent mod modulus`, lifted back to canonical polynomials. -/ +def xPowModWith {F : Type*} [Field F] [BEq F] [LawfulBEq F] + (M : CPolynomial.Raw.MulContext F) (D : CPolynomial.Raw.ModContext F) + (modulus : CPolynomial F) (exponent : Nat) : CPolynomial F := + CPolynomial.ofArray + (CPolynomial.Raw.powModWith M D modulus.val + (CPolynomial.Raw.X : CPolynomial.Raw F) exponent) + +end FiniteField + +end Roots + +end CPolynomial + +end CompPoly diff --git a/CompPoly/Univariate/ToPoly/Core.lean b/CompPoly/Univariate/ToPoly/Core.lean index df15dcf4..c0ba9f71 100644 --- a/CompPoly/Univariate/ToPoly/Core.lean +++ b/CompPoly/Univariate/ToPoly/Core.lean @@ -206,6 +206,12 @@ theorem trim_toImpl [LawfulBEq R] (p : R[X]) : p.toImpl.trim = p.toImpl := by end Raw +/-- `ofArray` preserves the raw polynomial's `toPoly` image. -/ +theorem ofArray_toPoly [LawfulBEq R] (p : CPolynomial.Raw R) : + (CPolynomial.ofArray p).toPoly = p.toPoly := by + unfold CPolynomial.ofArray + exact Raw.toPoly_trim + /-- On canonical polynomials, `toImpl` is a left-inverse of `toPoly`. This shows `toPoly` is a bijection from `CPolynomial R` to `Polynomial R`. -/ diff --git a/CompPoly/Univariate/ToPoly/Impl.lean b/CompPoly/Univariate/ToPoly/Impl.lean index 97aabc1d..c1e8ada6 100644 --- a/CompPoly/Univariate/ToPoly/Impl.lean +++ b/CompPoly/Univariate/ToPoly/Impl.lean @@ -51,6 +51,11 @@ theorem eval_toPoly [BEq R] [LawfulBEq R] (x : R) (p : CPolynomial R) : · rw [ Raw.eval_toPoly_eq_eval ]; rfl · convert Raw.eval_toPoly_eq_eval x p.val +/-- Evaluation of a constant computable polynomial. -/ +theorem eval_C [BEq R] [LawfulBEq R] (a c : R) : + CPolynomial.eval a (CPolynomial.C c) = c := by + rw [CPolynomial.eval_toPoly, CPolynomial.C_toPoly, Polynomial.eval_C] + /-- Raw.eval₂ is correct wrt the Mathlib spec. -/ theorem Raw.eval₂_toPoly {S : Type*} [Semiring S] (f : R →+* S) (x : S) (p : CPolynomial.Raw R) : @@ -78,6 +83,13 @@ theorem coeff_toPoly [BEq R] [LawfulBEq R] (p : CPolynomial R) (i : ℕ) : unfold toPoly coeff simp [Raw.coeff_toPoly] +/-- Evaluation at zero returns the constant coefficient. -/ +theorem eval_zero_eq_coeff_zero [BEq R] [LawfulBEq R] + (p : CPolynomial R) : CPolynomial.eval 0 p = p.coeff 0 := by + rw [CPolynomial.eval_toPoly] + rw [← Polynomial.coeff_zero_eq_eval_zero p.toPoly] + exact (CPolynomial.coeff_toPoly p 0).symm + /-- CPolynomial.divX is correct wrt the Mathlib spec. -/ theorem divX_toPoly [BEq R] [LawfulBEq R] (p : CPolynomial R) : (divX p).toPoly = p.toPoly.divX := by @@ -262,6 +274,52 @@ theorem eval_ext Polynomial.eq_zero_of_natDegree_lt_card_of_eval_eq_zero' r.toPoly T heval_zero (lt_of_le_of_lt hrPolyDeg hTcard) +/-- Evaluation preserves subtraction. -/ +theorem eval_sub [Ring R] [BEq R] [LawfulBEq R] + (a : R) (p q : CPolynomial R) : + CPolynomial.eval a (p - q) = CPolynomial.eval a p - CPolynomial.eval a q := by + rw [CPolynomial.eval_toPoly, CPolynomial.toPoly_sub, Polynomial.eval_sub, + ← CPolynomial.eval_toPoly, ← CPolynomial.eval_toPoly] + +/-- Evaluation of the constant one computable polynomial. -/ +theorem eval_one [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] + (a : R) : CPolynomial.eval a (1 : CPolynomial R) = 1 := by + rw [CPolynomial.eval_toPoly, CPolynomial.toPoly_one, Polynomial.eval_one] + +/-- Dividing by `X` preserves a nonzero root when the constant coefficient vanishes. -/ +theorem eval_divX_eq_zero_of_ne_zero_root [Field R] [BEq R] [LawfulBEq R] + {p : CPolynomial R} {a : R} (ha : a ≠ 0) + (hcoeff : p.coeff 0 = 0) (hroot : CPolynomial.eval a p = 0) : + CPolynomial.eval a (CPolynomial.divX p) = 0 := by + have hdecomp := CPolynomial.X_mul_divX_add (p := p) + have hroot' : + CPolynomial.eval a (CPolynomial.X * CPolynomial.divX p + CPolynomial.C (p.coeff 0)) = + 0 := by + rw [← hdecomp] + exact hroot + rw [CPolynomial.eval_toPoly, CPolynomial.toPoly_add, CPolynomial.toPoly_mul, + CPolynomial.X_toPoly, CPolynomial.C_toPoly, Polynomial.eval_add, Polynomial.eval_mul, + Polynomial.eval_X, Polynomial.eval_C, ← CPolynomial.eval_toPoly] at hroot' + rw [hcoeff] at hroot' + simp at hroot' + rcases hroot' with hzero | hdivRoot + · exact (ha hzero).elim + · exact hdivRoot + +/-- If a nonzero polynomial has zero constant coefficient, its quotient by `X` is nonzero. -/ +theorem divX_ne_zero_of_ne_zero_coeff_zero [Field R] [BEq R] [LawfulBEq R] + {p : CPolynomial R} (hp : p ≠ 0) (hcoeff : p.coeff 0 = 0) : + CPolynomial.divX p ≠ 0 := by + intro hdiv + apply hp + have hC0 : CPolynomial.C (0 : R) = 0 := by + apply (CPolynomial.eq_iff_coeff).2 + intro i + simp + rw [CPolynomial.X_mul_divX_add (p := p), hdiv, hcoeff] + rw [hC0] + simp + namespace Raw theorem eval_sub_C_mul_X_pow_trim_eq_self_of_eval_eq_zero diff --git a/tests/CompPolyTests.lean b/tests/CompPolyTests.lean index 9d4818d5..c8cb52dc 100644 --- a/tests/CompPolyTests.lean +++ b/tests/CompPolyTests.lean @@ -28,4 +28,6 @@ import CompPolyTests.Univariate.NTT.FastMul import CompPolyTests.Univariate.NTT.Forward import CompPolyTests.Univariate.NTT.Inverse import CompPolyTests.Univariate.Raw +import CompPolyTests.Univariate.Roots.Enumeration +import CompPolyTests.Univariate.Roots.FiniteField import CompPolyTests.Univariate.ToPoly diff --git a/tests/CompPolyTests/Univariate/Roots/Enumeration.lean b/tests/CompPolyTests/Univariate/Roots/Enumeration.lean new file mode 100644 index 00000000..72718c7c --- /dev/null +++ b/tests/CompPolyTests/Univariate/Roots/Enumeration.lean @@ -0,0 +1,84 @@ +/- +Copyright (c) 2026 CompPoly Contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ + +import CompPoly.Univariate.Roots.Enumeration +import Mathlib.Algebra.Field.ZMod + +/-! +# Exhaustive Enumeration Root Tests + +Executable coverage for lazy finite-field enumeration adapters over small +prime fields. +-/ + +namespace CompPolyTests + +open CompPoly +open CompPoly.CPolynomial.Roots.FiniteField + +namespace Univariate.Roots.Enumeration + +abbrev F3 := ZMod 3 +abbrev F5 := ZMod 5 + +instance : Fact (Nat.Prime 3) := + ⟨by decide⟩ + +instance : Fact (Nat.Prime 5) := + ⟨by decide⟩ + +private def f3Elements : Array F3 := + #[0, 1, 2] + +private theorem f3Elements_complete : ContainsAllFieldElements f3Elements := by + unfold ContainsAllFieldElements + intro a + fin_cases a <;> decide + +private def f3Enumeration : FieldEnumeration F3 := + fieldEnumerationOfArray f3Elements f3Elements_complete + +private def f3RootsPolynomial : CPolynomial F3 := + CPolynomial.linearFactor (0 : F3) * CPolynomial.linearFactor (2 : F3) + +private def f3Roots : Array F3 := + rootsInFieldByEnumeration f3Enumeration f3RootsPolynomial + +#guard f3Roots == #[(0 : F3), (2 : F3)] + +private def f5Elements : Array F5 := + #[0, 1, 2, 3, 4] + +private theorem f5Elements_complete : ContainsAllFieldElements f5Elements := by + unfold ContainsAllFieldElements + intro a + fin_cases a <;> decide + +private def f5Enumeration : FieldEnumeration F5 := + fieldEnumerationOfArray f5Elements f5Elements_complete + +private def f5RootsPolynomial : CPolynomial F5 := + CPolynomial.linearFactor (1 : F5) * + CPolynomial.linearFactor (4 : F5) * + CPolynomial.linearFactor (4 : F5) + +private def f5Roots : Array F5 := + rootsInFieldByEnumeration f5Enumeration f5RootsPolynomial + +#guard f5Roots == #[(1 : F5), (4 : F5)] + +private def f5EnumerationSplitter : LinearFactorProductSplitter F5 := + enumeratingLinearFactorProductSplitter f5Enumeration + +private def f5EnumerationFactors : Array (CPolynomial F5) := + f5EnumerationSplitter.splitLinearFactors 5 f5RootsPolynomial + +#guard (CPolynomial.rootsFromLinearFactors f5RootsPolynomial f5EnumerationFactors).contains (1 : F5) +#guard (CPolynomial.rootsFromLinearFactors f5RootsPolynomial f5EnumerationFactors).contains (4 : F5) + +end Univariate.Roots.Enumeration + +end CompPolyTests diff --git a/tests/CompPolyTests/Univariate/Roots/FiniteField.lean b/tests/CompPolyTests/Univariate/Roots/FiniteField.lean new file mode 100644 index 00000000..b00c6fb7 --- /dev/null +++ b/tests/CompPolyTests/Univariate/Roots/FiniteField.lean @@ -0,0 +1,87 @@ +/- +Copyright (c) 2026 CompPoly Contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ + +import CompPoly.Univariate.Roots.Correctness +import Mathlib.Algebra.Field.ZMod + +/-! +# Finite-Field Univariate Root Tests + +Focused executable coverage for the generic finite-field root backend. +-/ + +namespace CompPolyTests + +open CompPoly +open CompPoly.CPolynomial.Roots.FiniteField + +namespace Univariate.Roots.FiniteField + +abbrev F3 := ZMod 3 +abbrev F5 := ZMod 5 + +instance : Fact (Nat.Prime 3) := + ⟨by decide⟩ + +instance : Fact (Nat.Prime 5) := + ⟨by decide⟩ + +private def f3Ctx : FiniteFieldContext F3 where + q := 3 + finite := by infer_instance + card_eq := by + simp [F3, Nat.card_eq_fintype_card, ZMod.card] + frobenius_fixed := by decide + +private def f5Ctx : FiniteFieldContext F5 where + q := 5 + finite := by infer_instance + card_eq := by + simp [F5, Nat.card_eq_fintype_card, ZMod.card] + frobenius_fixed := by decide + +private def f3Repeated : CPolynomial F3 := + CPolynomial.linearFactor (1 : F3) * + CPolynomial.linearFactor (2 : F3) * + CPolynomial.linearFactor (2 : F3) + +private def f3SquarefreeProduct : CPolynomial F3 := + CPolynomial.linearFactor (1 : F3) * + CPolynomial.linearFactor (2 : F3) + +private def f3RootProduct : CPolynomial F3 := + finiteFieldRootProduct f3Ctx f3Repeated + +#guard CPolynomial.evalHorner (1 : F3) f3RootProduct == 0 +#guard CPolynomial.evalHorner (2 : F3) f3RootProduct == 0 +#guard CPolynomial.evalHorner (0 : F3) f3RootProduct != 0 +#guard f3RootProduct == f3SquarefreeProduct + +private def f3NoRoot : CPolynomial F3 := + CPolynomial.ofArray #[(1 : F3), 0, 1] + +#guard finiteFieldRootProduct f3Ctx f3NoRoot == 1 + +private def f5MultipleRoots : CPolynomial F5 := + CPolynomial.linearFactor (1 : F5) * + CPolynomial.linearFactor (1 : F5) * + CPolynomial.linearFactor (3 : F5) + +private def f5LinearProduct : CPolynomial F5 := + CPolynomial.linearFactor (1 : F5) * + CPolynomial.linearFactor (3 : F5) + +private def f5RootProduct : CPolynomial F5 := + finiteFieldRootProduct f5Ctx f5MultipleRoots + +#guard CPolynomial.evalHorner (1 : F5) f5RootProduct == 0 +#guard CPolynomial.evalHorner (3 : F5) f5RootProduct == 0 +#guard CPolynomial.evalHorner (2 : F5) f5RootProduct != 0 +#guard f5RootProduct == f5LinearProduct + +end Univariate.Roots.FiniteField + +end CompPolyTests From f19ada60f55766860257070cf4ed203973e34b38 Mon Sep 17 00:00:00 2001 From: Valerii Huhnin Date: Mon, 8 Jun 2026 10:26:51 +0000 Subject: [PATCH 06/14] Add Roth-Ruckenstein bivariate root search --- CompPoly.lean | 19 + .../Bivariate/GuruswamiSudan/Compose.lean | 13 + .../Bivariate/GuruswamiSudan/Context.lean | 110 ++ CompPoly/Bivariate/GuruswamiSudan/Hasse.lean | 13 + .../Bivariate/GuruswamiSudan/Monomials.lean | 14 + .../Bivariate/GuruswamiSudan/Polynomial.lean | 330 +++++ .../GuruswamiSudan/PolynomialCorrectness.lean | 1104 +++++++++++++++++ CompPoly/Bivariate/GuruswamiSudan/Root.lean | 13 + .../Bivariate/GuruswamiSudan/Root/Basic.lean | 13 + .../Bivariate/GuruswamiSudan/Root/Common.lean | 64 + .../GuruswamiSudan/Root/Common/Lemmas.lean | 592 +++++++++ .../GuruswamiSudan/Root/FieldRoots.lean | 54 + .../Root/FieldRoots/FiniteField.lean | 86 ++ .../Root/FieldRoots/KoalaBear.lean | 241 ++++ .../Root/RothRuckenstein/Algorithm.lean | 142 +++ .../Root/RothRuckenstein/Correctness.lean | 913 ++++++++++++++ .../Root/RothRuckenstein/Lemmas.lean | 640 ++++++++++ .../Root/ShiftedSubstitution.lean | 67 + .../Root/ShiftedSubstitution/Lemmas.lean | 123 ++ CompPoly/Bivariate/GuruswamiSudan/Util.lean | 24 + tests/CompPolyTests.lean | 4 + .../Bivariate/GuruswamiSudan/Compose.lean | 37 + .../Bivariate/GuruswamiSudan/Hasse.lean | 35 + .../Root/FieldRoots/KoalaBear.lean | 113 ++ .../GuruswamiSudan/Root/RothRuckenstein.lean | 90 ++ 25 files changed, 4854 insertions(+) create mode 100644 CompPoly/Bivariate/GuruswamiSudan/Compose.lean create mode 100644 CompPoly/Bivariate/GuruswamiSudan/Context.lean create mode 100644 CompPoly/Bivariate/GuruswamiSudan/Hasse.lean create mode 100644 CompPoly/Bivariate/GuruswamiSudan/Monomials.lean create mode 100644 CompPoly/Bivariate/GuruswamiSudan/Polynomial.lean create mode 100644 CompPoly/Bivariate/GuruswamiSudan/PolynomialCorrectness.lean create mode 100644 CompPoly/Bivariate/GuruswamiSudan/Root.lean create mode 100644 CompPoly/Bivariate/GuruswamiSudan/Root/Basic.lean create mode 100644 CompPoly/Bivariate/GuruswamiSudan/Root/Common.lean create mode 100644 CompPoly/Bivariate/GuruswamiSudan/Root/Common/Lemmas.lean create mode 100644 CompPoly/Bivariate/GuruswamiSudan/Root/FieldRoots.lean create mode 100644 CompPoly/Bivariate/GuruswamiSudan/Root/FieldRoots/FiniteField.lean create mode 100644 CompPoly/Bivariate/GuruswamiSudan/Root/FieldRoots/KoalaBear.lean create mode 100644 CompPoly/Bivariate/GuruswamiSudan/Root/RothRuckenstein/Algorithm.lean create mode 100644 CompPoly/Bivariate/GuruswamiSudan/Root/RothRuckenstein/Correctness.lean create mode 100644 CompPoly/Bivariate/GuruswamiSudan/Root/RothRuckenstein/Lemmas.lean create mode 100644 CompPoly/Bivariate/GuruswamiSudan/Root/ShiftedSubstitution.lean create mode 100644 CompPoly/Bivariate/GuruswamiSudan/Root/ShiftedSubstitution/Lemmas.lean create mode 100644 CompPoly/Bivariate/GuruswamiSudan/Util.lean create mode 100644 tests/CompPolyTests/Bivariate/GuruswamiSudan/Compose.lean create mode 100644 tests/CompPolyTests/Bivariate/GuruswamiSudan/Hasse.lean create mode 100644 tests/CompPolyTests/Bivariate/GuruswamiSudan/Root/FieldRoots/KoalaBear.lean create mode 100644 tests/CompPolyTests/Bivariate/GuruswamiSudan/Root/RothRuckenstein.lean diff --git a/CompPoly.lean b/CompPoly.lean index aa13c9a6..f6fa1dc8 100644 --- a/CompPoly.lean +++ b/CompPoly.lean @@ -3,6 +3,25 @@ import CompPoly.Bivariate.CMvEquiv import CompPoly.Bivariate.Deriv import CompPoly.Bivariate.Factor import CompPoly.Bivariate.FactorMonic +import CompPoly.Bivariate.GuruswamiSudan.Compose +import CompPoly.Bivariate.GuruswamiSudan.Context +import CompPoly.Bivariate.GuruswamiSudan.Hasse +import CompPoly.Bivariate.GuruswamiSudan.Monomials +import CompPoly.Bivariate.GuruswamiSudan.Polynomial +import CompPoly.Bivariate.GuruswamiSudan.PolynomialCorrectness +import CompPoly.Bivariate.GuruswamiSudan.Root +import CompPoly.Bivariate.GuruswamiSudan.Root.Basic +import CompPoly.Bivariate.GuruswamiSudan.Root.Common +import CompPoly.Bivariate.GuruswamiSudan.Root.Common.Lemmas +import CompPoly.Bivariate.GuruswamiSudan.Root.FieldRoots +import CompPoly.Bivariate.GuruswamiSudan.Root.FieldRoots.FiniteField +import CompPoly.Bivariate.GuruswamiSudan.Root.FieldRoots.KoalaBear +import CompPoly.Bivariate.GuruswamiSudan.Root.RothRuckenstein.Algorithm +import CompPoly.Bivariate.GuruswamiSudan.Root.RothRuckenstein.Correctness +import CompPoly.Bivariate.GuruswamiSudan.Root.RothRuckenstein.Lemmas +import CompPoly.Bivariate.GuruswamiSudan.Root.ShiftedSubstitution +import CompPoly.Bivariate.GuruswamiSudan.Root.ShiftedSubstitution.Lemmas +import CompPoly.Bivariate.GuruswamiSudan.Util import CompPoly.Bivariate.ToPoly import CompPoly.Data.Array.Lemmas import CompPoly.Data.Classes.DCast diff --git a/CompPoly/Bivariate/GuruswamiSudan/Compose.lean b/CompPoly/Bivariate/GuruswamiSudan/Compose.lean new file mode 100644 index 00000000..c373b7f1 --- /dev/null +++ b/CompPoly/Bivariate/GuruswamiSudan/Compose.lean @@ -0,0 +1,13 @@ +/- +Copyright (c) 2026 CompPoly Contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ + +import CompPoly.Bivariate.GuruswamiSudan.Hasse + +/-! +# Guruswami-Sudan Bivariate Composition + +Re-exports `CBivariate.composeY` and related coefficient/truncated evaluators. +-/ diff --git a/CompPoly/Bivariate/GuruswamiSudan/Context.lean b/CompPoly/Bivariate/GuruswamiSudan/Context.lean new file mode 100644 index 00000000..fab888f0 --- /dev/null +++ b/CompPoly/Bivariate/GuruswamiSudan/Context.lean @@ -0,0 +1,110 @@ +/- +Copyright (c) 2026 CompPoly Contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ + +import CompPoly.Bivariate.Deriv +import CompPoly.Bivariate.GuruswamiSudan.Compose + +/-! +# Guruswami-Sudan Backend Contexts + +Explicit executable contexts for the CompPoly Guruswami-Sudan core. The +contexts package replaceable operations together with the contracts used by the +public correctness theorems. +-/ + +namespace CompPoly + +namespace GuruswamiSudan + +/-- Parameters for the CompPoly interpolation step. -/ +structure GSInterpParams where + messageDegree : Nat + multiplicity : Nat + weightedDegreeBound : Nat +deriving Repr, BEq, DecidableEq + +/-- The GS weighted degree uses weights `(1, messageDegree - 1)`. -/ +def yWeight (params : GSInterpParams) : Nat := + params.messageDegree - 1 + +/-- `p.degree < k`, treating the zero polynomial as degree `bot`. -/ +def degreeLt {F : Type*} [Zero F] (p : CPolynomial F) (k : Nat) : Prop := + p.degree < (k : WithBot Nat) + +/-- Packed input points have no duplicate `x`-coordinates. -/ +def DistinctXCoordinates {F : Type*} (points : Array (Prod F F)) : Prop := + (points.toList.map fun point ↦ point.1).Nodup + +/-- Semantic interpolation witness used by backend contracts and core +completeness statements. -/ +def ValidInterpolationWitness {F : Type*} + [CommSemiring F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (points : Array (Prod F F)) (params : GSInterpParams) (Q : CBivariate F) : Prop := + Q ≠ 0 ∧ + CBivariate.natWeightedDegree Q 1 (yWeight params) ≤ params.weightedDegreeBound ∧ + ∀ point, point ∈ points.toList → + CBivariate.hasMultiplicity Q params.multiplicity point.1 point.2 + +/-- Guruswami-Sudan-facing interpolation backend. + +The backend packages the executable interpolation operation together with the +contract fields used by callers, using the explicit context style used by +univariate multiplication and remainder backends. +-/ +structure GSInterpContext (F : Type*) [Field F] [BEq F] [LawfulBEq F] + [DecidableEq F] where + interpolate : Array (Prod F F) → GSInterpParams → Option (CBivariate F) + sound : + ∀ points params Q, + interpolate points params = some Q → + ValidInterpolationWitness points params Q + complete : + ∀ points params, + DistinctXCoordinates points → + (exists Q, ValidInterpolationWitness points params Q) → + exists Q, interpolate points params = some Q + +/-- Executable root finder for univariate field polynomials. + +Completeness is only required for nonzero polynomials. A zero univariate +polynomial vanishes on every field element, so an unconditional array-valued +complete root finder would have to enumerate the whole field. +-/ +structure FieldRootContext (F : Type*) [Field F] [BEq F] [LawfulBEq F] where + rootsInField : CPolynomial F → Array F + sound : + ∀ p a, + a ∈ (rootsInField p).toList → + CPolynomial.eval a p = 0 + complete : + ∀ p a, + p ≠ 0 → + CPolynomial.eval a p = 0 → + a ∈ (rootsInField p).toList + +/-- Guruswami-Sudan-facing bounded-degree root backend. + +Completeness is only required for nonzero bivariate input. The zero bivariate +polynomial has every degree-bounded univariate polynomial as a root, which is not +a finite output contract for large fields. +-/ +structure GSRootContext (F : Type*) [Field F] [BEq F] [LawfulBEq F] + [DecidableEq F] where + rootsYDegreeLt : CBivariate F → Nat → Array (CPolynomial F) + sound : + ∀ Q k p, + p ∈ (rootsYDegreeLt Q k).toList → + degreeLt p k ∧ CBivariate.composeY Q p = 0 + complete : + ∀ Q k p, + Q ≠ 0 → + degreeLt p k → + CBivariate.composeY Q p = 0 → + p ∈ (rootsYDegreeLt Q k).toList + +end GuruswamiSudan + +end CompPoly diff --git a/CompPoly/Bivariate/GuruswamiSudan/Hasse.lean b/CompPoly/Bivariate/GuruswamiSudan/Hasse.lean new file mode 100644 index 00000000..dd7faea6 --- /dev/null +++ b/CompPoly/Bivariate/GuruswamiSudan/Hasse.lean @@ -0,0 +1,13 @@ +/- +Copyright (c) 2026 CompPoly Contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ + +import CompPoly.Bivariate.GuruswamiSudan.Monomials + +/-! +# Guruswami-Sudan Hasse Derivatives + +Re-exports executable Hasse derivatives and multiplicity checks on `CBivariate`. +-/ diff --git a/CompPoly/Bivariate/GuruswamiSudan/Monomials.lean b/CompPoly/Bivariate/GuruswamiSudan/Monomials.lean new file mode 100644 index 00000000..f30d230b --- /dev/null +++ b/CompPoly/Bivariate/GuruswamiSudan/Monomials.lean @@ -0,0 +1,14 @@ +/- +Copyright (c) 2026 CompPoly Contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ + +import CompPoly.Bivariate.GuruswamiSudan.Polynomial + +/-! +# Guruswami-Sudan Bivariate Monomials + +Re-exports dense coefficient-grid and weighted-monomial helpers used by the +Guruswami-Sudan interpolation matrix. +-/ diff --git a/CompPoly/Bivariate/GuruswamiSudan/Polynomial.lean b/CompPoly/Bivariate/GuruswamiSudan/Polynomial.lean new file mode 100644 index 00000000..2e309830 --- /dev/null +++ b/CompPoly/Bivariate/GuruswamiSudan/Polynomial.lean @@ -0,0 +1,330 @@ +/- +Copyright (c) 2026 CompPoly Contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ + +import CompPoly.Bivariate.FactorMonic +import CompPoly.Bivariate.ToPoly +import CompPoly.Univariate.Deriv + +/-! +# Guruswami-Sudan Polynomial Helpers + +Reusable univariate and bivariate polynomial operations used by the +Guruswami-Sudan interpolation and root-finding kernels. +-/ + +namespace CompPoly + +namespace CPolynomial + +/-- Drop the first `n` powers of `X`, i.e. divide by `X^n` when possible and +truncate toward zero otherwise. -/ +def dropXPower {R : Type*} [Zero R] (p : CPolynomial R) : Nat → CPolynomial R + | 0 => p + | n + 1 => dropXPower (CPolynomial.divX p) n + +/-- Keep only coefficients of degree `< n`. -/ +def truncate {R : Type*} [Zero R] [BEq R] [LawfulBEq R] + (p : CPolynomial R) (n : Nat) : CPolynomial R := + ofArray (p.val.extract 0 n) + +/-- First nonzero coefficient index of a univariate polynomial, if it is +nonzero. -/ +def xAdicOrder? {R : Type*} [Zero R] [BEq R] (p : CPolynomial R) : Option Nat := + (List.range' 0 p.val.size).find? fun i ↦ !(p.coeff i == 0) + +/-- Coefficients of the inverse of a power series with known nonzero constant +coefficient, truncated to length `n`. -/ +def inverseSeriesNextCoeff {F : Type*} [Field F] + (p : CPolynomial F) (constantInv : F) (prev : Array F) (idx : Nat) : F := + if idx = 0 then + constantInv + else + -(constantInv * + (List.range idx).foldl + (fun acc i ↦ acc + prev.getD i 0 * p.coeff (idx - i)) 0) + +/-- Coefficients of the inverse of a power series with known nonzero constant +coefficient, truncated to length `n`. -/ +def inverseSeriesCoeffs {F : Type*} [Field F] + (p : CPolynomial F) (constantInv : F) (n : Nat) : Array F := + (List.range n).foldl + (fun prev idx ↦ prev.push (inverseSeriesNextCoeff p constantInv prev idx)) + #[] + +/-- Inverse of a univariate power series modulo `X^n`, if the constant term is +invertible. -/ +def inverseSeries? {F : Type*} [Field F] [BEq F] [LawfulBEq F] + (p : CPolynomial F) (n : Nat) : Option (CPolynomial F) := + if p.coeff 0 == 0 then + none + else + some (ofArray (inverseSeriesCoeffs p (p.coeff 0)⁻¹ n)) + +/-- Coefficient of `X^n` in `p * q`, computed without materializing the +product. -/ +def mulCoeff {R : Type*} [Semiring R] (p q : CPolynomial R) (n : Nat) : R := + (List.range (n + 1)).foldl + (fun acc i ↦ acc + p.coeff i * q.coeff (n - i)) + 0 + +/-- Coefficient window of `p * q`, shifted down by `low` and truncated to +`width`, computed without materializing the full product. -/ +def mulWindow {R : Type*} [Semiring R] [BEq R] [LawfulBEq R] + (p q : CPolynomial R) (low width : Nat) : CPolynomial R := + ofArray ((List.range width).map fun offset ↦ mulCoeff p q (low + offset)).toArray + +/-- Coefficient window of `p * q` computed through a raw low-product context. -/ +def mulWindowWithLowProduct {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] + (mulLow : CPolynomial.Raw.MulLowContext R) + (p q : CPolynomial R) (low width : Nat) : CPolynomial R := + ofArray + ((mulLow.mulLow (low + width) p.val q.val).extract low (low + width)) + +/-- Coefficient of `X^n` in `p^k`, computed by coefficient convolution without +materializing the intermediate powers. -/ +def powCoeff {R : Type*} [Semiring R] (p : CPolynomial R) : Nat → Nat → R + | 0, n => if n = 0 then 1 else 0 + | k + 1, n => + (List.range (n + 1)).foldl + (fun acc i ↦ acc + p.coeff i * powCoeff p k (n - i)) + 0 + +/-- Coefficient of `X^n` in `a * p^k`, computed coefficient-wise. -/ +def mulPowCoeff {R : Type*} [Semiring R] + (a p : CPolynomial R) (k n : Nat) : R := + (List.range (n + 1)).foldl + (fun acc i ↦ acc + a.coeff i * powCoeff p k (n - i)) + 0 + +end CPolynomial + +namespace CBivariate + +/-- A bivariate monomial exponent pair. -/ +structure Monomial where + xDegree : Nat + yDegree : Nat +deriving BEq, DecidableEq, Repr + +/-- One monomial contribution to a Hasse derivative. -/ +structure HasseTerm (R : Type*) where + xDegree : Nat + yDegree : Nat + coeff : R + +/-- Construct a bivariate polynomial from a coefficient grid indexed by `grid[y][x]`. -/ +def ofCoeffGrid {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] [DecidableEq R] + (grid : Array (Array R)) : CBivariate R := + Id.run do + let mut out : CBivariate R := 0 + for y in [0:grid.size] do + let row := grid.getD y #[] + for x in [0:row.size] do + out := out + monomialXY x y (row.getD x 0) + pure out + +/-- Construct a bivariate polynomial from a monomial list and parallel coefficient vector. -/ +def ofMonomialCoeffs {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] [DecidableEq R] + (monomials : Array Monomial) (coeffs : Array R) : CBivariate R := + (List.range' 0 monomials.size).foldl + (fun out col ↦ + let monomial := monomials.getD col ⟨0, 0⟩ + out + monomialXY monomial.xDegree monomial.yDegree (coeffs.getD col 0)) + 0 + +/-- Candidate monomials in the finite square used by weighted-degree enumeration. -/ +def monomialGrid (bound : Nat) : List Monomial := + (List.range (bound + 1)).flatMap fun y ↦ + (List.range (bound + 1)).map fun x ↦ ({ xDegree := x, yDegree := y } : Monomial) + +/-- Enumerate monomials inside a finite weighted-degree search rectangle. + +When both weights are positive this is the complete set of monomials with +weighted degree at most `bound`. If a weight is zero, `bound` also serves as the +finite exponent cap for that variable. +-/ +def monomialsWeightedDegreeLE (xWeight yWeight bound : Nat) : Array Monomial := + ((monomialGrid bound).filter fun m ↦ + xWeight * m.xDegree + yWeight * m.yDegree ≤ bound).toArray + +/-- Shared monomial contributions for materialized and directly evaluated Hasse derivatives. -/ +def hasseDerivativeTermList {R : Type*} [Semiring R] + (a b : Nat) (Q : CBivariate R) : List (HasseTerm R) := + (List.range' 0 Q.val.size).foldl + (fun out y ↦ + let coeffY := Q.val.coeff y + if b ≤ y then + (List.range' 0 coeffY.val.size).foldl + (fun out x ↦ + if a ≤ x then + let coeff := (Nat.choose x a : R) * (Nat.choose y b : R) * coeffY.coeff x + out ++ [⟨x - a, y - b, coeff⟩] + else out) + out + else out) + [] + +/-- Shared monomial contributions for materialized and directly evaluated Hasse derivatives. -/ +def hasseDerivativeTerms {R : Type*} [Semiring R] + (a b : Nat) (Q : CBivariate R) : Array (HasseTerm R) := + (hasseDerivativeTermList a b Q).toArray + +/-- Materialize a Hasse derivative from a list of derivative terms. -/ +def hasseDerivativeFromTerms {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] [DecidableEq R] + (terms : List (HasseTerm R)) : CBivariate R := + terms.foldl (fun out term ↦ + out + monomialXY term.xDegree term.yDegree term.coeff) 0 + +/-- Executable Hasse derivative of a bivariate polynomial. -/ +def hasseDerivative {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] [DecidableEq R] + (a b : Nat) (Q : CBivariate R) : CBivariate R := + hasseDerivativeFromTerms (hasseDerivativeTermList a b Q) + +/-- Evaluate derivative terms directly at one point. -/ +def hasseDerivativeEvalFromTerms {R : Type*} [Semiring R] + (terms : List (HasseTerm R)) (x y : R) : R := + terms.foldl (fun acc term ↦ + acc + term.coeff * x ^ term.xDegree * y ^ term.yDegree) 0 + +/-- Evaluate a Hasse derivative at one point without materializing the derivative. -/ +def hasseDerivativeEval {R : Type*} [Semiring R] + (a b : Nat) (x y : R) (Q : CBivariate R) : R := + hasseDerivativeEvalFromTerms (hasseDerivativeTermList a b Q) x y + +/-- Candidate derivative orders in the finite square used by multiplicity checks. -/ +def derivativeOrderGrid (multiplicity : Nat) : List (Nat × Nat) := + (List.range multiplicity).flatMap fun a ↦ + (List.range multiplicity).map fun b ↦ (a, b) + +/-- Derivative orders `(a, b)` with `a + b < multiplicity`. -/ +def derivativeOrders (multiplicity : Nat) : Array (Nat × Nat) := + ((derivativeOrderGrid multiplicity).filter fun order ↦ + order.1 + order.2 < multiplicity).toArray + +/-- Mathematical multiplicity constraint used by the GS interpolation specification. -/ +def HasMultiplicityAtLeast {R : Type*} [Semiring R] + (Q : CBivariate R) (x y : R) (multiplicity : Nat) : Prop := + ∀ a b, a + b < multiplicity → hasseDerivativeEval a b x y Q = 0 + +/-- Executable multiplicity check at one point. -/ +def multiplicityAtLeastBool {R : Type*} [Semiring R] [BEq R] + (Q : CBivariate R) (x y : R) (multiplicity : Nat) : Bool := + (derivativeOrders multiplicity).all fun order ↦ + hasseDerivativeEval order.1 order.2 x y Q == 0 + +/-- Mathematical batch multiplicity constraints over packed point pairs. -/ +def SatisfiesMultiplicityConstraints {R : Type*} [Semiring R] + (Q : CBivariate R) (points : Array (R × R)) (multiplicity : Nat) : Prop := + ∀ point, point ∈ points.toList → + HasMultiplicityAtLeast Q point.1 point.2 multiplicity + +/-- Executable batch multiplicity check over packed point pairs. -/ +def satisfiesMultiplicityConstraintsBool {R : Type*} [Semiring R] [BEq R] + (Q : CBivariate R) (points : Array (R × R)) (multiplicity : Nat) : Bool := + points.all fun point ↦ + multiplicityAtLeastBool Q point.1 point.2 multiplicity + +/-- Compose a bivariate polynomial with a univariate polynomial in the `Y` slot: +`Q(X, p(X))`. -/ +def composeY {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] + (Q : CBivariate R) (p : CPolynomial R) : CPolynomial R := + CPolynomial.eval p Q + +/-- Horner implementation of `Q(X, p(X))` in the outer `Y` variable. -/ +@[inline, specialize] +def composeYHorner {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] + (Q : CBivariate R) (p : CPolynomial R) : CPolynomial R := + CPolynomial.evalHorner p Q + +/-- Truncated Horner implementation of `Q(X, p(X))` in the outer `Y` +variable. + +After each Horner step, the accumulator is truncated modulo `X^n`, so callers +that only need an `X`-adic certificate do not materialize coefficients that +will be discarded immediately. -/ +@[inline, specialize] +def composeYHornerTruncated {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] + (Q : CBivariate R) (p : CPolynomial R) (n : Nat) : CPolynomial R := + (List.range Q.val.size).reverse.foldl + (fun acc y ↦ CPolynomial.truncate (acc * p + Q.val.coeff y) n) + (0 : CPolynomial R) + +/-- Coefficient of `X^depth` in `Q(X, p(X))`, computed without materializing +the whole composed polynomial. -/ +def composeYCoeff {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] + (Q : CBivariate R) (p : CPolynomial R) (depth : Nat) : R := + (List.range' 0 Q.val.size).foldl + (fun acc y ↦ acc + CPolynomial.mulPowCoeff (Q.val.coeff y) p y depth) + 0 + +/-- Formal derivative in the outer `Y` variable. -/ +def yDerivative {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] [DecidableEq R] + (Q : CBivariate R) : CBivariate R := + (List.range' 1 Q.val.size).foldl + (fun out y ↦ + let coeffY := Q.val.coeff y + (List.range' 0 coeffY.val.size).foldl + (fun out x ↦ out + monomialXY x (y - 1) ((y : R) * coeffY.coeff x)) + out) + 0 + +/-- Minimum `X`-adic order across all nonzero `Y`-coefficients of a bivariate +polynomial. -/ +def xAdicOrder? {R : Type*} [Zero R] [BEq R] (Q : CBivariate R) : Option Nat := + (List.range' 0 Q.val.size).foldl + (fun best y ↦ + match CPolynomial.xAdicOrder? (Q.val.coeff y) with + | none => best + | some order => + match best with + | none => some order + | some current => some (min current order)) + none + +/-- Divide every `Y`-coefficient by `X^n`, truncating coefficients with lower +`X`-degree to zero. -/ +def divXPower {R : Type*} [Zero R] [BEq R] [LawfulBEq R] + (Q : CBivariate R) (n : Nat) : CBivariate R := + CPolynomial.ofArray (Q.val.map fun coeff ↦ CPolynomial.dropXPower coeff n) + +/-- Keep only coefficients of `X`-degree `< n` in every `Y`-coefficient. -/ +def truncateX {R : Type*} [Zero R] [BEq R] [LawfulBEq R] + (Q : CBivariate R) (n : Nat) : CBivariate R := + CPolynomial.ofArray (Q.val.map fun coeff ↦ CPolynomial.truncate coeff n) + +/-- Strip the common `X`-adic factor from a bivariate polynomial. -/ +def stripXAdicFactor {R : Type*} [Zero R] [BEq R] [LawfulBEq R] + (Q : CBivariate R) : CBivariate R := + match xAdicOrder? Q with + | none => default + | some order => divXPower Q order + +/-- View a univariate polynomial in `X` as a bivariate polynomial constant in +`Y`. -/ +def ofYConstant {R : Type*} [Zero R] [BEq R] [LawfulBEq R] + (p : CPolynomial R) : CBivariate R := + CPolynomial.C p + +/-- View a univariate polynomial in `X` as the coefficient of `Y^y` in a +bivariate polynomial. -/ +def ofYCoefficient {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] [DecidableEq R] + (y : Nat) (p : CPolynomial R) : CBivariate R := + CPolynomial.monomial y p + +end CBivariate + +end CompPoly diff --git a/CompPoly/Bivariate/GuruswamiSudan/PolynomialCorrectness.lean b/CompPoly/Bivariate/GuruswamiSudan/PolynomialCorrectness.lean new file mode 100644 index 00000000..f394400d --- /dev/null +++ b/CompPoly/Bivariate/GuruswamiSudan/PolynomialCorrectness.lean @@ -0,0 +1,1104 @@ +/- +Copyright (c) 2026 CompPoly Contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ + +import CompPoly.Bivariate.Deriv +import CompPoly.Bivariate.GuruswamiSudan.Polynomial +import CompPoly.Data.List.Lemmas +import Mathlib.Tactic.Ring + +/-! +# Guruswami-Sudan Polynomial Correctness Lemmas + +Correctness lemmas for dense bivariate coefficient assembly, weighted-degree +enumeration, and executable Hasse derivatives. +-/ + +namespace CompPoly + +namespace CPolynomial + +/-- Coefficients past the stored canonical array are zero. -/ +theorem coeff_eq_zero_of_size_le {R : Type*} [Zero R] (p : CPolynomial R) + {i : Nat} (hi : p.val.size ≤ i) : p.coeff i = 0 := by + unfold CPolynomial.coeff CPolynomial.Raw.coeff + rw [Array.getD_eq_getD_getElem?, Array.getElem?_eq_none hi] + rfl + +/-- The natural degree of a nonzero computable polynomial lies in its support. -/ +theorem natDegree_mem_support_of_nonzero {R : Type*} [Zero R] [BEq R] [LawfulBEq R] + {p : CPolynomial R} (hp : p ≠ 0) : p.natDegree ∈ p.support := by + rcases CPolynomial.degree_eq_support_max p hp with ⟨n, hnmem, hdegree⟩ + have hnat := CPolynomial.degree_eq_natDegree p hp + rw [hnat] at hdegree + have hn : n = p.natDegree := WithBot.coe_eq_coe.mp hdegree.symm + simpa [hn] using hnmem + +end CPolynomial + +namespace DenseMatrix + +/-- Fold over a range with one distinguished nonzero entry. -/ +theorem foldl_range_one_special {F : Type*} [AddCommMonoid F] {pivot : Nat} (x : F) : + ∀ start len (init : F), + (List.range' start len).foldl (fun acc j ↦ acc + if j = pivot then x else 0) init = + init + if start ≤ pivot ∧ pivot < start + len then x else 0 := by + intro start len init + revert start init + induction len with + | zero => + intro start init + have hpivotFalse : ¬(start ≤ pivot ∧ pivot < start) := by omega + simp [hpivotFalse] + | succ len ih => + intro start init + rw [List.range'] + simp only [List.foldl_cons] + rw [ih (start + 1) (init + if start = pivot then x else 0)] + by_cases hsp : start = pivot + · subst pivot + simp + · simp [hsp] + by_cases hpivotIn : start + 1 ≤ pivot ∧ pivot < start + 1 + len + · have hstartPivot : start ≤ pivot ∧ pivot < start + (len + 1) := by omega + have hstart_lt : start < pivot := by omega + simp [hpivotIn, hstartPivot, hstart_lt] + · have hstartPivotFalse : ¬(start ≤ pivot ∧ pivot < start + (len + 1)) := by + omega + have hnot : ¬(start < pivot ∧ pivot < start + 1 + len) := by omega + simp [hstartPivotFalse, hnot] + +/-- A range fold with a single distinguished index. -/ +theorem foldl_range_single_index {F : Type*} [AddCommMonoid F] {n k : Nat} + (hk : k < n) (x : F) : + (List.range' 0 n).foldl (fun acc j ↦ acc + if j = k then x else 0) 0 = x := by + rw [foldl_range_one_special (F := F) (pivot := k) x 0 n 0] + have hin : 0 ≤ k ∧ k < 0 + n := by omega + simp [hin, hk] + +/-- A range fold with identically zero contributions is zero. -/ +theorem foldl_range_eq_zero_of_zero {F : Type*} [AddMonoid F] {n : Nat} {f : Nat → F} + (hzero : ∀ c, c < n → f c = 0) : + (List.range' 0 n).foldl (fun acc c ↦ acc + f c) 0 = 0 := by + have hfold : ∀ (xs : List Nat) (acc : F), + acc = 0 → (∀ c, c ∈ xs → f c = 0) → + xs.foldl (fun acc c ↦ acc + f c) acc = 0 := by + intro xs + induction xs with + | nil => + intro acc hacc _ + exact hacc + | cons c xs ih => + intro acc hacc hxs + simp only [List.foldl_cons] + apply ih + · simp [hacc, hxs c (by simp)] + · intro d hd + exact hxs d (by simp [hd]) + apply hfold (List.range' 0 n) + · rfl + · intro c hc + exact hzero c (by simpa using (List.mem_range'_1.mp hc).2) + +end DenseMatrix + +namespace CBivariate + +/-- All bivariate coefficients of zero are zero. -/ +theorem coeff_zero {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] (i j : Nat) : + coeff (0 : CBivariate R) i j = 0 := by + change CPolynomial.coeff (CPolynomial.coeff (0 : CBivariate R) j) i = 0 + have houter : CPolynomial.coeff (0 : CBivariate R) j = 0 := CPolynomial.coeff_zero j + rw [houter] + rw [CPolynomial.coeff_zero] + +/-- Bivariate coefficients past the stored outer array are zero. -/ +theorem coeff_eq_zero_of_y_size_le {R : Type*} [Zero R] (Q : CBivariate R) + {i j : Nat} (hj : Q.val.size ≤ j) : coeff Q i j = 0 := by + change (CPolynomial.coeff Q j).coeff i = 0 + rw [CPolynomial.coeff_eq_zero_of_size_le Q hj] + exact CPolynomial.coeff_zero i + +/-- Bivariate extensionality through all scalar coefficients. -/ +theorem eq_iff_coeff {R : Type*} [Zero R] [BEq R] [LawfulBEq R] + {P Q : CBivariate R} : P = Q ↔ ∀ i j, coeff P i j = coeff Q i j := by + constructor + · intro h i j + rw [h] + · intro h + apply (CPolynomial.eq_iff_coeff (p := P) (q := Q)).2 + intro j + apply (CPolynomial.eq_iff_coeff (p := CPolynomial.coeff P j) + (q := CPolynomial.coeff Q j)).2 + intro i + exact h i j + +/-- Coefficients of one bivariate monomial. -/ +theorem coeff_monomialXY {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] [DecidableEq R] + (n m i j : Nat) (c : R) : + coeff (monomialXY n m c) i j = if i = n ∧ j = m then c else 0 := by + change CPolynomial.coeff + (CPolynomial.coeff (CPolynomial.monomial m (CPolynomial.monomial n c) : + CBivariate R) j) i = if i = n ∧ j = m then c else 0 + have houter : + CPolynomial.coeff + (CPolynomial.monomial m (CPolynomial.monomial n c) : CBivariate R) j = + if j = m then CPolynomial.monomial n c else 0 := by + exact CPolynomial.coeff_monomial m j (CPolynomial.monomial n c) + rw [houter] + by_cases hj : j = m + · subst j + simpa [CPolynomial.coeff] using CPolynomial.coeff_monomial (R := R) n i c + · simp [hj] + simpa [CPolynomial.coeff] using CPolynomial.coeff_zero (R := R) i + +/-- Coefficients of a polynomial assembled from a monomial array are the folded +sum of matching monomial coefficients. -/ +theorem ofMonomialCoeffs_coeff {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] [DecidableEq R] + (monomials : Array Monomial) (coeffs : Array R) (i j : Nat) : + coeff (ofMonomialCoeffs monomials coeffs) i j = + (List.range' 0 monomials.size).foldl + (fun acc col ↦ + let monomial := monomials.getD col ⟨0, 0⟩ + acc + if i = monomial.xDegree ∧ j = monomial.yDegree then coeffs.getD col 0 + else 0) + 0 := by + unfold ofMonomialCoeffs + have hfold : ∀ (xs : List Nat) (out : CBivariate R) (acc : R), + coeff out i j = acc → + coeff + (xs.foldl + (fun out col ↦ + let monomial := monomials.getD col ⟨0, 0⟩ + out + monomialXY monomial.xDegree monomial.yDegree (coeffs.getD col 0)) + out) i j = + xs.foldl + (fun acc col ↦ + let monomial := monomials.getD col ⟨0, 0⟩ + acc + if i = monomial.xDegree ∧ j = monomial.yDegree then coeffs.getD col 0 + else 0) + acc := by + intro xs + induction xs with + | nil => + intro out acc hout + exact hout + | cons col xs ih => + intro out acc hout + simp only [List.foldl_cons] + apply ih + rw [coeff_add, coeff_monomialXY, hout] + exact hfold (List.range' 0 monomials.size) 0 0 (coeff_zero i j) + +/-- The executable monomial grid has no duplicate exponent pairs. -/ +theorem monomialGrid_nodup (bound : Nat) : (monomialGrid bound).Nodup := by + unfold monomialGrid + rw [List.nodup_flatMap] + constructor + · intro y _ + exact List.nodup_range.map (by + intro x₁ x₂ h + cases h + rfl) + · exact List.nodup_range.pairwise_of_forall_ne (by + intro y hy y' hy' hyne + simp only [Function.onFun, List.disjoint_left] + intro m hm hm' + rcases List.mem_map.mp hm with ⟨x, _, rfl⟩ + rcases List.mem_map.mp hm' with ⟨x', _, hmEq⟩ + exact hyne (by cases hmEq; rfl)) + +/-- Weighted-degree monomial enumeration has no duplicate exponent pairs. -/ +theorem monomialsWeightedDegreeLE_nodup (xWeight yWeight bound : Nat) : + (monomialsWeightedDegreeLE xWeight yWeight bound).toList.Nodup := by + simpa [monomialsWeightedDegreeLE] using + (monomialGrid_nodup bound).filter + (fun m ↦ xWeight * m.xDegree + yWeight * m.yDegree ≤ bound) + +/-- All returned weighted-degree monomials satisfy the requested bound. -/ +theorem monomialsWeightedDegreeLE_sound + {xWeight yWeight bound : Nat} {m : Monomial} + (hm : m ∈ (monomialsWeightedDegreeLE xWeight yWeight bound).toList) : + xWeight * m.xDegree + yWeight * m.yDegree ≤ bound := by + simp [monomialsWeightedDegreeLE] at hm + exact hm.2 + +/-- In a nodup array, equal in-bounds `getD` entries have equal indices. -/ +theorem array_getD_inj_of_nodup {α : Type*} [DecidableEq α] {xs : Array α} + {default : α} (hnodup : xs.toList.Nodup) {i j : Nat} + (hi : i < xs.size) (hj : j < xs.size) + (h : xs.getD i default = xs.getD j default) : i = j := by + exact (List.getElem_inj hnodup).mp (by + rw [Array.getD_eq_getD_getElem?, Array.getElem?_eq_getElem hi] at h + rw [Array.getD_eq_getD_getElem?, Array.getElem?_eq_getElem hj] at h + simpa using h) + +/-- For a nodup monomial array, the assembled polynomial recovers the matching +coefficient at each listed monomial. -/ +theorem ofMonomialCoeffs_coeff_getD {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] [DecidableEq R] + {monomials : Array Monomial} {coeffs : Array R} + (hnodup : monomials.toList.Nodup) {k : Nat} (hk : k < monomials.size) : + coeff (ofMonomialCoeffs monomials coeffs) + (monomials.getD k ⟨0, 0⟩).xDegree (monomials.getD k ⟨0, 0⟩).yDegree = + coeffs.getD k 0 := by + rw [ofMonomialCoeffs_coeff] + have hfold : ∀ (xs : List Nat) (acc : R), + (∀ col, col ∈ xs → col < monomials.size) → + xs.foldl + (fun acc col ↦ + let monomial := monomials.getD col ⟨0, 0⟩ + acc + + if (monomials.getD k ⟨0, 0⟩).xDegree = monomial.xDegree ∧ + (monomials.getD k ⟨0, 0⟩).yDegree = monomial.yDegree then + coeffs.getD col 0 + else 0) + acc = + xs.foldl (fun acc col ↦ acc + if col = k then coeffs.getD k 0 else 0) + acc := by + intro xs + induction xs with + | nil => + intro acc _ + rfl + | cons col xs ih => + intro acc hxs + simp only [List.foldl_cons] + have hcol : col < monomials.size := hxs col (by simp) + have hterm : + (if (monomials.getD k ⟨0, 0⟩).xDegree = + (monomials.getD col ⟨0, 0⟩).xDegree ∧ + (monomials.getD k ⟨0, 0⟩).yDegree = + (monomials.getD col ⟨0, 0⟩).yDegree then + coeffs.getD col 0 + else 0) = + if col = k then coeffs.getD k 0 else 0 := by + by_cases hcolk : col = k + · subst col + simp + · have hmono_ne : monomials.getD k ⟨0, 0⟩ ≠ + monomials.getD col ⟨0, 0⟩ := by + intro hmono + have hidx := array_getD_inj_of_nodup (xs := monomials) + (default := ⟨0, 0⟩) hnodup hk hcol hmono + exact hcolk hidx.symm + have hpair_ne : + ¬((monomials.getD k ⟨0, 0⟩).xDegree = + (monomials.getD col ⟨0, 0⟩).xDegree ∧ + (monomials.getD k ⟨0, 0⟩).yDegree = + (monomials.getD col ⟨0, 0⟩).yDegree) := by + rintro ⟨hx, hy⟩ + apply hmono_ne + cases hkmono : monomials.getD k ⟨0, 0⟩ + cases hcmono : monomials.getD col ⟨0, 0⟩ + simp [hkmono, hcmono] at hx hy ⊢ + exact ⟨hx, hy⟩ + rw [if_neg hpair_ne, if_neg hcolk] + rw [hterm] + apply ih + intro c hc + exact hxs c (by simp [hc]) + rw [hfold] + · exact DenseMatrix.foldl_range_single_index hk (coeffs.getD k 0) + · intro col hcol + simpa using (List.mem_range'_1.mp hcol).2 + +/-- A nonzero coefficient at a listed monomial makes the assembled bivariate +polynomial nonzero. -/ +theorem ofMonomialCoeffs_ne_zero_of_coeff_getD_ne_zero {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] [DecidableEq R] + {monomials : Array Monomial} {coeffs : Array R} + (hnodup : monomials.toList.Nodup) {k : Nat} (hk : k < monomials.size) + (hcoeff : coeffs.getD k 0 ≠ 0) : + ofMonomialCoeffs monomials coeffs ≠ 0 := by + intro hzero + let monomial := monomials.getD k ⟨0, 0⟩ + have hget := ofMonomialCoeffs_coeff_getD (R := R) (monomials := monomials) + (coeffs := coeffs) hnodup hk + dsimp [monomial] at hget + have hzeroCoeff : + coeff (ofMonomialCoeffs monomials coeffs) monomial.xDegree monomial.yDegree = 0 := by + rw [hzero] + exact coeff_zero monomial.xDegree monomial.yDegree + dsimp [monomial] at hzeroCoeff + rw [hget] at hzeroCoeff + exact hcoeff hzeroCoeff + +/-- If all coefficients above a weighted-degree bound are zero, the executable +weighted degree is below that bound. -/ +theorem natWeightedDegree_le_of_coeff_zero {R : Type*} [Zero R] [BEq R] [LawfulBEq R] + (f : CBivariate R) (u v bound : Nat) + (hzero : ∀ i j, bound < u * i + v * j → coeff f i j = 0) : + natWeightedDegree f u v ≤ bound := by + unfold natWeightedDegree + apply Finset.sup_le + intro j hj + by_cases hcoeffY : f.val.coeff j = 0 + · have hne : f.val.coeff j ≠ 0 := (CPolynomial.mem_support_iff f j).mp hj + exact False.elim (hne hcoeffY) + · have hmem : (f.val.coeff j).natDegree ∈ CPolynomial.support (f.val.coeff j) := + CPolynomial.natDegree_mem_support_of_nonzero hcoeffY + have hcoeff_ne : CPolynomial.coeff (f.val.coeff j) (f.val.coeff j).natDegree ≠ 0 := + (CPolynomial.mem_support_iff (f.val.coeff j) _).mp hmem + by_contra hnot + have hgt : bound < u * (f.val.coeff j).natDegree + v * j := by omega + have hz := hzero (f.val.coeff j).natDegree j hgt + unfold coeff at hz + exact hcoeff_ne hz + +/-- Coefficients above a bound are zero for a polynomial assembled from monomials +all below that bound. -/ +theorem ofMonomialCoeffs_coeff_eq_zero_of_weight_gt {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] [DecidableEq R] + {monomials : Array Monomial} {coeffs : Array R} {u v bound i j : Nat} + (hall : ∀ monomial, monomial ∈ monomials.toList → + u * monomial.xDegree + v * monomial.yDegree ≤ bound) + (hgt : bound < u * i + v * j) : + coeff (ofMonomialCoeffs monomials coeffs) i j = 0 := by + rw [ofMonomialCoeffs_coeff] + apply DenseMatrix.foldl_range_eq_zero_of_zero + intro col hcol + let monomial := monomials.getD col ⟨0, 0⟩ + have hmem : monomial ∈ monomials.toList := by + unfold monomial + rw [Array.getD_eq_getD_getElem?, Array.getElem?_eq_getElem hcol] + exact Array.mem_def.mp (Array.getElem_mem (xs := monomials) hcol) + have hle := hall monomial hmem + by_cases hmatch : i = monomial.xDegree ∧ j = monomial.yDegree + · exfalso + rcases hmatch with ⟨hi, hj⟩ + rw [hi, hj] at hgt + exact Nat.not_lt_of_ge hle hgt + · change (if i = monomial.xDegree ∧ j = monomial.yDegree then coeffs.getD col 0 + else 0) = 0 + rw [if_neg hmatch] + +/-- A polynomial assembled from bounded monomials has weighted degree below the +same bound. -/ +theorem ofMonomialCoeffs_natWeightedDegree_le {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] [DecidableEq R] + {monomials : Array Monomial} {coeffs : Array R} {u v bound : Nat} + (hall : ∀ monomial, monomial ∈ monomials.toList → + u * monomial.xDegree + v * monomial.yDegree ≤ bound) : + natWeightedDegree (ofMonomialCoeffs monomials coeffs) u v ≤ bound := + natWeightedDegree_le_of_coeff_zero _ u v bound fun _ _ hgt ↦ + ofMonomialCoeffs_coeff_eq_zero_of_weight_gt (monomials := monomials) + (coeffs := coeffs) hall hgt + +/-- Coefficients of a polynomial materialized from Hasse terms are the folded +sum of matching term coefficients. -/ +theorem hasseDerivativeFromTerms_coeff {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] [DecidableEq R] + (terms : List (HasseTerm R)) (i j : Nat) : + coeff (hasseDerivativeFromTerms terms) i j = + terms.foldl + (fun acc term ↦ acc + if i = term.xDegree ∧ j = term.yDegree then term.coeff else 0) + 0 := by + unfold hasseDerivativeFromTerms + have hfold : ∀ (xs : List (HasseTerm R)) (out : CBivariate R) (acc : R), + coeff out i j = acc → + coeff (xs.foldl (fun out term ↦ + out + monomialXY term.xDegree term.yDegree term.coeff) out) i j = + xs.foldl + (fun acc term ↦ acc + if i = term.xDegree ∧ j = term.yDegree then term.coeff else 0) + acc := by + intro xs + induction xs with + | nil => + intro out acc hout + exact hout + | cons term xs ih => + intro out acc hout + simp only [List.foldl_cons] + apply ih + rw [coeff_add, coeff_monomialXY, hout] + exact hfold terms 0 0 (coeff_zero i j) + +/-- Evaluating after appending one Hasse term adds that term's contribution. -/ +theorem hasseDerivativeEvalFromTerms_append_single {R : Type*} [Semiring R] + (terms : List (HasseTerm R)) (term : HasseTerm R) (x y : R) : + hasseDerivativeEvalFromTerms (terms ++ [term]) x y = + hasseDerivativeEvalFromTerms terms x y + + term.coeff * x ^ term.xDegree * y ^ term.yDegree := by + unfold hasseDerivativeEvalFromTerms + simp [List.foldl_append] + +/-- The fixed coefficient of the executable Hasse term list is the matching +coefficient fold over the same degree ranges. -/ +theorem hasseDerivativeTermList_coeff_fold {R : Type*} [Semiring R] + (a b i j : Nat) (Q : CBivariate R) : + (hasseDerivativeTermList a b Q).foldl + (fun acc term ↦ acc + if i = term.xDegree ∧ j = term.yDegree then term.coeff else 0) + 0 = + (List.range' 0 Q.val.size).foldl + (fun acc yDeg ↦ + let coeffY := Q.val.coeff yDeg + if b ≤ yDeg then + (List.range' 0 coeffY.val.size).foldl + (fun acc xDeg ↦ + if a ≤ xDeg then + acc + + if i = xDeg - a ∧ j = yDeg - b then + (Nat.choose xDeg a : R) * (Nat.choose yDeg b : R) * coeffY.coeff xDeg + else 0 + else acc) + acc + else acc) + 0 := by + unfold hasseDerivativeTermList + have houter : ∀ (ys : List Nat) (terms : List (HasseTerm R)) (acc : R), + terms.foldl + (fun acc term ↦ acc + if i = term.xDegree ∧ j = term.yDegree then term.coeff else 0) + 0 = acc → + (ys.foldl + (fun out yDeg ↦ + let coeffY := Q.val.coeff yDeg + if b ≤ yDeg then + (List.range' 0 coeffY.val.size).foldl + (fun out xDeg ↦ + if a ≤ xDeg then + let coeff := (Nat.choose xDeg a : R) * (Nat.choose yDeg b : R) * + coeffY.coeff xDeg + out ++ [⟨xDeg - a, yDeg - b, coeff⟩] + else out) + out + else out) + terms).foldl + (fun acc term ↦ acc + if i = term.xDegree ∧ j = term.yDegree then term.coeff else 0) + 0 = + ys.foldl + (fun acc yDeg ↦ + let coeffY := Q.val.coeff yDeg + if b ≤ yDeg then + (List.range' 0 coeffY.val.size).foldl + (fun acc xDeg ↦ + if a ≤ xDeg then + acc + + if i = xDeg - a ∧ j = yDeg - b then + (Nat.choose xDeg a : R) * (Nat.choose yDeg b : R) * coeffY.coeff xDeg + else 0 + else acc) + acc + else acc) + acc := by + intro ys + induction ys with + | nil => + intro terms acc hterms + exact hterms + | cons yDeg ys ih => + intro terms acc hterms + simp only [List.foldl_cons] + by_cases hy : b ≤ yDeg + · simp only [hy, if_true] + have hinner : ∀ (xs : List Nat) (terms : List (HasseTerm R)) (acc : R), + terms.foldl + (fun acc term ↦ + acc + if i = term.xDegree ∧ j = term.yDegree then term.coeff else 0) + 0 = acc → + (xs.foldl + (fun out xDeg ↦ + if a ≤ xDeg then + let coeff := (Nat.choose xDeg a : R) * (Nat.choose yDeg b : R) * + (Q.val.coeff yDeg).coeff xDeg + out ++ [⟨xDeg - a, yDeg - b, coeff⟩] + else out) + terms).foldl + (fun acc term ↦ + acc + if i = term.xDegree ∧ j = term.yDegree then term.coeff else 0) + 0 = + xs.foldl + (fun acc xDeg ↦ + if a ≤ xDeg then + acc + + if i = xDeg - a ∧ j = yDeg - b then + (Nat.choose xDeg a : R) * (Nat.choose yDeg b : R) * + (Q.val.coeff yDeg).coeff xDeg + else 0 + else acc) + acc := by + intro xs + induction xs with + | nil => + intro terms acc hterms + exact hterms + | cons xDeg xs ihx => + intro terms acc hterms + simp only [List.foldl_cons] + by_cases hx : a ≤ xDeg + · simp only [hx, if_true] + apply ihx + simp [List.foldl_append, hterms] + · simp only [hx, if_false] + exact ihx terms acc hterms + apply ih + exact hinner (List.range' 0 (Q.val.coeff yDeg).val.size) terms acc hterms + · simp only [hy, if_false] + exact ih terms acc hterms + simpa using houter (List.range' 0 Q.val.size) [] 0 rfl + +/-- Collapse the inner `x`-fold in a fixed Hasse coefficient calculation. -/ +theorem hasseDerivativeTermList_coeff_inner_fold {R : Type*} [Semiring R] + (a b i j yDeg : Nat) (coeffY : CPolynomial R) (acc : R) (hy : b ≤ yDeg) : + (List.range' 0 coeffY.val.size).foldl + (fun acc xDeg ↦ + if a ≤ xDeg then + acc + + if i = xDeg - a ∧ j = yDeg - b then + (Nat.choose xDeg a : R) * (Nat.choose yDeg b : R) * coeffY.coeff xDeg + else 0 + else acc) + acc = + acc + + if yDeg = j + b ∧ i + a < coeffY.val.size then + (Nat.choose (i + a) a : R) * (Nat.choose yDeg b : R) * coeffY.coeff (i + a) + else 0 := by + have hstep : + (List.range' 0 coeffY.val.size).foldl + (fun acc xDeg ↦ + if a ≤ xDeg then + acc + + if i = xDeg - a ∧ j = yDeg - b then + (Nat.choose xDeg a : R) * (Nat.choose yDeg b : R) * coeffY.coeff xDeg + else 0 + else acc) + acc = + (List.range' 0 coeffY.val.size).foldl + (fun acc xDeg ↦ + acc + + if a ≤ xDeg then + if i = xDeg - a ∧ j = yDeg - b then + (Nat.choose xDeg a : R) * (Nat.choose yDeg b : R) * coeffY.coeff xDeg + else 0 + else 0) + acc := by + apply List.foldl_congr_of_mem + intro acc xDeg + by_cases hx : a ≤ xDeg <;> simp [hx] + rw [hstep] + by_cases hyTarget : yDeg = j + b + · subst yDeg + have hcontrib : ∀ xDeg, + (if a ≤ xDeg then + if i = xDeg - a ∧ j = j + b - b then + (Nat.choose xDeg a : R) * (Nat.choose (j + b) b : R) * coeffY.coeff xDeg + else 0 + else 0) = + if xDeg = i + a then + (Nat.choose (i + a) a : R) * (Nat.choose (j + b) b : R) * + coeffY.coeff (i + a) + else 0 := by + intro xDeg + by_cases hxEq : xDeg = i + a + · subst xDeg + have ha : a ≤ i + a := by omega + have hi : i = i + a - a := by omega + have hjy : j = j + b - b := by omega + rw [if_pos ha, if_pos ⟨hi, hjy⟩, if_pos rfl] + · by_cases ha : a ≤ xDeg + · have hi_ne : i ≠ xDeg - a := by + intro hi + have : xDeg = i + a := by omega + exact hxEq this + simp [ha, hxEq, hi_ne] + · simp [ha, hxEq] + rw [List.foldl_congr_of_mem + (List.range' 0 coeffY.val.size) acc + (fun _acc' xDeg _hxDeg ↦ by rw [hcontrib xDeg])] + rw [DenseMatrix.foldl_range_one_special + (F := R) (pivot := i + a) + ((Nat.choose (i + a) a : R) * (Nat.choose (j + b) b : R) * + coeffY.coeff (i + a)) 0 coeffY.val.size acc] + simp + · have hcontrib : ∀ xDeg, + (if a ≤ xDeg then + if i = xDeg - a ∧ j = yDeg - b then + (Nat.choose xDeg a : R) * (Nat.choose yDeg b : R) * coeffY.coeff xDeg + else 0 + else 0) = (0 : R) := by + intro xDeg + by_cases ha : a ≤ xDeg + · have hnot : ¬(i = xDeg - a ∧ j = yDeg - b) := by + rintro ⟨_hi, hj⟩ + have : yDeg = j + b := by omega + exact hyTarget this + simp [ha, hnot] + · simp [ha] + rw [List.foldl_congr_of_mem + (List.range' 0 coeffY.val.size) acc + (fun _acc' xDeg _hxDeg ↦ by rw [hcontrib xDeg])] + simp [hyTarget] + +/-- The fixed coefficient of the executable Hasse term list has the closed +coefficient-shift formula. -/ +theorem hasseDerivativeTermList_coeff_value {R : Type*} [Semiring R] + (a b i j : Nat) (Q : CBivariate R) : + (hasseDerivativeTermList a b Q).foldl + (fun acc term ↦ acc + if i = term.xDegree ∧ j = term.yDegree then term.coeff else 0) + 0 = + (Nat.choose (i + a) a : R) * (Nat.choose (j + b) b : R) * + coeff Q (i + a) (j + b) := by + rw [hasseDerivativeTermList_coeff_fold] + let source := + if i + a < (Q.val.coeff (j + b)).val.size then + (Nat.choose (i + a) a : R) * (Nat.choose (j + b) b : R) * + (Q.val.coeff (j + b)).coeff (i + a) + else 0 + have houterStep : + (List.range' 0 Q.val.size).foldl + (fun acc yDeg ↦ + let coeffY := Q.val.coeff yDeg + if b ≤ yDeg then + (List.range' 0 coeffY.val.size).foldl + (fun acc xDeg ↦ + if a ≤ xDeg then + acc + + if i = xDeg - a ∧ j = yDeg - b then + (Nat.choose xDeg a : R) * (Nat.choose yDeg b : R) * coeffY.coeff xDeg + else 0 + else acc) + acc + else acc) + 0 = + (List.range' 0 Q.val.size).foldl + (fun acc yDeg ↦ acc + if yDeg = j + b then source else 0) + 0 := by + apply List.foldl_congr_of_mem + intro acc yDeg + by_cases hy : b ≤ yDeg + · rw [if_pos hy] + rw [hasseDerivativeTermList_coeff_inner_fold a b i j yDeg (Q.val.coeff yDeg) acc hy] + by_cases hyTarget : yDeg = j + b + · subst yDeg + simp [source] + · simp [hyTarget] + · rw [if_neg hy] + have hyTarget : yDeg ≠ j + b := by + intro htarget + subst yDeg + exact hy (by omega) + simp [hyTarget] + rw [houterStep] + rw [DenseMatrix.foldl_range_one_special (F := R) (pivot := j + b) source 0 Q.val.size 0] + by_cases hyIn : j + b < Q.val.size + · have hin : 0 ≤ j + b ∧ j + b < 0 + Q.val.size := by omega + simp [hin, hyIn] + by_cases hxIn : i + a < (Q.val.coeff (j + b)).val.size + · dsimp only [source] + rw [if_pos hxIn] + simp [CPolynomial.coeff, CPolynomial.Raw.coeff, Array.getD_eq_getD_getElem?, + Array.getElem?_eq_getElem hyIn] + · have hxLe : (Q.val.coeff (j + b)).val.size ≤ i + a := Nat.le_of_not_gt hxIn + have hxLe' : (Q.val[j + b]).val.size ≤ i + a := by + simpa [CPolynomial.Raw.coeff, Array.getD_eq_getD_getElem?, + Array.getElem?_eq_getElem hyIn] using hxLe + dsimp only [source] + rw [if_neg hxIn] + rw [Array.getElem?_eq_none hxLe'] + simp + · have hyLe : Q.val.size ≤ j + b := Nat.le_of_not_gt hyIn + rw [coeff_eq_zero_of_y_size_le Q hyLe] + simp [source, hyIn] + +/-- Coefficients of the executable Hasse derivative are shifted source +coefficients scaled by the corresponding binomial factors. -/ +theorem hasseDerivative_coeff {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] [DecidableEq R] + (a b i j : Nat) (Q : CBivariate R) : + coeff (hasseDerivative a b Q) i j = + (Nat.choose (i + a) a : R) * (Nat.choose (j + b) b : R) * + coeff Q (i + a) (j + b) := by + unfold hasseDerivative + rw [hasseDerivativeFromTerms_coeff] + exact hasseDerivativeTermList_coeff_value a b i j Q + +/-- Hasse derivatives are additive. -/ +theorem hasseDerivative_add {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] [DecidableEq R] + (a b : Nat) (P Q : CBivariate R) : + hasseDerivative a b (P + Q) = hasseDerivative a b P + hasseDerivative a b Q := by + rw [eq_iff_coeff] + intro i j + rw [hasseDerivative_coeff] + rw [coeff_add] + rw [coeff_add] + rw [hasseDerivative_coeff, hasseDerivative_coeff] + rw [mul_add] + +/-- The Hasse derivative of zero is zero. -/ +theorem hasseDerivative_zero {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] [DecidableEq R] + (a b : Nat) : + hasseDerivative a b (0 : CBivariate R) = 0 := by + rw [eq_iff_coeff] + intro i j + rw [hasseDerivative_coeff, coeff_zero, coeff_zero] + simp + +/-- Hasse derivative of a single bivariate monomial. -/ +theorem hasseDerivative_monomialXY {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] [DecidableEq R] + (a b n m : Nat) (c : R) : + hasseDerivative a b (monomialXY n m c) = + if a ≤ n ∧ b ≤ m then + monomialXY (n - a) (m - b) + ((Nat.choose n a : R) * (Nat.choose m b : R) * c) + else 0 := by + rw [eq_iff_coeff] + intro i j + rw [hasseDerivative_coeff] + by_cases hle : a ≤ n ∧ b ≤ m + · rw [if_pos hle, coeff_monomialXY, coeff_monomialXY] + by_cases hmatch : i = n - a ∧ j = m - b + · rcases hmatch with ⟨hi, hj⟩ + have hia : i + a = n := by omega + have hjb : j + b = m := by omega + have hsource : i + a = n ∧ j + b = m := ⟨hia, hjb⟩ + rw [if_pos hsource, if_pos ⟨hi, hj⟩] + rw [hia, hjb] + · rw [if_neg hmatch] + have hsource : ¬(i + a = n ∧ j + b = m) := by + rintro ⟨hia, hjb⟩ + apply hmatch + constructor <;> omega + rw [if_neg hsource] + simp + · rw [if_neg hle, coeff_monomialXY, coeff_zero] + have hsource : ¬(i + a = n ∧ j + b = m) := by + rintro ⟨hia, hjb⟩ + apply hle + constructor <;> omega + rw [if_neg hsource] + simp + +/-- Full evaluation is additive. -/ +theorem evalEval_add {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] + (x y : R) (P Q : CBivariate R) : + evalEval x y (P + Q) = evalEval x y P + evalEval x y Q := by + rw [evalEval_toPoly, evalEval_toPoly, evalEval_toPoly, toPoly_add] + simp [Polynomial.evalEval, Polynomial.eval_add] + +/-- Full evaluation of zero is zero. -/ +theorem evalEval_zero {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] + (x y : R) : evalEval x y (0 : CBivariate R) = 0 := by + rw [evalEval_toPoly, toPoly_zero] + simp [Polynomial.evalEval] + +/-- Full evaluation of a bivariate monomial has the expected closed form. -/ +theorem evalEval_monomialXY {R : Type*} + [CommSemiring R] [BEq R] [LawfulBEq R] [Nontrivial R] [DecidableEq R] + (x y c : R) (n m : Nat) : + evalEval x y (monomialXY n m c) = c * x ^ n * y ^ m := by + rw [evalEval_toPoly, monomialXY_toPoly] + simp [Polynomial.evalEval, Polynomial.eval_monomial] + +/-- Evaluating the materialized derivative-term fold matches the direct scalar fold. -/ +theorem hasseDerivativeTerms_eval_aux {R : Type*} + [CommSemiring R] [BEq R] [LawfulBEq R] [Nontrivial R] [DecidableEq R] + (x y : R) (terms : List (HasseTerm R)) (acc : CBivariate R) : + evalEval x y (terms.foldl + (fun out term ↦ out + monomialXY term.xDegree term.yDegree term.coeff) acc) = + terms.foldl + (fun z term ↦ z + term.coeff * x ^ term.xDegree * y ^ term.yDegree) + (evalEval x y acc) := by + induction terms generalizing acc with + | nil => rfl + | cons term terms ih => + simp [List.foldl] + rw [ih] + congr 1 + rw [evalEval_add, evalEval_monomialXY] + +/-- Evaluating a materialized derivative-term polynomial matches direct term evaluation. -/ +theorem hasseDerivativeFromTerms_eval {R : Type*} + [CommSemiring R] [BEq R] [LawfulBEq R] [Nontrivial R] [DecidableEq R] + (x y : R) (terms : List (HasseTerm R)) : + evalEval x y (hasseDerivativeFromTerms terms) = + hasseDerivativeEvalFromTerms terms x y := by + unfold hasseDerivativeFromTerms hasseDerivativeEvalFromTerms + rw [hasseDerivativeTerms_eval_aux] + rw [evalEval_zero] + +/-- Correctness of executable Hasse derivative evaluation. -/ +theorem hasseDerivative_eval_eq_eval {R : Type*} + [CommSemiring R] [BEq R] [LawfulBEq R] [Nontrivial R] [DecidableEq R] + (a b : Nat) (x y : R) (Q : CBivariate R) : + evalEval x y (hasseDerivative a b Q) = hasseDerivativeEval a b x y Q := by + unfold hasseDerivative hasseDerivativeEval + exact hasseDerivativeFromTerms_eval x y (hasseDerivativeTerms a b Q).toList + +/-- Hasse derivatives in the outer variable commute with Taylor shifting the +inner coefficients. -/ +private theorem hasseDeriv_map_taylorAlgHom {F : Type*} [Field F] + (P : Polynomial (Polynomial F)) (x : F) (b : Nat) : + Polynomial.hasseDeriv b (P.map (Polynomial.taylorAlgHom x).toRingHom) = + (Polynomial.hasseDeriv b P).map (Polynomial.taylorAlgHom x).toRingHom := by + ext n + simp [Polynomial.hasseDeriv_coeff, Polynomial.taylorAlgHom, Polynomial.taylor_apply] + +/-- Evaluating after Taylor-shifting all inner coefficients is the Taylor shift +of the evaluated coefficient polynomial. -/ +private theorem eval_map_taylorAlgHom {F : Type*} [Field F] + (P : Polynomial (Polynomial F)) (x y : F) : + Polynomial.eval (Polynomial.C y) (P.map (Polynomial.taylorAlgHom x).toRingHom) = + Polynomial.taylor x (Polynomial.eval (Polynomial.C y) P) := by + rw [Polynomial.eval_map] + change P.eval₂ (Polynomial.taylorAlgHom x).toRingHom (Polynomial.C y) = + (Polynomial.taylorAlgHom x).toRingHom + (P.eval₂ (RingHom.id (Polynomial F)) (Polynomial.C y)) + rw [Polynomial.hom_eval₂] + simp [Polynomial.taylorAlgHom] + +/-- Hasse derivatives in `X` commute with multiplication by an `X`-constant +polynomial. -/ +private theorem hasseDeriv_mul_C_pow {F : Type*} [Field F] + (P : Polynomial F) (c : F) (n a : Nat) : + Polynomial.hasseDeriv a (P * Polynomial.C c ^ n) = + Polynomial.hasseDeriv a P * Polynomial.C c ^ n := by + rw [show Polynomial.C c ^ n = Polynomial.C (c ^ n) by rw [Polynomial.C_pow]] + ext d + rw [Polynomial.hasseDeriv_coeff] + rw [Polynomial.coeff_mul_C] + rw [Polynomial.coeff_mul_C] + rw [Polynomial.hasseDeriv_coeff] + ring + +/-- Hasse differentiating the inner-variable polynomial after evaluating the +outer variable at a constant equals evaluating the coefficientwise Hasse +derivative. -/ +private theorem hasseDeriv_eval_C_eq_eval_coeffwise_hasseDeriv {F : Type*} + [Field F] (P : Polynomial (Polynomial F)) (y : F) (a : Nat) : + Polynomial.hasseDeriv a (Polynomial.eval (Polynomial.C y) P) = + Polynomial.eval (Polynomial.C y) + (P.sum fun j coeff ↦ Polynomial.monomial j (Polynomial.hasseDeriv a coeff)) := by + rw [Polynomial.eval_eq_sum] + induction P using Polynomial.induction_on' with + | add P Q hP hQ => + rw [Polynomial.sum_add_index] + rw [map_add] + rw [hP, hQ] + rw [Polynomial.sum_add_index] + · simp + · intro i + simp + · intro i p q + simp + · intro i + simp + · intro i p q + simp [add_mul] + | monomial _n _coeff => + simp [Polynomial.sum_monomial_index, hasseDeriv_mul_C_pow] + +/-- The `j`-th coefficient of the coefficientwise Hasse-derivative outer sum is +the Hasse derivative of the `j`-th coefficient. -/ +private theorem coeff_coeffwise_hasseDeriv_sum {F : Type*} [Field F] + (P : Polynomial (Polynomial F)) (a j : Nat) : + ((P.sum fun k coeff ↦ Polynomial.monomial k (Polynomial.hasseDeriv a coeff)).coeff j) = + Polynomial.hasseDeriv a (P.coeff j) := by + rw [Polynomial.coeff_sum] + rw [Polynomial.sum_def] + by_cases hj : j ∈ P.support + · rw [Finset.sum_eq_single j] + · rw [Polynomial.coeff_monomial, if_pos rfl] + · intro k _hk hkj + rw [Polynomial.coeff_monomial, if_neg hkj] + · intro hjnot + contradiction + · rw [Finset.sum_eq_zero] + · rw [Polynomial.notMem_support_iff.mp hj] + simp + · intro k hk + have hkj : k ≠ j := by + intro h + exact hj (h ▸ hk) + rw [Polynomial.coeff_monomial, if_neg hkj] + +/-- The executable bivariate Hasse derivative matches the Mathlib-side +coefficientwise inner Hasse derivative of the outer Hasse derivative. -/ +private theorem toPoly_hasseDerivative_eq_coeffwise_hasseDeriv_hasseDeriv {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (Q : CBivariate F) (a b : Nat) : + (CBivariate.hasseDerivative a b Q).toPoly = + (Polynomial.hasseDeriv b Q.toPoly).sum fun j coeff ↦ + Polynomial.monomial j (Polynomial.hasseDeriv a coeff) := by + ext j n + rw [coeff_coeffwise_hasseDeriv_sum] + rw [Polynomial.hasseDeriv_coeff] + rw [CBivariate.coeff_toPoly] + rw [CBivariate.hasseDerivative_coeff] + rw [Polynomial.hasseDeriv_coeff] + simp [CBivariate.coeff_toPoly] + ring + +/-- Evaluating the univariate `X`-Hasse derivative of the evaluated `Y`-Hasse +derivative matches the executable bivariate Hasse derivative. -/ +private theorem eval_hasseDeriv_eval_hasseDeriv_toPoly {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (Q : CBivariate F) (x y : F) (a b : Nat) : + Polynomial.eval x (Polynomial.hasseDeriv a + (Polynomial.eval (Polynomial.C y) (Polynomial.hasseDeriv b Q.toPoly))) = + CBivariate.hasseDerivativeEval a b x y Q := by + rw [← CBivariate.hasseDerivative_eval_eq_eval] + rw [CBivariate.evalEval_toPoly] + rw [Polynomial.evalEval] + rw [hasseDeriv_eval_C_eq_eval_coeffwise_hasseDeriv] + rw [← toPoly_hasseDerivative_eq_coeffwise_hasseDeriv_hasseDeriv] + +/-- The coefficient of the generic Taylor shift is the direct Hasse derivative +evaluation at the shift point. -/ +theorem coeff_shiftC_eq_hasseDerivativeEval {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (Q : CBivariate F) (x y : F) (a b : Nat) : + CBivariate.coeff (CBivariate.shiftC x y Q) a b = + CBivariate.hasseDerivativeEval a b x y Q := by + rw [← CBivariate.coeff_toPoly] + rw [CBivariate.shiftC_toPoly] + unfold Polynomial.Bivariate.shift + rw [Polynomial.coeff_map] + rw [Polynomial.coe_compRingHom_apply] + rw [← Polynomial.taylor_apply] + rw [← Polynomial.taylor_apply] + rw [Polynomial.taylor_coeff] + rw [Polynomial.taylor_coeff] + exact eval_hasseDeriv_eval_hasseDeriv_toPoly Q x y a b + +/-- The generic multiplicity predicate agrees with the direct GS Hasse +multiplicity predicate. -/ +theorem hasMultiplicity_iff_hasMultiplicityAtLeast {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (Q : CBivariate F) (r : Nat) (x y : F) : + CBivariate.hasMultiplicity Q r x y ↔ + CBivariate.HasMultiplicityAtLeast Q x y r := by + unfold CBivariate.hasMultiplicity CBivariate.HasMultiplicityAtLeast + constructor + · intro h a b hab + rw [← coeff_shiftC_eq_hasseDerivativeEval Q x y a b] + exact h a b hab + · intro h a b hab + rw [coeff_shiftC_eq_hasseDerivativeEval Q x y a b] + exact h a b hab + +/-- The GS batch Hasse predicate agrees with the generic multiplicity +predicate over every packed point. -/ +theorem satisfiesMultiplicityConstraints_iff_hasMultiplicity {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (Q : CBivariate F) (points : Array (F × F)) (r : Nat) : + CBivariate.SatisfiesMultiplicityConstraints Q points r ↔ + ∀ point, point ∈ points.toList → + CBivariate.hasMultiplicity Q r point.1 point.2 := by + unfold CBivariate.SatisfiesMultiplicityConstraints + constructor + · intro h point hmem + exact (hasMultiplicity_iff_hasMultiplicityAtLeast Q r point.1 point.2).2 + (h point hmem) + · intro h point hmem + exact (hasMultiplicity_iff_hasMultiplicityAtLeast Q r point.1 point.2).1 + (h point hmem) + +/-- The executable GS point checker agrees with the generic multiplicity +predicate. -/ +theorem multiplicityAtLeastBool_iff_hasMultiplicity {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (Q : CBivariate F) (x y : F) (r : Nat) : + CBivariate.multiplicityAtLeastBool Q x y r = true ↔ + CBivariate.hasMultiplicity Q r x y := by + have horders_mem : ∀ a b, + a + b < r → (a, b) ∈ (CBivariate.derivativeOrders r).toList := by + intro a b hlt + simp [CBivariate.derivativeOrders, CBivariate.derivativeOrderGrid] + omega + have horders_sound : ∀ order, + order ∈ (CBivariate.derivativeOrders r).toList → order.1 + order.2 < r := by + intro order h + simp [CBivariate.derivativeOrders] at h + exact h.2 + rw [CBivariate.hasMultiplicity_iff_hasMultiplicityAtLeast] + simp [CBivariate.multiplicityAtLeastBool, CBivariate.HasMultiplicityAtLeast] + constructor + · intro h a b hab + rcases List.getElem_of_mem (horders_mem a b hab) with ⟨i, hi, hget⟩ + have horder : (CBivariate.derivativeOrders r)[i] = (a, b) := by + simpa [Array.getElem_toList] using hget + have hzero := h i (by simpa using hi) + simpa [horder] using hzero + · intro h i hi + exact h _ _ (horders_sound _ (Array.getElem_mem_toList hi)) + +/-- The executable GS batch checker agrees with the generic multiplicity +predicate over every packed point. -/ +theorem satisfiesMultiplicityConstraintsBool_iff_hasMultiplicity {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (Q : CBivariate F) (points : Array (F × F)) (r : Nat) : + CBivariate.satisfiesMultiplicityConstraintsBool Q points r = true ↔ + ∀ point, point ∈ points.toList → + CBivariate.hasMultiplicity Q r point.1 point.2 := by + simp [CBivariate.satisfiesMultiplicityConstraintsBool] + constructor + · intro h x y hmem + have hmemList : (x, y) ∈ points.toList := by + simpa only [Array.mem_def] using hmem + rcases List.getElem_of_mem hmemList with ⟨i, hi, hget⟩ + have hpoint : points[i] = (x, y) := by + simpa [Array.getElem_toList] using hget + exact (multiplicityAtLeastBool_iff_hasMultiplicity Q x y r).1 + (by simpa [hpoint] using h i (by simpa using hi)) + · intro h i hi + have hmem : (points[i].1, points[i].2) ∈ points := by + simpa only [Array.mem_def, Prod.eta] using (Array.getElem_mem_toList hi) + exact (multiplicityAtLeastBool_iff_hasMultiplicity Q points[i].1 points[i].2 r).2 + (h points[i].1 points[i].2 hmem) + +/-- The executable GS point checker agrees with the generic boolean +checker. -/ +theorem multiplicityAtLeastBool_iff_checkMultiplicity {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (Q : CBivariate F) (x y : F) (r : Nat) : + CBivariate.multiplicityAtLeastBool Q x y r = true ↔ + CBivariate.checkMultiplicity Q r x y = true := by + rw [multiplicityAtLeastBool_iff_hasMultiplicity, CBivariate.hasMultiplicity_iff_check] + +/-- The executable GS batch checker agrees pointwise with the generic +boolean checker over the packed point array. -/ +theorem satisfiesMultiplicityConstraintsBool_iff_checkMultiplicity {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (Q : CBivariate F) (points : Array (F × F)) (r : Nat) : + CBivariate.satisfiesMultiplicityConstraintsBool Q points r = true ↔ + ∀ point, point ∈ points.toList → + CBivariate.checkMultiplicity Q r point.1 point.2 = true := by + rw [satisfiesMultiplicityConstraintsBool_iff_hasMultiplicity] + constructor + · intro h point hmem + exact (CBivariate.hasMultiplicity_iff_check Q r point.1 point.2).1 (h point hmem) + · intro h point hmem + exact (CBivariate.hasMultiplicity_iff_check Q r point.1 point.2).2 (h point hmem) + +/-- Direct Hasse evaluation is additive in the input polynomial. -/ +theorem hasseDerivativeEval_add {R : Type*} + [CommSemiring R] [BEq R] [LawfulBEq R] [Nontrivial R] [DecidableEq R] + (a b : Nat) (x y : R) (P Q : CBivariate R) : + hasseDerivativeEval a b x y (P + Q) = + hasseDerivativeEval a b x y P + hasseDerivativeEval a b x y Q := by + rw [← hasseDerivative_eval_eq_eval a b x y (P + Q)] + rw [hasseDerivative_add] + rw [evalEval_add] + rw [hasseDerivative_eval_eq_eval, hasseDerivative_eval_eq_eval] + +/-- Direct Hasse evaluation of zero is zero. -/ +theorem hasseDerivativeEval_zero {R : Type*} + [CommSemiring R] [BEq R] [LawfulBEq R] [Nontrivial R] [DecidableEq R] + (a b : Nat) (x y : R) : + hasseDerivativeEval a b x y (0 : CBivariate R) = 0 := by + rw [← hasseDerivative_eval_eq_eval a b x y (0 : CBivariate R)] + rw [hasseDerivative_zero, evalEval_zero] + +end CBivariate + +end CompPoly diff --git a/CompPoly/Bivariate/GuruswamiSudan/Root.lean b/CompPoly/Bivariate/GuruswamiSudan/Root.lean new file mode 100644 index 00000000..6931bd04 --- /dev/null +++ b/CompPoly/Bivariate/GuruswamiSudan/Root.lean @@ -0,0 +1,13 @@ +/- +Copyright (c) 2026 CompPoly Contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ + +import CompPoly.Bivariate.GuruswamiSudan.Root.Basic + +/-! +# Guruswami-Sudan Root Finding + +Public root-finding surface for the Guruswami-Sudan backend. +-/ diff --git a/CompPoly/Bivariate/GuruswamiSudan/Root/Basic.lean b/CompPoly/Bivariate/GuruswamiSudan/Root/Basic.lean new file mode 100644 index 00000000..25beeb45 --- /dev/null +++ b/CompPoly/Bivariate/GuruswamiSudan/Root/Basic.lean @@ -0,0 +1,13 @@ +/- +Copyright (c) 2026 CompPoly Contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ + +import CompPoly.Bivariate.GuruswamiSudan.Root.RothRuckenstein.Correctness + +/-! +# Guruswami-Sudan Root Backend Basics + +Root backend re-export surface. +-/ diff --git a/CompPoly/Bivariate/GuruswamiSudan/Root/Common.lean b/CompPoly/Bivariate/GuruswamiSudan/Root/Common.lean new file mode 100644 index 00000000..3c1a1ed7 --- /dev/null +++ b/CompPoly/Bivariate/GuruswamiSudan/Root/Common.lean @@ -0,0 +1,64 @@ +/- +Copyright (c) 2026 CompPoly Contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ + +import CompPoly.Bivariate.GuruswamiSudan.Root.FieldRoots + +/-! +# Common Guruswami-Sudan Root Helpers + +Executable helpers shared by bounded bivariate root backends. +-/ + +namespace CompPoly + +namespace GuruswamiSudan + +/-- The coefficient of `Y^j` in `Q(0, Y)`, as a polynomial in the next root +coefficient. -/ +def initialCoefficientPolynomial {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (Q : CBivariate F) : CPolynomial F := + (List.range Q.val.size).foldl + (fun out y ↦ out + CPolynomial.monomial y (CBivariate.coeff Q 0 y)) + 0 + +/-- Executable final check for the GS root condition. -/ +def isRootYDegreeLtBool {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] + (Q : CBivariate F) (k : Nat) (p : CPolynomial F) : Bool := + degreeLtBool p k && CBivariate.composeYHorner Q p == 0 + +/-- Filter a candidate family to exact bounded-degree roots. -/ +def rootsYDegreeLtFromCandidates {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] + (candidates : Array (CPolynomial F)) (Q : CBivariate F) (k : Nat) : + Array (CPolynomial F) := + candidates.filter fun p ↦ isRootYDegreeLtBool Q k p + +/-- Extend one candidate prefix by one coefficient at `X^depth`. -/ +def extendPrefix {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [DecidableEq F] + (pref : CPolynomial F) (depth : Nat) (coeff : F) : CPolynomial F := + pref + CPolynomial.monomial depth coeff + +/-- Truncate a polynomial to its first `n` coefficients. -/ +def polynomialPrefix {R : Type*} [Zero R] [BEq R] [LawfulBEq R] + (p : CPolynomial R) (n : Nat) : CPolynomial R := + CPolynomial.truncate p n + +/-- Query a field-root backend only for nonzero equations. + +A zero equation imposes no restriction on the next coefficient. Enumerating all +field elements is unsuitable for large fields, so bounded-root backends use +residual normalization to avoid zero equations for nonzero bivariate inputs. +-/ +def rootsInFieldForNonzeroEquation {F : Type*} [Field F] [BEq F] [LawfulBEq F] + (fieldRoots : FieldRootContext F) (p : CPolynomial F) : Array F := + if p == 0 then #[] else fieldRoots.rootsInField p + +end GuruswamiSudan + +end CompPoly diff --git a/CompPoly/Bivariate/GuruswamiSudan/Root/Common/Lemmas.lean b/CompPoly/Bivariate/GuruswamiSudan/Root/Common/Lemmas.lean new file mode 100644 index 00000000..c1555155 --- /dev/null +++ b/CompPoly/Bivariate/GuruswamiSudan/Root/Common/Lemmas.lean @@ -0,0 +1,592 @@ +/- +Copyright (c) 2026 CompPoly Contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ + +import CompPoly.Bivariate.GuruswamiSudan.Root.Common +import CompPoly.Bivariate.GuruswamiSudan.PolynomialCorrectness + +/-! +# Common Guruswami-Sudan Root Helper Lemmas + +Reusable proof facts for bounded bivariate root backends. +-/ + +namespace CompPoly + +theorem array_mem_eraseDups_fold {α : Type*} [BEq α] [LawfulBEq α] + (a : α) : ∀ (xs : List α) (out : Array α), + a ∈ List.foldl (fun out x ↦ if x ∈ out then out else out.push x) out xs → + a ∈ out ∨ a ∈ xs := by + intro xs + induction xs with + | nil => + intro out h + exact Or.inl h + | cons x xs ih => + intro out h + simp only [List.foldl_cons] at h + by_cases hx : x ∈ out + · have h' := ih out (by simpa [hx] using h) + cases h' with + | inl hout => exact Or.inl hout + | inr hxs => exact Or.inr (by simp [hxs]) + · have h' := ih (out.push x) (by simpa [hx] using h) + cases h' with + | inl hout => + simp at hout + cases hout with + | inl hout => exact Or.inl hout + | inr hax => exact Or.inr (by simp [hax]) + | inr hxs => exact Or.inr (by simp [hxs]) + +theorem array_mem_of_mem_eraseDups {α : Type*} [BEq α] [LawfulBEq α] + {xs : Array α} {a : α} (h : a ∈ xs.eraseDups) : a ∈ xs := by + unfold Array.eraseDups at h + rcases xs with ⟨l⟩ + simp at h ⊢ + have hh := array_mem_eraseDups_fold a l #[] h + simpa using hh + +theorem array_mem_eraseDups_fold_of_mem {α : Type*} [BEq α] [LawfulBEq α] + (a : α) : ∀ (xs : List α) (out : Array α), + a ∈ out ∨ a ∈ xs → + a ∈ List.foldl (fun out x ↦ if x ∈ out then out else out.push x) out xs + | [], out, h => by + rcases h with h | h + · exact h + · simp at h + | x :: xs, out, h => by + rw [List.foldl_cons] + by_cases hx : x ∈ out + · rw [if_pos hx] + apply array_mem_eraseDups_fold_of_mem a xs out + rcases h with h | h + · exact Or.inl h + · simp at h + rcases h with rfl | h + · exact Or.inl hx + · exact Or.inr h + · rw [if_neg hx] + apply array_mem_eraseDups_fold_of_mem a xs (out.push x) + rcases h with h | h + · exact Or.inl (by simp [h]) + · simp at h + rcases h with rfl | h + · exact Or.inl (by simp) + · exact Or.inr h + +theorem array_mem_eraseDups_of_mem {α : Type*} [BEq α] [LawfulBEq α] + {xs : Array α} {a : α} (h : a ∈ xs) : a ∈ xs.eraseDups := by + unfold Array.eraseDups + rcases xs with ⟨l⟩ + simp at h ⊢ + exact array_mem_eraseDups_fold_of_mem a l #[] (Or.inr h) + +namespace GuruswamiSudan + +theorem degreeLt_of_degreeLtBool {F : Type*} [Zero F] + {p : CPolynomial F} {k : Nat} (h : degreeLtBool p k = true) : degreeLt p k := by + rw [degreeLtBool] at h + simp at h + unfold degreeLt CPolynomial.degree + cases hs : p.val.size with + | zero => simp + | succ n => + simp + omega + +theorem degreeLtBool_of_degreeLt {F : Type*} [Zero F] + {p : CPolynomial F} {k : Nat} (h : degreeLt p k) : degreeLtBool p k = true := by + rw [degreeLtBool] + unfold degreeLt CPolynomial.degree at h + cases hs : p.val.size with + | zero => + simp + | succ n => + rw [hs] at h + simp at h + simp + omega + +theorem cpoly_truncate_coeff {R : Type*} [Zero R] [BEq R] [LawfulBEq R] + (p : CPolynomial R) (n i : Nat) : + (CPolynomial.truncate p n).coeff i = if i < n then p.coeff i else 0 := by + unfold CPolynomial.truncate CPolynomial.ofArray CPolynomial.coeff + rw [CPolynomial.Raw.Trim.coeff_eq_coeff] + unfold CPolynomial.Raw.coeff + simp [Array.getElem?_extract] + by_cases hin : i < n + · by_cases hip : i < p.val.size + · simp [hin, hip] + · simp [hin, hip] + · simp [hin] + +theorem cbivar_coeff_truncateX {R : Type*} [Zero R] [BEq R] [LawfulBEq R] + (Q : CBivariate R) (n i j : Nat) : + CBivariate.coeff (CBivariate.truncateX Q n) i j = + if i < n then CBivariate.coeff Q i j else 0 := by + unfold CBivariate.coeff CBivariate.truncateX + unfold CPolynomial.ofArray + rw [CPolynomial.Raw.Trim.coeff_eq_coeff] + unfold CPolynomial.Raw.coeff + by_cases hj : j < Q.val.size + · rw [Array.getD_eq_getD_getElem?, Array.getElem?_map, Array.getElem?_eq_getElem hj] + simpa [Array.getD_eq_getD_getElem?, Array.getElem?_eq_getElem hj] using + cpoly_truncate_coeff Q.val[j] n i + · have hjle : Q.val.size ≤ j := Nat.le_of_not_lt hj + have hmaple : (Q.val.map fun coeff ↦ CPolynomial.truncate coeff n).size ≤ j := by + simpa using hjle + rw [Array.getD_eq_getD_getElem?, Array.getElem?_eq_none hmaple] + change (0 : CPolynomial R).coeff i = + if i < n then (Array.getD Q.val j 0).coeff i else 0 + rw [CPolynomial.coeff_zero] + by_cases hi : i < n + · rw [if_pos hi] + rw [Array.getD_eq_getD_getElem?, Array.getElem?_eq_none hjle] + change 0 = (0 : CPolynomial R).coeff i + rw [CPolynomial.coeff_zero] + · rw [if_neg hi] + +theorem polynomialPrefix_zero {R : Type*} [Zero R] [BEq R] [LawfulBEq R] + (p : CPolynomial R) : polynomialPrefix p 0 = 0 := by + rw [CPolynomial.eq_iff_coeff] + intro i + unfold polynomialPrefix + rw [cpoly_truncate_coeff] + simp + rfl + +theorem polynomialPrefix_succ {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (p : CPolynomial F) (depth : Nat) : + polynomialPrefix p (depth + 1) = + extendPrefix (polynomialPrefix p depth) depth (p.coeff depth) := by + rw [CPolynomial.eq_iff_coeff] + intro i + unfold polynomialPrefix extendPrefix + rw [cpoly_truncate_coeff, CPolynomial.coeff_add, cpoly_truncate_coeff, + CPolynomial.coeff_monomial] + by_cases hi : i = depth + · subst i + simp + · by_cases hlt : i < depth + · have hltSucc : i < depth + 1 := by omega + simp [hi, hlt, hltSucc] + · have hnotSucc : ¬ i < depth + 1 := by omega + simp [hi, hlt, hnotSucc] + +theorem polynomialPrefix_eq_self_of_degreeLt {F : Type*} + [Zero F] [BEq F] [LawfulBEq F] + {p : CPolynomial F} {k : Nat} (hdegree : degreeLt p k) : + polynomialPrefix p k = p := by + rw [CPolynomial.eq_iff_coeff] + intro i + unfold polynomialPrefix + rw [cpoly_truncate_coeff] + by_cases hi : i < k + · simp [hi] + · have hb := degreeLtBool_of_degreeLt hdegree + rw [degreeLtBool] at hb + simp at hb + have hsize : p.val.size ≤ i := by omega + rw [if_neg hi, CPolynomial.coeff_eq_zero_of_size_le p hsize] + +theorem list_foldl_add_eq_sum {R : Type*} [AddMonoid R] + (f : Nat → R) : ∀ (xs : List Nat) (acc : R), + List.foldl (fun acc i ↦ acc + f i) acc xs = acc + (xs.map f).sum + | [], acc => by simp + | x :: xs, acc => by + rw [List.foldl_cons, list_foldl_add_eq_sum f xs (acc + f x)] + exact (_root_.add_assoc acc (f x) ((xs.map f).sum)) + +theorem list_sum_map_range_eq_finset_sum {R : Type*} [AddCommMonoid R] + (f : Nat → R) : ∀ n : Nat, + (List.map f (List.range n)).sum = ∑ i ∈ Finset.range n, f i + | 0 => by simp + | n + 1 => by + rw [List.sum_range_succ, Finset.sum_range_succ, list_sum_map_range_eq_finset_sum f n] + +theorem cpoly_eval_add {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] + (p q : CPolynomial R) (c : R) : + CPolynomial.eval c (p + q) = CPolynomial.eval c p + CPolynomial.eval c q := by + rw [CPolynomial.eval_toPoly, CPolynomial.toPoly_add, Polynomial.eval_add, + ← CPolynomial.eval_toPoly, ← CPolynomial.eval_toPoly] + +theorem cpoly_eval_monomial {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] [DecidableEq R] + (y : Nat) (a c : R) : + (CPolynomial.monomial y a).eval c = a * c ^ y := by + rw [CPolynomial.eval_toPoly] + rw [show (CPolynomial.monomial y a : CPolynomial R).toPoly = + Polynomial.monomial y a from CPolynomial.monomial_toPoly (R := R) y a] + simp [Polynomial.eval_monomial] + +theorem composeY_add {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] + (P Q : CBivariate R) (p : CPolynomial R) : + CBivariate.composeY (P + Q) p = CBivariate.composeY P p + CBivariate.composeY Q p := by + unfold CBivariate.composeY + exact cpoly_eval_add P Q p + +theorem composeY_outer_monomial {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] [DecidableEq R] + (c p : CPolynomial R) (y : Nat) : + CBivariate.composeY (CPolynomial.monomial y c : CBivariate R) p = c * p ^ y := by + unfold CBivariate.composeY + exact cpoly_eval_monomial y c p + +theorem cpoly_powCoeff_eq_coeff_pow {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] + (p : CPolynomial R) (k n : Nat) : + CPolynomial.powCoeff p k n = (p ^ k : CPolynomial R).coeff n := by + induction k generalizing n with + | zero => + simp only [CPolynomial.powCoeff, pow_zero] + rw [CPolynomial.coeff_one] + | succ k ih => + unfold CPolynomial.powCoeff + rw [list_foldl_add_eq_sum] + rw [pow_succ'] + rw [CPolynomial.coeff_mul] + rw [list_sum_map_range_eq_finset_sum] + simp [ih] + +theorem cpoly_mulPowCoeff_eq_coeff_mul_pow {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] + (a p : CPolynomial R) (k n : Nat) : + CPolynomial.mulPowCoeff a p k n = (a * (p ^ k : CPolynomial R)).coeff n := by + unfold CPolynomial.mulPowCoeff + rw [list_foldl_add_eq_sum] + rw [CPolynomial.coeff_mul] + rw [list_sum_map_range_eq_finset_sum] + simp [cpoly_powCoeff_eq_coeff_pow] + +theorem cpoly_coeff_zero_mul {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] + (p q : CPolynomial F) : + (p * q).coeff 0 = p.coeff 0 * q.coeff 0 := by + rw [CPolynomial.coeff_mul] + rw [show Finset.range (0 + 1) = Finset.range 1 by rfl] + rw [Finset.sum_range_succ] + rw [Finset.sum_range_zero, zero_add, tsub_zero] + +theorem cpoly_coeff_zero_pow {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (p : CPolynomial F) : + ∀ n : Nat, (p ^ n : CPolynomial F).coeff 0 = p.coeff 0 ^ n := by + intro n + induction n with + | zero => + rw [pow_zero, pow_zero] + rw [CPolynomial.coeff_one] + simp + | succ n ih => + rw [pow_succ, pow_succ, CPolynomial.coeff_mul] + rw [show Finset.range (0 + 1) = Finset.range 1 by rfl] + rw [Finset.sum_range_succ] + rw [Finset.sum_range_zero, zero_add, tsub_zero] + rw [ih] + +theorem cpoly_coeff_zero_pow_monomial_zero {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [DecidableEq F] + (c : F) : + ∀ n : Nat, ((CPolynomial.monomial 0 c) ^ n : CPolynomial F).coeff 0 = c ^ n := by + intro n + rw [cpoly_coeff_zero_pow] + rw [CPolynomial.coeff_monomial] + rfl + +theorem cpoly_mulPowCoeff_monomial_zero_depth_zero {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [DecidableEq F] + (a : CPolynomial F) (c : F) (y : Nat) : + CPolynomial.mulPowCoeff a (CPolynomial.monomial 0 c) y 0 = a.coeff 0 * c ^ y := by + rw [cpoly_mulPowCoeff_eq_coeff_mul_pow] + rw [CPolynomial.coeff_mul] + rw [show Finset.range (0 + 1) = Finset.range 1 by rfl] + rw [Finset.sum_range_succ] + rw [Finset.sum_range_zero, zero_add, tsub_zero] + rw [cpoly_coeff_zero_pow_monomial_zero] + +theorem composeY_coeff_zero_zipIdx_eq_range_aux {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] + (xs : List (CPolynomial F)) (p accPoly : CPolynomial F) (accCoeff : F) + (offset : Nat) (hacc : accPoly.coeff 0 = accCoeff) : + List.foldl + (fun acc y ↦ acc + (xs.getD (y - offset) 0).coeff 0 * p.coeff 0 ^ y) + accCoeff (List.range' offset xs.length) = + (List.foldl (fun acc x ↦ acc + x.1 * p ^ x.2) accPoly + (xs.zipIdx offset)).coeff 0 := by + induction xs generalizing accPoly accCoeff offset with + | nil => + simp [hacc] + | cons x xs ih => + simp only [List.length_cons, List.zipIdx_cons, List.foldl_cons] + rw [List.range'_succ, List.foldl_cons] + rw [show offset - offset = 0 by omega] + simp only [List.getD_cons_zero] + rw [List.foldl_congr_of_mem + (f := fun acc y ↦ + acc + ((x :: xs).getD (y - offset) 0).coeff 0 * p.coeff 0 ^ y) + (g := fun acc y ↦ + acc + (xs.getD (y - (offset + 1)) 0).coeff 0 * p.coeff 0 ^ y) + (List.range' (offset + 1) xs.length) + (accCoeff + x.coeff 0 * p.coeff 0 ^ offset) + (fun _acc y hy ↦ by + have hsub : y - offset = (y - (offset + 1)) + 1 := by + have hymem := List.mem_range'.mp hy + omega + simp [hsub])] + apply ih + rw [CPolynomial.coeff_add, cpoly_coeff_zero_mul, cpoly_coeff_zero_pow, hacc] + +theorem composeY_coeff_zero_fold_eq {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] + (coeffs : Array (CPolynomial F)) (p : CPolynomial F) : + List.foldl (fun acc y ↦ acc + (coeffs.getD y 0).coeff 0 * p.coeff 0 ^ y) 0 + (List.range' 0 coeffs.size) = + (Array.foldl (fun acc x ↦ acc + x.1 * p ^ x.2) 0 coeffs.zipIdx).coeff 0 := by + rw [Array.foldl_zipIdx_eq_foldl_toList_zipIdx] + simpa using + (composeY_coeff_zero_zipIdx_eq_range_aux coeffs.toList p (0 : CPolynomial F) 0 0 + (by rw [CPolynomial.coeff_zero])) + +theorem composeY_coeff_zipIdx_eq_range_aux {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] + (xs : List (CPolynomial R)) (p accPoly : CPolynomial R) (accCoeff : R) + (offset n : Nat) (hacc : accPoly.coeff n = accCoeff) : + List.foldl + (fun acc y ↦ acc + ((xs.getD (y - offset) 0) * p ^ y).coeff n) + accCoeff (List.range' offset xs.length) = + (List.foldl (fun acc x ↦ acc + x.1 * p ^ x.2) accPoly + (xs.zipIdx offset)).coeff n := by + induction xs generalizing accPoly accCoeff offset with + | nil => + simp [hacc] + | cons x xs ih => + simp only [List.length_cons, List.zipIdx_cons, List.foldl_cons] + rw [List.range'_succ, List.foldl_cons] + rw [show offset - offset = 0 by omega] + simp only [List.getD_cons_zero] + rw [List.foldl_congr_of_mem + (f := fun acc y ↦ + acc + (((x :: xs).getD (y - offset) 0) * p ^ y).coeff n) + (g := fun acc y ↦ + acc + ((xs.getD (y - (offset + 1)) 0) * p ^ y).coeff n) + (List.range' (offset + 1) xs.length) + (accCoeff + (x * p ^ offset).coeff n) + (fun _acc y hy ↦ by + have hsub : y - offset = (y - (offset + 1)) + 1 := by + have hymem := List.mem_range'.mp hy + omega + simp [hsub])] + apply ih + rw [CPolynomial.coeff_add, hacc] + +theorem composeY_coeff_fold_eq {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] + (coeffs : Array (CPolynomial R)) (p : CPolynomial R) (n : Nat) : + List.foldl (fun acc y ↦ acc + ((coeffs.getD y 0) * p ^ y).coeff n) 0 + (List.range' 0 coeffs.size) = + (Array.foldl (fun acc x ↦ acc + x.1 * p ^ x.2) 0 coeffs.zipIdx).coeff n := by + rw [Array.foldl_zipIdx_eq_foldl_toList_zipIdx] + simpa using + (composeY_coeff_zipIdx_eq_range_aux coeffs.toList p (0 : CPolynomial R) 0 0 n + (by rw [CPolynomial.coeff_zero])) + +theorem composeYCoeff_eq_composeY_coeff {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] + (Q : CBivariate R) (p : CPolynomial R) (depth : Nat) : + CBivariate.composeYCoeff Q p depth = (CBivariate.composeY Q p).coeff depth := by + unfold CBivariate.composeYCoeff CBivariate.composeY CPolynomial.eval + simp [cpoly_mulPowCoeff_eq_coeff_mul_pow] + simpa using (composeY_coeff_fold_eq Q.val p depth) + +theorem fold_range_coeff_add_mul_pow {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] + (coeffs : Array (CPolynomial R)) (p : CPolynomial R) : + ∀ (ys : List Nat) (acc : CPolynomial R) (accCoeff : R) (n : Nat), + acc.coeff n = accCoeff → + (List.foldl (fun acc y ↦ acc + coeffs.getD y 0 * p ^ y) acc ys).coeff n = + List.foldl + (fun acc y ↦ acc + ((coeffs.getD y 0) * p ^ y).coeff n) + accCoeff ys := by + intro ys + induction ys with + | nil => + intro acc accCoeff n hacc + simpa using hacc + | cons y ys ih => + intro acc accCoeff n hacc + simp only [List.foldl_cons] + apply ih + rw [CPolynomial.coeff_add, hacc] + +theorem composeY_eq_range_fold {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] + (Q : CBivariate R) (p : CPolynomial R) : + CBivariate.composeY Q p = + (List.range' 0 Q.val.size).foldl + (fun acc y ↦ acc + Q.val.coeff y * p ^ y) 0 := by + rw [CPolynomial.eq_iff_coeff] + intro n + rw [fold_range_coeff_add_mul_pow Q.val p (List.range' 0 Q.val.size) 0 0 n + (CPolynomial.coeff_zero n)] + rw [← composeYCoeff_eq_composeY_coeff] + unfold CBivariate.composeYCoeff + simp [cpoly_mulPowCoeff_eq_coeff_mul_pow] + +theorem composeY_zero {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] + (p : CPolynomial R) : + CBivariate.composeY (0 : CBivariate R) p = 0 := by + rw [composeY_eq_range_fold] + rfl + +theorem composeY_toPoly {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (Q : CBivariate F) (p : CPolynomial F) : + (CBivariate.composeY Q p).toPoly = (CBivariate.toPoly Q).eval p.toPoly := by + unfold CBivariate.composeY + rw [CPolynomial.eval_toPoly] + rw [CBivariate.toPoly_eq_map] + rw [Polynomial.eval_map] + exact (Polynomial.eval₂_hom + (f := (CPolynomial.ringEquiv (R := F)).toRingHom) + (p := CPolynomial.toPoly Q) (x := p)).symm + +theorem initialCoefficientPolynomial_evalHorner_eq_composeYCoeff_monomial_zero + {F : Type*} [Field F] [BEq F] [LawfulBEq F] [DecidableEq F] + (Q : CBivariate F) (c : F) : + (initialCoefficientPolynomial Q).evalHorner c = + CBivariate.composeYCoeff Q (CPolynomial.monomial 0 c) 0 := by + rw [CPolynomial.eval_horner_eq_eval] + unfold initialCoefficientPolynomial CBivariate.composeYCoeff + rw [List.range_eq_range'] + let polyStep : CPolynomial F → Nat → CPolynomial F := + fun out y ↦ out + CPolynomial.monomial y (CBivariate.coeff Q 0 y) + let coeffStep : F → Nat → F := + fun acc y ↦ acc + CPolynomial.mulPowCoeff (Q.val.coeff y) + (CPolynomial.monomial 0 c) y 0 + change CPolynomial.eval c (List.foldl polyStep 0 (List.range' 0 Q.val.size)) = + List.foldl coeffStep 0 (List.range' 0 Q.val.size) + have hfold : ∀ (xs : List Nat) (out : CPolynomial F) (acc : F), + CPolynomial.eval c out = acc → + CPolynomial.eval c (List.foldl polyStep out xs) = List.foldl coeffStep acc xs := by + intro xs + induction xs with + | nil => + intro out acc hacc + simpa using hacc + | cons y ys ih => + intro out acc hacc + simp only [List.foldl_cons] + apply ih + dsimp [polyStep, coeffStep] + rw [cpoly_eval_add, hacc, cpoly_eval_monomial, + cpoly_mulPowCoeff_monomial_zero_depth_zero] + exact hfold (List.range' 0 Q.val.size) 0 0 + (by simp [CPolynomial.eval_toPoly, CPolynomial.toPoly_zero]) + +theorem composeYCoeff_monomial_zero_eq_composeY_coeff_zero {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (Q : CBivariate F) (p : CPolynomial F) : + CBivariate.composeYCoeff Q (CPolynomial.monomial 0 (p.coeff 0)) 0 = + (CBivariate.composeY Q p).coeff 0 := by + unfold CBivariate.composeYCoeff CBivariate.composeY CPolynomial.eval + simp [cpoly_mulPowCoeff_monomial_zero_depth_zero] + simpa using (composeY_coeff_zero_fold_eq Q.val p) + +theorem initialCoefficientPolynomial_eval_eq_composeY_coeff_zero {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (Q : CBivariate F) (p : CPolynomial F) : + CPolynomial.eval (p.coeff 0) (initialCoefficientPolynomial Q) = + (CBivariate.composeY Q p).coeff 0 := by + rw [← CPolynomial.eval_horner_eq_eval] + rw [initialCoefficientPolynomial_evalHorner_eq_composeYCoeff_monomial_zero] + exact composeYCoeff_monomial_zero_eq_composeY_coeff_zero Q p + +theorem rootsInFieldForNonzeroEquation_complete {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (fieldRoots : FieldRootContext F) {p : CPolynomial F} {a : F} + (hp : p ≠ 0) (ha : CPolynomial.eval a p = 0) : + a ∈ (rootsInFieldForNonzeroEquation fieldRoots p).toList := by + unfold rootsInFieldForNonzeroEquation + rw [if_neg] + · exact fieldRoots.complete p a hp ha + · intro hbeq + exact hp (beq_iff_eq.mp hbeq) + +theorem composeY_of_composeYHorner_eq_zero {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] + {Q : CBivariate F} {p : CPolynomial F} + (h : CBivariate.composeYHorner Q p = 0) : CBivariate.composeY Q p = 0 := by + simpa [CBivariate.composeY, CBivariate.composeYHorner, CPolynomial.eval_horner_eq_eval] using h + +theorem composeYHorner_eq_zero_of_composeY {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] + {Q : CBivariate F} {p : CPolynomial F} + (h : CBivariate.composeY Q p = 0) : CBivariate.composeYHorner Q p = 0 := by + simpa [CBivariate.composeY, CBivariate.composeYHorner, CPolynomial.eval_horner_eq_eval] using h + +theorem isRootYDegreeLtBool_of_root {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] + {Q : CBivariate F} {p : CPolynomial F} {k : Nat} + (hdegree : degreeLt p k) (hroot : CBivariate.composeY Q p = 0) : + isRootYDegreeLtBool Q k p = true := by + unfold isRootYDegreeLtBool + rw [degreeLtBool_of_degreeLt hdegree, composeYHorner_eq_zero_of_composeY hroot] + simp + +theorem rootsYDegreeLtFromCandidates_sound {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] + {candidates : Array (CPolynomial F)} {Q : CBivariate F} {k : Nat} + {p : CPolynomial F} + (h : p ∈ (rootsYDegreeLtFromCandidates candidates Q k).toList) : + degreeLt p k ∧ CBivariate.composeY Q p = 0 := by + unfold rootsYDegreeLtFromCandidates at h + simp [isRootYDegreeLtBool] at h + exact ⟨degreeLt_of_degreeLtBool h.2.1, composeY_of_composeYHorner_eq_zero h.2.2⟩ + +theorem rootsYDegreeLtFromCandidates_eraseDups_sound {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] + {candidates : Array (CPolynomial F)} {Q : CBivariate F} {k : Nat} + {p : CPolynomial F} + (h : p ∈ (rootsYDegreeLtFromCandidates candidates Q k).eraseDups.toList) : + degreeLt p k ∧ CBivariate.composeY Q p = 0 := by + have hmem : p ∈ rootsYDegreeLtFromCandidates candidates Q k := + array_mem_of_mem_eraseDups (by simpa using h) + exact rootsYDegreeLtFromCandidates_sound (by simpa using hmem) + +theorem rootsYDegreeLtFromCandidates_complete_of_mem {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] + {candidates : Array (CPolynomial F)} {Q : CBivariate F} {k : Nat} + {p : CPolynomial F} + (hmem : p ∈ candidates.toList) + (hdegree : degreeLt p k) (hroot : CBivariate.composeY Q p = 0) : + p ∈ (rootsYDegreeLtFromCandidates candidates Q k).toList := by + unfold rootsYDegreeLtFromCandidates + simp [hmem, isRootYDegreeLtBool_of_root hdegree hroot] + +theorem rootsYDegreeLtFromCandidates_eraseDups_complete_of_mem {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] + {candidates : Array (CPolynomial F)} {Q : CBivariate F} {k : Nat} + {p : CPolynomial F} + (hmem : p ∈ candidates.toList) + (hdegree : degreeLt p k) (hroot : CBivariate.composeY Q p = 0) : + p ∈ (rootsYDegreeLtFromCandidates candidates Q k).eraseDups.toList := by + have hfilterList : + p ∈ (rootsYDegreeLtFromCandidates candidates Q k).toList := + rootsYDegreeLtFromCandidates_complete_of_mem hmem hdegree hroot + have hfilterArray : + p ∈ rootsYDegreeLtFromCandidates candidates Q k := by + simpa using hfilterList + have herase : p ∈ (rootsYDegreeLtFromCandidates candidates Q k).eraseDups := + array_mem_eraseDups_of_mem hfilterArray + simpa using herase + +end GuruswamiSudan + +end CompPoly diff --git a/CompPoly/Bivariate/GuruswamiSudan/Root/FieldRoots.lean b/CompPoly/Bivariate/GuruswamiSudan/Root/FieldRoots.lean new file mode 100644 index 00000000..1e60d44b --- /dev/null +++ b/CompPoly/Bivariate/GuruswamiSudan/Root/FieldRoots.lean @@ -0,0 +1,54 @@ +/- +Copyright (c) 2026 CompPoly Contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ + +import CompPoly.Bivariate.GuruswamiSudan.Context +import CompPoly.Bivariate.GuruswamiSudan.Root.FieldRoots.FiniteField +import CompPoly.Univariate.Roots.Enumeration + +/-! +# Guruswami-Sudan Field Roots + +Executable univariate field-root helpers used by Roth-Ruckenstein recursion. +The explicit `FieldRootContext` context makes this dependency replaceable for +large concrete fields. +-/ + +namespace CompPoly + +namespace GuruswamiSudan + +/-- Executable degree-`< k` check for canonical polynomials. -/ +def degreeLtBool {F : Type*} [Zero F] (p : CPolynomial F) (k : Nat) : Bool := + p.val.size ≤ k + +/-- Compatibility alias for the univariate exhaustive-enumeration predicate. -/ +abbrev ContainsAllFieldElements {F : Type*} (elements : Array F) : Prop := + CPolynomial.Roots.FiniteField.ContainsAllFieldElements elements + +/-- Compatibility wrapper for roots by exhaustive evaluation over an explicit field list. -/ +def rootsInFieldByEnumeration {F : Type*} [Field F] [BEq F] [LawfulBEq F] + (elements : Array F) (hElements : ContainsAllFieldElements elements) + (p : CPolynomial F) : Array F := + let enumeration := + CPolynomial.Roots.FiniteField.fieldEnumerationOfArray elements hElements + CPolynomial.Roots.FiniteField.rootsInFieldByEnumeration enumeration p + +/-- Field roots by explicit enumeration over a supplied field-element list. -/ +def enumeratingFieldRootContext (F : Type*) [Field F] [BEq F] [LawfulBEq F] + (elements : Array F) (hElements : ContainsAllFieldElements elements) : + FieldRootContext F where + rootsInField := rootsInFieldByEnumeration elements hElements + sound := by + intro p a h + exact CPolynomial.Roots.FiniteField.rootsInFieldByEnumeration_sound h + complete := by + intro p a hp h + exact CPolynomial.Roots.FiniteField.rootsInFieldByEnumeration_complete + (CPolynomial.Roots.FiniteField.fieldEnumerationOfArray elements hElements) h + +end GuruswamiSudan + +end CompPoly diff --git a/CompPoly/Bivariate/GuruswamiSudan/Root/FieldRoots/FiniteField.lean b/CompPoly/Bivariate/GuruswamiSudan/Root/FieldRoots/FiniteField.lean new file mode 100644 index 00000000..c7cd023f --- /dev/null +++ b/CompPoly/Bivariate/GuruswamiSudan/Root/FieldRoots/FiniteField.lean @@ -0,0 +1,86 @@ +/- +Copyright (c) 2026 CompPoly Contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ + +import CompPoly.Bivariate.GuruswamiSudan.Context +import CompPoly.Univariate.Roots.Correctness +import CompPoly.Univariate.Roots.SmoothSubgroup + +/-! +# Guruswami-Sudan Finite-Field Root Adapter + +Adapter from the reusable finite-field univariate root operation to the certified +`FieldRootContext` context consumed by Roth-Ruckenstein root finding. +-/ + +namespace CompPoly + +namespace GuruswamiSudan + +/-- Package the generic finite-field root finder as a GS field-root backend. -/ +def finiteFieldRootContextWith (F : Type*) [Field F] [BEq F] [LawfulBEq F] + (M : CPolynomial.Raw.MulContext F) (D : CPolynomial.Raw.ModContext F) + (ctx : CPolynomial.Roots.FiniteField.FiniteFieldContext F) + (splitter : CPolynomial.Roots.FiniteField.LinearFactorProductSplitter F) + (splitterValid : + ∀ {p : CPolynomial F}, p ≠ 0 → + splitter.validInput ctx.q + (CPolynomial.Roots.FiniteField.finiteFieldRootProductWith M D ctx p)) : + FieldRootContext F where + rootsInField := CPolynomial.Roots.FiniteField.rootsInFiniteFieldWith M D ctx splitter + sound := by + intro p a h + exact CPolynomial.Roots.FiniteField.rootsInFiniteFieldWith_sound M D ctx splitter h + complete := by + intro p a hp hroot + exact CPolynomial.Roots.FiniteField.rootsInFiniteFieldWith_complete + M D ctx splitter splitterValid hp hroot + +/-- Package the generic finite-field root finder with the default raw arithmetic backends. -/ +def finiteFieldRootContext (F : Type*) [Field F] [BEq F] [LawfulBEq F] + (ctx : CPolynomial.Roots.FiniteField.FiniteFieldContext F) + (splitter : CPolynomial.Roots.FiniteField.LinearFactorProductSplitter F) + (splitterValid : + ∀ {p : CPolynomial F}, p ≠ 0 → + splitter.validInput ctx.q (CPolynomial.Roots.FiniteField.finiteFieldRootProduct ctx p)) : + FieldRootContext F := + finiteFieldRootContextWith F CPolynomial.Raw.MulContext.naive + CPolynomial.Raw.ModContext.naive ctx splitter (by + intro p hp + exact splitterValid hp) + +/-- Package a smooth cyclic splitter as a GS field-root backend. -/ +def smoothFiniteFieldRootContextWith (F : Type*) [Field F] [BEq F] [LawfulBEq F] + (M : CPolynomial.Raw.MulContext F) (D : CPolynomial.Raw.ModContext F) + (ctx : CPolynomial.Roots.FiniteField.FiniteFieldContext F) + (smoothCtx : CPolynomial.Roots.FiniteField.SmoothCyclicRootContext F) + (smoothValid : + ∀ {p : CPolynomial F}, p ≠ 0 → + smoothCtx.validInput + (CPolynomial.Roots.FiniteField.finiteFieldRootProductWith M D ctx p)) : + FieldRootContext F := + finiteFieldRootContextWith F M D ctx + (CPolynomial.Roots.FiniteField.smoothLinearFactorProductSplitterWith M D smoothCtx) + (by + intro p hp + exact smoothValid hp) + +/-- Package a smooth cyclic splitter with default raw arithmetic as a GS field-root backend. -/ +def smoothFiniteFieldRootContext (F : Type*) [Field F] [BEq F] [LawfulBEq F] + (ctx : CPolynomial.Roots.FiniteField.FiniteFieldContext F) + (smoothCtx : CPolynomial.Roots.FiniteField.SmoothCyclicRootContext F) + (smoothValid : + ∀ {p : CPolynomial F}, p ≠ 0 → + smoothCtx.validInput + (CPolynomial.Roots.FiniteField.finiteFieldRootProduct ctx p)) : + FieldRootContext F := + smoothFiniteFieldRootContextWith F CPolynomial.Raw.MulContext.naive + CPolynomial.Raw.ModContext.naive ctx smoothCtx (by + intro p hp + exact smoothValid hp) + +end GuruswamiSudan + +end CompPoly diff --git a/CompPoly/Bivariate/GuruswamiSudan/Root/FieldRoots/KoalaBear.lean b/CompPoly/Bivariate/GuruswamiSudan/Root/FieldRoots/KoalaBear.lean new file mode 100644 index 00000000..867c2885 --- /dev/null +++ b/CompPoly/Bivariate/GuruswamiSudan/Root/FieldRoots/KoalaBear.lean @@ -0,0 +1,241 @@ +/- +Copyright (c) 2026 CompPoly Contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ + +import CompPoly.Bivariate.GuruswamiSudan.Root.FieldRoots.FiniteField +import CompPoly.Fields.KoalaBear +import CompPoly.Univariate.NTT.KoalaBear +import CompPoly.Univariate.Roots.SmoothSubgroup + +/-! +# KoalaBear Guruswami-Sudan Field-Root Backends + +Concrete finite-field root backends for canonical KoalaBear and native-word fast +KoalaBear. Both use the generic finite-field algorithm directly over their field +carriers. +-/ + +namespace CompPoly + +namespace GuruswamiSudan + +/-- Finite-field context for canonical KoalaBear. -/ +def koalaBearFiniteFieldContext : + CPolynomial.Roots.FiniteField.FiniteFieldContext KoalaBear.Field where + q := KoalaBear.fieldSize + finite := by infer_instance + card_eq := by + simp [KoalaBear.Field, KoalaBear.fieldSize, Nat.card_eq_fintype_card, ZMod.card] + frobenius_fixed := by + intro a + simpa [KoalaBear.Field, KoalaBear.fieldSize] using ZMod.pow_card a + +/-- Smooth cyclic splitter context for canonical KoalaBear. -/ +def koalaBearSmoothCyclicRootContext : + CPolynomial.Roots.FiniteField.SmoothCyclicRootContext KoalaBear.Field := + CPolynomial.Roots.FiniteField.smoothCyclicRootContextOf + KoalaBear.fieldSize + KoalaBear.primitiveRoot + KoalaBear.smoothRootSchedule + (CPolynomial.BatchEvalContext.horner KoalaBear.Field) + (CPolynomial.Roots.FiniteField.smoothSplitterInput + KoalaBear.fieldSize KoalaBear.primitiveRoot KoalaBear.smoothRootSchedule) + (by + simp [KoalaBear.Field, KoalaBear.fieldSize, Nat.card_eq_fintype_card, ZMod.card]) + KoalaBear.primitiveRoot_order + KoalaBear.smoothRootSchedule_fold_eq_one + (by + intro M D p factor h + exact CPolynomial.Roots.FiniteField.smoothLinearFactorsAlgorithmWith_sound + M D (CPolynomial.BatchEvalContext.horner KoalaBear.Field) + KoalaBear.fieldSize KoalaBear.primitiveRoot KoalaBear.smoothRootSchedule h) + (by + intro M D p a _hvalid hp hroot + exact CPolynomial.Roots.FiniteField.smoothLinearFactorsAlgorithmWith_complete + M D (CPolynomial.BatchEvalContext.horner KoalaBear.Field) + KoalaBear.fieldSize KoalaBear.primitiveRoot KoalaBear.smoothRootSchedule + (by + simp [KoalaBear.Field, KoalaBear.fieldSize, Nat.card_eq_fintype_card, ZMod.card]) + KoalaBear.primitiveRoot_order + (by decide) + hp hroot) + +/-- The KoalaBear smooth splitter accepts finite-field root products. -/ +private theorem koalaBearSmoothRootProduct_valid + (M : CPolynomial.Raw.MulContext KoalaBear.Field) + (D : CPolynomial.Raw.ModContext KoalaBear.Field) + {p : CPolynomial KoalaBear.Field} (hp : p ≠ 0) : + koalaBearSmoothCyclicRootContext.validInput + (CPolynomial.Roots.FiniteField.finiteFieldRootProductWith M D + koalaBearFiniteFieldContext p) := by + simpa [koalaBearSmoothCyclicRootContext, koalaBearFiniteFieldContext] using + (CPolynomial.Roots.FiniteField.finiteFieldRootProductWith_smoothSplitterInput + M D koalaBearFiniteFieldContext KoalaBear.primitiveRoot + KoalaBear.smoothRootSchedule hp) + +/-- Complete GS-facing finite-field root backend for canonical KoalaBear. -/ +def koalaBearFieldRootContext : FieldRootContext KoalaBear.Field := + smoothFiniteFieldRootContext KoalaBear.Field + koalaBearFiniteFieldContext koalaBearSmoothCyclicRootContext + (by + intro p hp + exact koalaBearSmoothRootProduct_valid + CPolynomial.Raw.MulContext.naive CPolynomial.Raw.ModContext.naive hp) + +/-- Complete GS-facing finite-field root backend for canonical KoalaBear with NTT arithmetic. -/ +def koalaBearNttFieldRootContext : FieldRootContext KoalaBear.Field := + smoothFiniteFieldRootContextWith KoalaBear.Field + (CPolynomial.Raw.MulContext.ntt CPolynomial.NTT.KoalaBear.bestDomainForLength?) + (CPolynomial.Raw.ModContext.reversalNtt CPolynomial.NTT.KoalaBear.bestDomainForLength?) + koalaBearFiniteFieldContext koalaBearSmoothCyclicRootContext + (by + intro p hp + exact koalaBearSmoothRootProduct_valid + (CPolynomial.Raw.MulContext.ntt CPolynomial.NTT.KoalaBear.bestDomainForLength?) + (CPolynomial.Raw.ModContext.reversalNtt CPolynomial.NTT.KoalaBear.bestDomainForLength?) + hp) + +/-- Complete GS-facing finite-field root backend for canonical KoalaBear with NTTFast arithmetic. -/ +def koalaBearNttFastFieldRootContext : FieldRootContext KoalaBear.Field := + smoothFiniteFieldRootContextWith KoalaBear.Field + (CPolynomial.Raw.MulContext.nttFast CPolynomial.NTT.KoalaBear.bestDomainForLength?) + (CPolynomial.Raw.ModContext.reversalNttFast CPolynomial.NTT.KoalaBear.bestDomainForLength?) + koalaBearFiniteFieldContext koalaBearSmoothCyclicRootContext + (by + intro p hp + exact koalaBearSmoothRootProduct_valid + (CPolynomial.Raw.MulContext.nttFast CPolynomial.NTT.KoalaBear.bestDomainForLength?) + (CPolynomial.Raw.ModContext.reversalNttFast CPolynomial.NTT.KoalaBear.bestDomainForLength?) + hp) + +/-- Finite-field context for native-word fast KoalaBear. -/ +def fastKoalaBearFiniteFieldContext : + CPolynomial.Roots.FiniteField.FiniteFieldContext KoalaBear.Fast.Field where + q := KoalaBear.fieldSize + finite := by + exact Finite.of_equiv KoalaBear.Field KoalaBear.Fast.ringEquiv.toEquiv.symm + card_eq := by + have hcard : Nat.card KoalaBear.Fast.Field = Nat.card KoalaBear.Field := + Nat.card_congr KoalaBear.Fast.ringEquiv.toEquiv + rw [hcard] + simp [KoalaBear.Field, Nat.card_eq_fintype_card, ZMod.card] + frobenius_fixed := by + intro a + apply KoalaBear.Fast.toField_injective + rw [KoalaBear.Fast.toField_npow] + simpa [KoalaBear.Field, KoalaBear.fieldSize] using + ZMod.pow_card (KoalaBear.Fast.toField a) + +/-- Primitive generator transported to native-word fast KoalaBear. -/ +def fastKoalaBearPrimitiveRoot : KoalaBear.Fast.Field := + KoalaBear.Fast.ofField KoalaBear.primitiveRoot + +/-- The transported fast KoalaBear generator has full multiplicative order. -/ +lemma fastKoalaBearPrimitiveRoot_order : + orderOf fastKoalaBearPrimitiveRoot = KoalaBear.fieldSize - 1 := by + unfold fastKoalaBearPrimitiveRoot + have h := MulEquiv.orderOf_eq KoalaBear.Fast.ringEquiv.toMulEquiv + (KoalaBear.Fast.ofField KoalaBear.primitiveRoot) + rw [← h] + simpa [KoalaBear.Fast.ringEquiv_apply, KoalaBear.Fast.toField_ofField] using + KoalaBear.primitiveRoot_order + +/-- Smooth cyclic splitter context for native-word fast KoalaBear. -/ +def fastKoalaBearSmoothCyclicRootContext : + CPolynomial.Roots.FiniteField.SmoothCyclicRootContext KoalaBear.Fast.Field := + CPolynomial.Roots.FiniteField.smoothCyclicRootContextOf + KoalaBear.fieldSize + fastKoalaBearPrimitiveRoot + KoalaBear.smoothRootSchedule + (CPolynomial.BatchEvalContext.horner KoalaBear.Fast.Field) + (CPolynomial.Roots.FiniteField.smoothSplitterInput + KoalaBear.fieldSize fastKoalaBearPrimitiveRoot KoalaBear.smoothRootSchedule) + (by + have hcard : Nat.card KoalaBear.Fast.Field = Nat.card KoalaBear.Field := + Nat.card_congr KoalaBear.Fast.ringEquiv.toEquiv + rw [hcard] + simp [KoalaBear.Field, Nat.card_eq_fintype_card, ZMod.card]) + fastKoalaBearPrimitiveRoot_order + KoalaBear.smoothRootSchedule_fold_eq_one + (by + intro M D p factor h + exact CPolynomial.Roots.FiniteField.smoothLinearFactorsAlgorithmWith_sound + M D (CPolynomial.BatchEvalContext.horner KoalaBear.Fast.Field) + KoalaBear.fieldSize fastKoalaBearPrimitiveRoot KoalaBear.smoothRootSchedule h) + (by + intro M D p a _hvalid hp hroot + letI : Finite KoalaBear.Fast.Field := + Finite.of_equiv KoalaBear.Field KoalaBear.Fast.ringEquiv.toEquiv.symm + exact CPolynomial.Roots.FiniteField.smoothLinearFactorsAlgorithmWith_complete + M D (CPolynomial.BatchEvalContext.horner KoalaBear.Fast.Field) + KoalaBear.fieldSize fastKoalaBearPrimitiveRoot KoalaBear.smoothRootSchedule + (by + have hcard : Nat.card KoalaBear.Fast.Field = Nat.card KoalaBear.Field := + Nat.card_congr KoalaBear.Fast.ringEquiv.toEquiv + rw [hcard] + simp [KoalaBear.Field, Nat.card_eq_fintype_card, ZMod.card]) + fastKoalaBearPrimitiveRoot_order + (by + decide) + hp hroot) + +/-- The fast KoalaBear smooth splitter accepts finite-field root products. -/ +private theorem fastKoalaBearSmoothRootProduct_valid + (M : CPolynomial.Raw.MulContext KoalaBear.Fast.Field) + (D : CPolynomial.Raw.ModContext KoalaBear.Fast.Field) + {p : CPolynomial KoalaBear.Fast.Field} (hp : p ≠ 0) : + fastKoalaBearSmoothCyclicRootContext.validInput + (CPolynomial.Roots.FiniteField.finiteFieldRootProductWith M D + fastKoalaBearFiniteFieldContext p) := by + simpa [fastKoalaBearSmoothCyclicRootContext, fastKoalaBearFiniteFieldContext] using + (CPolynomial.Roots.FiniteField.finiteFieldRootProductWith_smoothSplitterInput + M D fastKoalaBearFiniteFieldContext fastKoalaBearPrimitiveRoot + KoalaBear.smoothRootSchedule hp) + +/-- Complete GS-facing finite-field root backend for native-word fast KoalaBear. -/ +def fastKoalaBearFieldRootContext : FieldRootContext KoalaBear.Fast.Field := + smoothFiniteFieldRootContext KoalaBear.Fast.Field + fastKoalaBearFiniteFieldContext fastKoalaBearSmoothCyclicRootContext + (by + intro p hp + exact fastKoalaBearSmoothRootProduct_valid + CPolynomial.Raw.MulContext.naive CPolynomial.Raw.ModContext.naive hp) + +/-- +Complete GS-facing finite-field root backend for native-word fast KoalaBear with +NTT arithmetic. +-/ +def fastKoalaBearNttFieldRootContext : FieldRootContext KoalaBear.Fast.Field := + smoothFiniteFieldRootContextWith KoalaBear.Fast.Field + (CPolynomial.Raw.MulContext.ntt CPolynomial.NTT.KoalaBear.fastBestDomainForLength?) + (CPolynomial.Raw.ModContext.reversalNtt CPolynomial.NTT.KoalaBear.fastBestDomainForLength?) + fastKoalaBearFiniteFieldContext fastKoalaBearSmoothCyclicRootContext + (by + intro p hp + exact fastKoalaBearSmoothRootProduct_valid + (CPolynomial.Raw.MulContext.ntt CPolynomial.NTT.KoalaBear.fastBestDomainForLength?) + (CPolynomial.Raw.ModContext.reversalNtt CPolynomial.NTT.KoalaBear.fastBestDomainForLength?) + hp) + +/-- +Complete GS-facing finite-field root backend for native-word fast KoalaBear with +NTTFast arithmetic. +-/ +def fastKoalaBearNttFastFieldRootContext : FieldRootContext KoalaBear.Fast.Field := + smoothFiniteFieldRootContextWith KoalaBear.Fast.Field + (CPolynomial.Raw.MulContext.nttFast CPolynomial.NTT.KoalaBear.fastBestDomainForLength?) + (CPolynomial.Raw.ModContext.reversalNttFast CPolynomial.NTT.KoalaBear.fastBestDomainForLength?) + fastKoalaBearFiniteFieldContext fastKoalaBearSmoothCyclicRootContext + (by + intro p hp + exact fastKoalaBearSmoothRootProduct_valid + (CPolynomial.Raw.MulContext.nttFast CPolynomial.NTT.KoalaBear.fastBestDomainForLength?) + (CPolynomial.Raw.ModContext.reversalNttFast + CPolynomial.NTT.KoalaBear.fastBestDomainForLength?) + hp) + +end GuruswamiSudan + +end CompPoly diff --git a/CompPoly/Bivariate/GuruswamiSudan/Root/RothRuckenstein/Algorithm.lean b/CompPoly/Bivariate/GuruswamiSudan/Root/RothRuckenstein/Algorithm.lean new file mode 100644 index 00000000..db5b02d4 --- /dev/null +++ b/CompPoly/Bivariate/GuruswamiSudan/Root/RothRuckenstein/Algorithm.lean @@ -0,0 +1,142 @@ +/- +Copyright (c) 2026 CompPoly Contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ + +import CompPoly.Bivariate.GuruswamiSudan.Root.Common + +/-! +# Roth-Ruckenstein-Style Root Finding + +Executable bounded-degree roots for `CBivariate F` using recursive coefficient +reconstruction and an explicit univariate field-root backend, following the +Roth-Ruckenstein root-search step [RR00]. + +## References + +* [Roth, R. M., and Ruckenstein, G., *Efficient decoding of Reed-Solomon codes + beyond half the minimum distance*][RR00] +-/ + +namespace CompPoly + +namespace GuruswamiSudan + +open CBivariate + +/-- Substitute `Y = a + X * Y` into a bivariate polynomial. -/ +def substituteYRootPlusXY {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (Q : CBivariate F) (a : F) : CBivariate F := + Id.run do + let mut out : CBivariate F := default + for y in [0:Q.val.size] do + let coeffY := Q.val.coeff y + for x in [0:coeffY.val.size] do + let coeff := coeffY.coeff x + for t in [0:y + 1] do + out := out + + CBivariate.monomialXY (x + t) t + (coeff * (Nat.choose y t : F) * a ^ (y - t)) + pure out + +/-- One residual step in the transformed Roth-Ruckenstein recursion. -/ +def transformedRothRuckensteinResidual {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (Q : CBivariate F) (a : F) : CBivariate F := + CBivariate.stripXAdicFactor (substituteYRootPlusXY Q a) + +/-- The linear coefficient of the next recursive root equation after depth zero. -/ +def nextCoefficientSlope {F : Type*} [Field F] + (Q : CBivariate F) (pref : CPolynomial F) : F := + (List.range' 1 Q.val.size).foldl + (fun acc (y : Nat) ↦ + acc + (y : F) * CBivariate.coeff Q 0 y * pref.coeff 0 ^ (y - 1)) + 0 + +/-- Polynomial equation for the next coefficient in the prefix recursion. -/ +def nextCoefficientPolynomial {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (Q : CBivariate F) (pref : CPolynomial F) (depth : Nat) : CPolynomial F := + if depth = 0 then + initialCoefficientPolynomial Q + else + CPolynomial.ofArray + #[CBivariate.composeYCoeff Q pref depth, nextCoefficientSlope Q pref] + +/-- Ordered recursive candidate extensions using a field-root backend. + +This direct coefficient-equation helper does not expand zero equations. The +residual-transform Roth-Ruckenstein backend uses residual normalization before +field-root queries. +-/ +def rootPrefixExtensionsWithFieldRootContext {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (fieldRoots : FieldRootContext F) (Q : CBivariate F) (depth : Nat) + (prefixes : Array (CPolynomial F)) : List (CPolynomial F) := + prefixes.toList.flatMap fun pref ↦ + (rootsInFieldForNonzeroEquation fieldRoots + (nextCoefficientPolynomial Q pref depth)).toList.map + fun coeff ↦ extendPrefix pref depth coeff + +/-- Candidate prefixes after choosing coefficients through depth `< k` in the +direct coefficient-equation recursion. Zero equations are not expanded. -/ +def rothRuckensteinRootPrefixes {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (fieldRoots : FieldRootContext F) (Q : CBivariate F) : Nat → Array (CPolynomial F) + | 0 => #[0] + | depth + 1 => + (rootPrefixExtensionsWithFieldRootContext fieldRoots Q depth + (rothRuckensteinRootPrefixes fieldRoots Q depth)).toArray + +/-- Residual-transform Roth-Ruckenstein prefixes with explicit recursion fuel. -/ +def transformedRothRuckensteinRootPrefixesWithFuel {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (fieldRoots : FieldRootContext F) : + Nat → CBivariate F → Nat → CPolynomial F → Array (CPolynomial F) + | 0, _Q, _depth, pref => #[pref] + | fuel + 1, Q, depth, pref => + let Qnorm := CBivariate.stripXAdicFactor Q + if Qnorm == (0 : CBivariate F) then + #[] + else + (rootsInFieldForNonzeroEquation fieldRoots + (initialCoefficientPolynomial Qnorm)).foldl + (fun out coeff ↦ + out ++ + transformedRothRuckensteinRootPrefixesWithFuel fieldRoots fuel + (transformedRothRuckensteinResidual Qnorm coeff) + (depth + 1) (extendPrefix pref depth coeff)) + #[] + +/-- Candidate prefixes from the residual-transform recursion through precision `X^k`. -/ +def transformedRothRuckensteinRootPrefixes {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (fieldRoots : FieldRootContext F) (Q : CBivariate F) (k : Nat) : + Array (CPolynomial F) := + transformedRothRuckensteinRootPrefixesWithFuel fieldRoots k Q 0 default + +/-- Residual-transform Roth-Ruckenstein bounded-degree roots. -/ +def transformedRothRuckensteinRootsYDegreeLt {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (fieldRoots : FieldRootContext F) (Q : CBivariate F) (k : Nat) : + Array (CPolynomial F) := + (transformedRothRuckensteinRootPrefixes fieldRoots Q k).filter fun p ↦ + isRootYDegreeLtBool Q k p + +/-- Roth-Ruckenstein bounded-degree roots. + +The public backend uses the residual-transform recursion, which strips common +`X`-adic factors before each field-root query. Zero univariate equations are +excluded from the field-root dependency for nonzero bivariate inputs. +-/ +def rothRuckensteinRootsYDegreeLt {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (fieldRoots : FieldRootContext F) (Q : CBivariate F) (k : Nat) : + Array (CPolynomial F) := + transformedRothRuckensteinRootsYDegreeLt fieldRoots Q k + +end GuruswamiSudan + +end CompPoly diff --git a/CompPoly/Bivariate/GuruswamiSudan/Root/RothRuckenstein/Correctness.lean b/CompPoly/Bivariate/GuruswamiSudan/Root/RothRuckenstein/Correctness.lean new file mode 100644 index 00000000..4fa8b77a --- /dev/null +++ b/CompPoly/Bivariate/GuruswamiSudan/Root/RothRuckenstein/Correctness.lean @@ -0,0 +1,913 @@ +/- +Copyright (c) 2026 CompPoly Contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ + +import CompPoly.Bivariate.GuruswamiSudan.Root.RothRuckenstein.Lemmas + +/-! +# Roth-Ruckenstein Root Correctness + +Correctness statements for the Roth-Ruckenstein root backend. +-/ + +namespace CompPoly + +namespace GuruswamiSudan + +/-- Soundness of Roth-Ruckenstein root filtering. -/ +theorem rothRuckensteinRootsYDegreeLt_sound {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + {fieldRoots : FieldRootContext F} {Q : CBivariate F} {k : Nat} + {p : CPolynomial F} + (h : p ∈ (rothRuckensteinRootsYDegreeLt fieldRoots Q k).toList) : + degreeLt p k ∧ CBivariate.composeY Q p = 0 := by + unfold rothRuckensteinRootsYDegreeLt transformedRothRuckensteinRootsYDegreeLt at h + simp [isRootYDegreeLtBool] at h + exact ⟨degreeLt_of_degreeLtBool h.2.1, composeY_of_composeYHorner_eq_zero h.2.2⟩ + +/-- Normalizing a nonzero bivariate polynomial exposes a nonzero initial +coefficient equation for the residual-transform RR step. -/ +theorem initialCoefficientPolynomial_stripXAdicFactor_ne_zero {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + {Q : CBivariate F} (hQ : Q ≠ 0) : + initialCoefficientPolynomial (CBivariate.stripXAdicFactor Q) ≠ 0 := by + intro hzero + cases horder : CBivariate.xAdicOrder? Q with + | none => + exact hQ (cbivar_xAdicOrder?_none_eq_zero horder) + | some order => + rcases cbivar_xAdicOrder?_some_exists horder with ⟨y, hy, hcoeff⟩ + have hstripCoeffNe : + CBivariate.coeff (CBivariate.divXPower Q order) 0 y ≠ 0 := by + rw [cbivar_coeff_divXPower] + simpa using hcoeff + have hyStrip : y < (CBivariate.divXPower Q order).val.size := by + by_contra hnot + exact hstripCoeffNe + (cbivar_coeff_eq_zero_of_y_size_le + (CBivariate.divXPower Q order) (i := 0) (j := y) (Nat.le_of_not_lt hnot)) + have hcoeffInitial : (initialCoefficientPolynomial Q.stripXAdicFactor).coeff y = 0 := by + rw [hzero] + exact CPolynomial.coeff_zero y + rw [initialCoefficientPolynomial_coeff_of_lt] at hcoeffInitial + · rw [CBivariate.stripXAdicFactor, horder, cbivar_coeff_divXPower] at hcoeffInitial + exact hcoeff (by simpa using hcoeffInitial) + · simpa [CBivariate.stripXAdicFactor, horder] using hyStrip + +private theorem stripXAdicFactor_ne_zero {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] + {Q : CBivariate F} (hQ : Q ≠ 0) : + CBivariate.stripXAdicFactor Q ≠ 0 := by + intro hzero + cases horder : CBivariate.xAdicOrder? Q with + | none => + exact hQ (cbivar_xAdicOrder?_none_eq_zero horder) + | some order => + rcases cbivar_xAdicOrder?_some_exists horder with ⟨y, hy, hcoeff⟩ + have hcoeffStrip : CBivariate.coeff Q.stripXAdicFactor 0 y = 0 := by + rw [hzero] + exact CPolynomial.coeff_zero 0 + rw [CBivariate.stripXAdicFactor, horder, cbivar_coeff_divXPower] at hcoeffStrip + exact hcoeff (by simpa using hcoeffStrip) + +private theorem composeY_stripXAdicFactor_eq_zero_of_composeY {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + {Q : CBivariate F} {p : CPolynomial F} + (hQ : Q ≠ 0) (hroot : CBivariate.composeY Q p = 0) : + CBivariate.composeY Q.stripXAdicFactor p = 0 := by + cases horder : CBivariate.xAdicOrder? Q with + | none => + exact (hQ (cbivar_xAdicOrder?_none_eq_zero horder)).elim + | some order => + apply (CPolynomial.toPoly_eq_zero_iff + (CBivariate.composeY Q.stripXAdicFactor p)).mp + rw [composeY_toPoly] + rw [CBivariate.stripXAdicFactor, horder] + have hrootPoly : (CBivariate.toPoly Q).eval p.toPoly = 0 := by + rw [← composeY_toPoly Q p, hroot] + exact CPolynomial.toPoly_zero + have hfactor := + cbivar_toPoly_eq_C_X_pow_mul_divXPower_of_xAdicOrder (Q := Q) horder + rw [hfactor] at hrootPoly + rw [Polynomial.eval_mul, Polynomial.eval_C] at hrootPoly + have hx : (Polynomial.X ^ order : Polynomial F) ≠ 0 := + pow_ne_zero order Polynomial.X_ne_zero + exact (mul_eq_zero.mp hrootPoly).resolve_left hx + +private theorem cbivariate_default_eq_zero {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] : + (default : CBivariate F) = 0 := rfl + +private theorem cpoly_default_eq_zero {R : Type*} [Zero R] : + (default : CPolynomial R) = 0 := rfl + +private theorem substituteYRootPlusXY_eq_fold {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (Q : CBivariate F) (a : F) : + substituteYRootPlusXY Q a = + (List.range' 0 Q.val.size).foldl + (fun out y ↦ + let coeffY := Q.val.coeff y + (List.range' 0 coeffY.val.size).foldl + (fun out x ↦ + let coeff := coeffY.coeff x + (List.range' 0 (y + 1)).foldl + (fun out t ↦ + out + + CBivariate.monomialXY (x + t) t + (coeff * (Nat.choose y t : F) * a ^ (y - t))) + out) + out) + 0 := by + simp [substituteYRootPlusXY, Std.Legacy.Range.forIn_eq_forIn_range', + cbivariate_default_eq_zero] + +private theorem substituteYRootPlusXY_coeff_fold {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (Q : CBivariate F) (a : F) (i j : Nat) : + CBivariate.coeff (substituteYRootPlusXY Q a) i j = + (List.range' 0 Q.val.size).foldl + (fun acc y ↦ + let coeffY := Q.val.coeff y + (List.range' 0 coeffY.val.size).foldl + (fun acc x ↦ + let coeff := coeffY.coeff x + (List.range' 0 (y + 1)).foldl + (fun acc t ↦ + acc + + if i = x + t ∧ j = t then + coeff * (Nat.choose y t : F) * a ^ (y - t) + else 0) + acc) + acc) + 0 := by + rw [substituteYRootPlusXY_eq_fold] + let outerStep : CBivariate F → Nat → CBivariate F := + fun out y ↦ + let coeffY := Q.val.coeff y + (List.range' 0 coeffY.val.size).foldl + (fun out x ↦ + let coeff := coeffY.coeff x + (List.range' 0 (y + 1)).foldl + (fun out t ↦ + out + + CBivariate.monomialXY (x + t) t + (coeff * (Nat.choose y t : F) * a ^ (y - t))) + out) + out + let coeffOuterStep : F → Nat → F := + fun acc y ↦ + let coeffY := Q.val.coeff y + (List.range' 0 coeffY.val.size).foldl + (fun acc x ↦ + let coeff := coeffY.coeff x + (List.range' 0 (y + 1)).foldl + (fun acc t ↦ + acc + + if i = x + t ∧ j = t then + coeff * (Nat.choose y t : F) * a ^ (y - t) + else 0) + acc) + acc + change CBivariate.coeff + (List.foldl outerStep 0 (List.range' 0 Q.val.size)) i j = + List.foldl coeffOuterStep 0 (List.range' 0 Q.val.size) + have houter : ∀ (ys : List Nat) (out : CBivariate F) (acc : F), + CBivariate.coeff out i j = acc → + CBivariate.coeff (List.foldl outerStep out ys) i j = + List.foldl coeffOuterStep acc ys := by + intro ys + induction ys with + | nil => + intro out acc hacc + simpa using hacc + | cons y ys ih => + intro out acc hacc + simp only [List.foldl_cons] + apply ih + dsimp [outerStep, coeffOuterStep] + let coeffY := Q.val.coeff y + let innerStep : CBivariate F → Nat → CBivariate F := + fun out x ↦ + let coeff := coeffY.coeff x + (List.range' 0 (y + 1)).foldl + (fun out t ↦ + out + + CBivariate.monomialXY (x + t) t + (coeff * (Nat.choose y t : F) * a ^ (y - t))) + out + let coeffInnerStep : F → Nat → F := + fun acc x ↦ + let coeff := coeffY.coeff x + (List.range' 0 (y + 1)).foldl + (fun acc t ↦ + acc + + if i = x + t ∧ j = t then + coeff * (Nat.choose y t : F) * a ^ (y - t) + else 0) + acc + change CBivariate.coeff + (List.foldl innerStep out (List.range' 0 coeffY.val.size)) i j = + List.foldl coeffInnerStep acc (List.range' 0 coeffY.val.size) + have hinner : ∀ (xs : List Nat) (out : CBivariate F) (acc : F), + CBivariate.coeff out i j = acc → + CBivariate.coeff (List.foldl innerStep out xs) i j = + List.foldl coeffInnerStep acc xs := by + intro xs + induction xs with + | nil => + intro out acc hacc + simpa using hacc + | cons x xs ihx => + intro out acc hacc + simp only [List.foldl_cons] + apply ihx + dsimp [innerStep, coeffInnerStep] + let coeff := coeffY.coeff x + let termStep : CBivariate F → Nat → CBivariate F := + fun out t ↦ + out + + CBivariate.monomialXY (x + t) t + (coeff * (Nat.choose y t : F) * a ^ (y - t)) + let coeffTermStep : F → Nat → F := + fun acc t ↦ + acc + + if i = x + t ∧ j = t then + coeff * (Nat.choose y t : F) * a ^ (y - t) + else 0 + change CBivariate.coeff + (List.foldl termStep out (List.range' 0 (y + 1))) i j = + List.foldl coeffTermStep acc (List.range' 0 (y + 1)) + have hterm : ∀ (ts : List Nat) (out : CBivariate F) (acc : F), + CBivariate.coeff out i j = acc → + CBivariate.coeff (List.foldl termStep out ts) i j = + List.foldl coeffTermStep acc ts := by + intro ts + induction ts with + | nil => + intro out acc hacc + simpa using hacc + | cons t ts iht => + intro out acc hacc + simp only [List.foldl_cons] + apply iht + dsimp [termStep, coeffTermStep] + change CBivariate.coeff + (out + CBivariate.monomialXY (x + t) t + (coeff * ↑(y.choose t) * a ^ (y - t))) i j = + acc + if i = x + t ∧ j = t then + coeff * ↑(y.choose t) * a ^ (y - t) else 0 + rw [CBivariate.coeff_add, CBivariate.coeff_monomialXY, hacc] + exact hterm (List.range' 0 (y + 1)) out acc hacc + exact hinner (List.range' 0 coeffY.val.size) out acc hacc + exact houter (List.range' 0 Q.val.size) 0 0 (CBivariate.coeff_zero i j) + +private theorem substituteYRootPlusXY_term_fold_target {F : Type*} + [Field F] (a coeff : F) (x y x₀ : Nat) (acc : F) : + (List.range' 0 (y + 1)).foldl + (fun acc t ↦ + acc + + if x + y = x₀ + t ∧ y = t then + coeff * (Nat.choose y t : F) * a ^ (y - t) + else 0) + acc = + acc + if x₀ = x then coeff else 0 := by + rw [List.foldl_congr_of_mem + (f := fun acc t ↦ + acc + + if x + y = x₀ + t ∧ y = t then + coeff * (Nat.choose y t : F) * a ^ (y - t) + else 0) + (g := fun acc t ↦ acc + if t = y then (if x₀ = x then coeff else 0) else 0) + (List.range' 0 (y + 1)) acc] + · rw [DenseMatrix.foldl_range_one_special + (F := F) (pivot := y) (if x₀ = x then coeff else 0) 0 (y + 1) acc] + have hin : 0 ≤ y ∧ y < 0 + (y + 1) := by omega + simp [hin] + · intro acc' t ht + by_cases hty : t = y + · subst t + by_cases hx : x₀ = x + · subst x₀ + simp + · have hnot : ¬(x + y = x₀ + y ∧ y = y) := by + rintro ⟨hxy, _⟩ + apply hx + omega + have hx' : x ≠ x₀ := fun h ↦ hx h.symm + simp [hx, hx'] + · have hnot : ¬(x + y = x₀ + t ∧ y = t) := by + exact fun h ↦ hty h.2.symm + simp [hnot, hty] + +private theorem substituteYRootPlusXY_term_fold_zero_of_y_lt {F : Type*} + [Field F] (a coeff : F) (x y y₀ x₀ : Nat) (acc : F) (hy₀ : y₀ < y) : + (List.range' 0 (y₀ + 1)).foldl + (fun acc t ↦ + acc + + if x + y = x₀ + t ∧ y = t then + coeff * (Nat.choose y₀ t : F) * a ^ (y₀ - t) + else 0) + acc = + acc := by + rw [List.foldl_congr_of_mem + (f := fun acc t ↦ + acc + + if x + y = x₀ + t ∧ y = t then + coeff * (Nat.choose y₀ t : F) * a ^ (y₀ - t) + else 0) + (g := fun acc _t ↦ acc) + (List.range' 0 (y₀ + 1)) acc] + · induction (List.range' 0 (y₀ + 1)) generalizing acc with + | nil => rfl + | cons t ts ih => + simp only [List.foldl_cons] + exact ih acc + · intro acc' t ht + have ht_le : t ≤ y₀ := by + have ht' := (List.mem_range'_1.mp ht).2 + omega + have hty : y ≠ t := by omega + have hnot : ¬(x + y = x₀ + t ∧ y = t) := fun h ↦ hty h.2 + simp [hnot] + +private theorem substituteYRootPlusXY_inner_fold_target {F : Type*} + [Field F] (a : F) (coeffY : CPolynomial F) (x y : Nat) (acc : F) + (hsize : coeffY.val.size = x + 1) : + (List.range' 0 coeffY.val.size).foldl + (fun acc x₀ ↦ + let coeff := coeffY.coeff x₀ + (List.range' 0 (y + 1)).foldl + (fun acc t ↦ + acc + + if x + y = x₀ + t ∧ y = t then + coeff * (Nat.choose y t : F) * a ^ (y - t) + else 0) + acc) + acc = + acc + coeffY.coeff x := by + rw [List.foldl_congr_of_mem + (f := fun acc x₀ ↦ + let coeff := coeffY.coeff x₀ + (List.range' 0 (y + 1)).foldl + (fun acc t ↦ + acc + + if x + y = x₀ + t ∧ y = t then + coeff * (Nat.choose y t : F) * a ^ (y - t) + else 0) + acc) + (g := fun acc x₀ ↦ acc + if x₀ = x then coeffY.coeff x else 0) + (List.range' 0 coeffY.val.size) acc] + · rw [hsize] + rw [DenseMatrix.foldl_range_one_special + (F := F) (pivot := x) (coeffY.coeff x) 0 (x + 1) acc] + have hin : 0 ≤ x ∧ x < 0 + (x + 1) := by omega + simp [hin] + · intro acc' x₀ _hx₀ + rw [substituteYRootPlusXY_term_fold_target] + by_cases hx : x₀ = x <;> simp [hx] + +private theorem substituteYRootPlusXY_inner_fold_zero_of_y_lt {F : Type*} + [Field F] (a : F) (coeffY : CPolynomial F) (x y y₀ : Nat) (acc : F) + (hy₀ : y₀ < y) : + (List.range' 0 coeffY.val.size).foldl + (fun acc x₀ ↦ + let coeff := coeffY.coeff x₀ + (List.range' 0 (y₀ + 1)).foldl + (fun acc t ↦ + acc + + if x + y = x₀ + t ∧ y = t then + coeff * (Nat.choose y₀ t : F) * a ^ (y₀ - t) + else 0) + acc) + acc = + acc := by + rw [List.foldl_congr_of_mem + (f := fun acc x₀ ↦ + let coeff := coeffY.coeff x₀ + (List.range' 0 (y₀ + 1)).foldl + (fun acc t ↦ + acc + + if x + y = x₀ + t ∧ y = t then + coeff * (Nat.choose y₀ t : F) * a ^ (y₀ - t) + else 0) + acc) + (g := fun acc _x₀ ↦ acc) + (List.range' 0 coeffY.val.size) acc] + · induction (List.range' 0 coeffY.val.size) generalizing acc with + | nil => rfl + | cons x₀ xs ih => + simp only [List.foldl_cons] + exact ih acc + · intro acc' x₀ _hx₀ + exact substituteYRootPlusXY_term_fold_zero_of_y_lt a (coeffY.coeff x₀) x y y₀ x₀ acc' hy₀ + +private theorem substituteYRootPlusXY_coeff_top {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (Q : CBivariate F) (a : F) (y x : Nat) + (hQsize : Q.val.size = y + 1) (hRowSize : (Q.val.coeff y).val.size = x + 1) : + CBivariate.coeff (substituteYRootPlusXY Q a) (x + y) y = (Q.val.coeff y).coeff x := by + rw [substituteYRootPlusXY_coeff_fold] + rw [hQsize] + rw [List.foldl_congr_of_mem + (f := fun acc y₀ ↦ + let coeffY := Q.val.coeff y₀ + (List.range' 0 coeffY.val.size).foldl + (fun acc x₀ ↦ + let coeff := coeffY.coeff x₀ + (List.range' 0 (y₀ + 1)).foldl + (fun acc t ↦ + acc + + if x + y = x₀ + t ∧ y = t then + coeff * (Nat.choose y₀ t : F) * a ^ (y₀ - t) + else 0) + acc) + acc) + (g := fun acc y₀ ↦ acc + if y₀ = y then (Q.val.coeff y).coeff x else 0) + (List.range' 0 (y + 1)) 0] + · rw [DenseMatrix.foldl_range_one_special + (F := F) (pivot := y) ((Q.val.coeff y).coeff x) 0 (y + 1) 0] + have hin : 0 ≤ y ∧ y < 0 + (y + 1) := by omega + simp [hin] + · intro acc y₀ hy₀mem + have hy₀lt : y₀ < y + 1 := by + have hy₀lt' := (List.mem_range'_1.mp hy₀mem).2 + omega + by_cases hy₀ : y₀ = y + · subst y₀ + rw [substituteYRootPlusXY_inner_fold_target] + · simp + · exact hRowSize + · have hy₀_lt_y : y₀ < y := by omega + rw [substituteYRootPlusXY_inner_fold_zero_of_y_lt] + · simp [hy₀] + · exact hy₀_lt_y + +private theorem substituteYRootPlusXY_ne_zero {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + {Q : CBivariate F} (a : F) (hQ : Q ≠ 0) : + substituteYRootPlusXY Q a ≠ 0 := by + intro hzero + let y := Q.natDegree + let coeffY := Q.val.coeff y + have hQsize : Q.val.size = y + 1 := cpoly_size_eq_natDegree_succ_of_ne_zero hQ + have hcoeffY_ne : coeffY ≠ 0 := by + dsimp [coeffY, y] + exact cpoly_coeff_natDegree_ne_zero_of_ne_zero hQ + let x := coeffY.natDegree + have hRowSize : coeffY.val.size = x + 1 := + cpoly_size_eq_natDegree_succ_of_ne_zero hcoeffY_ne + have hcoeff_ne : coeffY.coeff x ≠ 0 := + cpoly_coeff_natDegree_ne_zero_of_ne_zero hcoeffY_ne + have htop : + CBivariate.coeff (substituteYRootPlusXY Q a) (x + y) y = coeffY.coeff x := by + simpa [coeffY] using substituteYRootPlusXY_coeff_top Q a y x hQsize hRowSize + have hzeroCoeff : CBivariate.coeff (substituteYRootPlusXY Q a) (x + y) y = 0 := by + rw [hzero] + exact CBivariate.coeff_zero (x + y) y + rw [htop] at hzeroCoeff + exact hcoeff_ne hzeroCoeff + +private theorem list_sum_map_mul_right {R : Type*} [Semiring R] + (xs : List Nat) (f : Nat → R) (q : R) : + (xs.map (fun x ↦ f x * q)).sum = (xs.map f).sum * q := by + induction xs with + | nil => + simp + | cons x xs ih => + simp [ih, add_mul] + +private theorem polynomialPrefix_eq_range_fold {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (p : CPolynomial F) : + ∀ n : Nat, + polynomialPrefix p n = + (List.range n).foldl + (fun acc i ↦ acc + CPolynomial.monomial i (p.coeff i)) 0 + | 0 => by + rw [polynomialPrefix_zero] + rfl + | n + 1 => by + rw [polynomialPrefix_succ, polynomialPrefix_eq_range_fold p n] + unfold extendPrefix + rw [List.range_succ, List.foldl_append] + simp + +private theorem cpoly_eq_range_fold {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (p : CPolynomial F) : + p = + (List.range' 0 p.val.size).foldl + (fun acc i ↦ acc + CPolynomial.monomial i (p.coeff i)) 0 := by + have hprefix := polynomialPrefix_eq_range_fold p p.val.size + have hdegree : degreeLt p p.val.size := by + unfold degreeLt CPolynomial.degree + cases hsize : p.val.size with + | zero => + simp + | succ n => + exact WithBot.coe_lt_coe.mpr (Nat.lt_succ_self n) + have hpref : polynomialPrefix p p.val.size = p := + polynomialPrefix_eq_self_of_degreeLt hdegree + rw [hpref] at hprefix + rw [List.range_eq_range'] at hprefix + exact hprefix + +private theorem cpoly_range_fold_monomial_mul_pow {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (coeffY q : CPolynomial F) (y : Nat) (acc : CPolynomial F) : + (List.range' 0 coeffY.val.size).foldl + (fun acc x ↦ acc + CPolynomial.monomial x (coeffY.coeff x) * q ^ y) + acc = + acc + coeffY * q ^ y := by + rw [list_foldl_add_eq_sum] + rw [list_sum_map_mul_right] + have hrow := cpoly_eq_range_fold coeffY + rw [list_foldl_add_eq_sum + (fun x ↦ CPolynomial.monomial x (coeffY.coeff x)) + (List.range' 0 coeffY.val.size) 0] at hrow + simp only [zero_add] at hrow + rw [← hrow] + +private theorem composeY_monomialXY {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (p : CPolynomial F) (x y : Nat) (c : F) : + CBivariate.composeY (CBivariate.monomialXY x y c) p = + CPolynomial.monomial x c * p ^ y := by + apply (CPolynomial.ringEquiv (R := F)).injective + rw [show CPolynomial.ringEquiv (CBivariate.composeY (CBivariate.monomialXY x y c) p) = + (CBivariate.composeY (CBivariate.monomialXY x y c) p).toPoly by rfl] + rw [show CPolynomial.ringEquiv (CPolynomial.monomial x c * p ^ y) = + (CPolynomial.monomial x c * p ^ y).toPoly by rfl] + rw [composeY_toPoly, CBivariate.monomialXY_toPoly, CPolynomial.toPoly_mul, + CPolynomial.toPoly_pow, Polynomial.eval_monomial] + rw [show (CPolynomial.monomial x c : CPolynomial F).toPoly = + Polynomial.monomial x c from + CPolynomial.monomial_toPoly (R := F) x c] + +private theorem composeY_foldl_add {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (f : Nat → CBivariate F) (p : CPolynomial F) : + ∀ (xs : List Nat) (out : CBivariate F) (acc : CPolynomial F), + CBivariate.composeY out p = acc → + CBivariate.composeY (xs.foldl (fun out x ↦ out + f x) out) p = + List.foldl (fun acc x ↦ acc + CBivariate.composeY (f x) p) acc xs := by + intro xs + induction xs with + | nil => + intro out acc hacc + simpa using hacc + | cons x xs ih => + intro out acc hacc + simp only [List.foldl_cons] + apply ih + rw [composeY_add, hacc] + +private theorem composeY_substituteYRootPlusXY_term_fold {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (a coeff : F) (p : CPolynomial F) (x y : Nat) (out : CBivariate F) : + CBivariate.composeY + ((List.range' 0 (y + 1)).foldl + (fun out t ↦ + out + + CBivariate.monomialXY (x + t) t + (coeff * (Nat.choose y t : F) * a ^ (y - t))) + out) + p = + CBivariate.composeY out p + + CPolynomial.monomial x coeff * (CPolynomial.C a + CPolynomial.X * p) ^ y := by + rw [composeY_foldl_add + (fun t ↦ + CBivariate.monomialXY (x + t) t + (coeff * (Nat.choose y t : F) * a ^ (y - t))) + p (List.range' 0 (y + 1)) out (CBivariate.composeY out p) rfl] + rw [List.foldl_congr_of_mem + (f := fun acc t ↦ + acc + CBivariate.composeY + (CBivariate.monomialXY (x + t) t + (coeff * (Nat.choose y t : F) * a ^ (y - t))) p) + (g := fun acc t ↦ + acc + CPolynomial.monomial (x + t) + (coeff * (Nat.choose y t : F) * a ^ (y - t)) * p ^ t) + (List.range' 0 (y + 1)) (CBivariate.composeY out p)] + · rw [list_foldl_add_eq_sum + (fun t ↦ + CPolynomial.monomial (x + t) + (coeff * (Nat.choose y t : F) * a ^ (y - t)) * p ^ t)] + have hsum := cpoly_monomial_substitution_sum a coeff p x y + rw [list_foldl_add_eq_sum + (fun t ↦ + CPolynomial.monomial (x + t) + (coeff * (Nat.choose y t : F) * a ^ (y - t)) * p ^ t) + (List.range' 0 (y + 1)) 0] at hsum + simp only [zero_add] at hsum + rw [hsum] + · intro acc t _ht + rw [composeY_monomialXY] + +private theorem composeY_substituteYRootPlusXY_inner_fold {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (a : F) (p : CPolynomial F) (coeffY : CPolynomial F) (y : Nat) + (out : CBivariate F) : + CBivariate.composeY + ((List.range' 0 coeffY.val.size).foldl + (fun out x ↦ + let coeff := coeffY.coeff x + (List.range' 0 (y + 1)).foldl + (fun out t ↦ + out + + CBivariate.monomialXY (x + t) t + (coeff * (Nat.choose y t : F) * a ^ (y - t))) + out) + out) + p = + CBivariate.composeY out p + + coeffY * (CPolynomial.C a + CPolynomial.X * p) ^ y := by + let q := CPolynomial.C a + CPolynomial.X * p + let innerStep : CBivariate F → Nat → CBivariate F := + fun out x ↦ + let coeff := coeffY.coeff x + (List.range' 0 (y + 1)).foldl + (fun out t ↦ + out + + CBivariate.monomialXY (x + t) t + (coeff * (Nat.choose y t : F) * a ^ (y - t))) + out + let coeffStep : CPolynomial F → Nat → CPolynomial F := + fun acc x ↦ acc + CPolynomial.monomial x (coeffY.coeff x) * q ^ y + change CBivariate.composeY + (List.foldl innerStep out (List.range' 0 coeffY.val.size)) p = + CBivariate.composeY out p + coeffY * q ^ y + have hfold : ∀ (xs : List Nat) (out : CBivariate F) (acc : CPolynomial F), + CBivariate.composeY out p = acc → + CBivariate.composeY (List.foldl innerStep out xs) p = + List.foldl coeffStep acc xs := by + intro xs + induction xs with + | nil => + intro out acc hacc + simpa using hacc + | cons x xs ih => + intro out acc hacc + simp only [List.foldl_cons] + apply ih + dsimp [innerStep, coeffStep, q] + rw [composeY_substituteYRootPlusXY_term_fold, hacc] + rw [hfold (List.range' 0 coeffY.val.size) out (CBivariate.composeY out p) rfl] + exact cpoly_range_fold_monomial_mul_pow coeffY q y (CBivariate.composeY out p) + +private theorem composeY_substituteYRootPlusXY_eq {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (Q : CBivariate F) (a : F) (p : CPolynomial F) : + CBivariate.composeY (substituteYRootPlusXY Q a) p = + CBivariate.composeY Q (CPolynomial.C a + CPolynomial.X * p) := by + rw [substituteYRootPlusXY_eq_fold] + let q := CPolynomial.C a + CPolynomial.X * p + let outerStep : CBivariate F → Nat → CBivariate F := + fun out y ↦ + let coeffY := Q.val.coeff y + (List.range' 0 coeffY.val.size).foldl + (fun out x ↦ + let coeff := coeffY.coeff x + (List.range' 0 (y + 1)).foldl + (fun out t ↦ + out + + CBivariate.monomialXY (x + t) t + (coeff * (Nat.choose y t : F) * a ^ (y - t))) + out) + out + let coeffStep : CPolynomial F → Nat → CPolynomial F := + fun acc y ↦ acc + Q.val.coeff y * q ^ y + change CBivariate.composeY + (List.foldl outerStep 0 (List.range' 0 Q.val.size)) p = + CBivariate.composeY Q q + have hfold : ∀ (ys : List Nat) (out : CBivariate F) (acc : CPolynomial F), + CBivariate.composeY out p = acc → + CBivariate.composeY (List.foldl outerStep out ys) p = + List.foldl coeffStep acc ys := by + intro ys + induction ys with + | nil => + intro out acc hacc + simpa using hacc + | cons y ys ih => + intro out acc hacc + simp only [List.foldl_cons] + apply ih + dsimp [outerStep, coeffStep, q] + rw [composeY_substituteYRootPlusXY_inner_fold, hacc] + rw [hfold (List.range' 0 Q.val.size) 0 0 (composeY_zero p)] + rw [← composeY_eq_range_fold Q q] + +private theorem transformedRothRuckensteinResidual_ne_zero {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + {Q : CBivariate F} (a : F) (hQ : Q ≠ 0) : + transformedRothRuckensteinResidual Q a ≠ 0 := by + unfold transformedRothRuckensteinResidual + exact stripXAdicFactor_ne_zero (substituteYRootPlusXY_ne_zero a hQ) + +private theorem composeY_transformedRothRuckensteinResidual_dropXPower_eq_zero {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + {Q : CBivariate F} {p : CPolynomial F} {a : F} + (hQ : Q ≠ 0) + (hroot : CBivariate.composeY Q (CPolynomial.C a + CPolynomial.X * p) = 0) : + CBivariate.composeY (transformedRothRuckensteinResidual Q a) p = 0 := by + unfold transformedRothRuckensteinResidual + apply composeY_stripXAdicFactor_eq_zero_of_composeY + · exact substituteYRootPlusXY_ne_zero a hQ + · rw [composeY_substituteYRootPlusXY_eq] + exact hroot + +private theorem degreeLt_dropXPower_succ_of_degreeLt {F : Type*} + [Zero F] {p : CPolynomial F} {depth fuel : Nat} + (hdegree : degreeLt (CPolynomial.dropXPower p depth) (fuel + 1)) : + degreeLt (CPolynomial.dropXPower p (depth + 1)) fuel := by + apply degreeLt_of_degreeLtBool + rw [degreeLtBool] + have hsizeBool := degreeLtBool_of_degreeLt hdegree + rw [degreeLtBool] at hsizeBool + simp at hsizeBool + let suffix := CPolynomial.dropXPower p depth + have hsuffixSize : suffix.val.size ≤ fuel + 1 := by + dsimp [suffix] + exact hsizeBool + have htail : + CPolynomial.dropXPower p (depth + 1) = CPolynomial.divX suffix := by + dsimp [suffix] + rw [← cpoly_dropXPower_add p depth 1] + rfl + rw [htail] + cases hs : suffix.val.size with + | zero => + unfold CPolynomial.divX + simp [suffix, hs] + | succ n => + have hpos : suffix.val.size > 0 := by simp [hs] + have hlt := CPolynomial.divX_size_lt (p := suffix) hpos + rw [decide_eq_true_eq] + omega + +private theorem polynomialPrefix_eq_self_of_dropXPower_degreeLt_zero {F : Type*} + [Zero F] [BEq F] [LawfulBEq F] + {p : CPolynomial F} {depth : Nat} + (hdegree : degreeLt (CPolynomial.dropXPower p depth) 0) : + polynomialPrefix p depth = p := by + rw [CPolynomial.eq_iff_coeff] + intro i + unfold polynomialPrefix + rw [cpoly_truncate_coeff] + by_cases hi : i < depth + · simp [hi] + · have hsizeBool := degreeLtBool_of_degreeLt hdegree + rw [degreeLtBool] at hsizeBool + simp at hsizeBool + have hsizeZero : (CPolynomial.dropXPower p depth).val.size = 0 := by + rw [hsizeBool] + rfl + have hsuffixCoeff : + (CPolynomial.dropXPower p depth).coeff (i - depth) = 0 := + cpoly_coeff_eq_zero_of_size_le _ (by rw [hsizeZero]; omega) + rw [cpoly_coeff_dropXPower] at hsuffixCoeff + have hidx : i - depth + depth = i := by omega + rw [hidx] at hsuffixCoeff + simp [hi, hsuffixCoeff] + +private theorem transformedRothRuckensteinRootPrefixesWithFuel_complete_aux {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (fieldRoots : FieldRootContext F) : + ∀ (fuel : Nat) (Q : CBivariate F) (depth : Nat) (pref p : CPolynomial F), + Q ≠ 0 → + pref = polynomialPrefix p depth → + degreeLt (CPolynomial.dropXPower p depth) fuel → + CBivariate.composeY Q (CPolynomial.dropXPower p depth) = 0 → + p ∈ (transformedRothRuckensteinRootPrefixesWithFuel fieldRoots fuel Q depth pref).toList := by + intro fuel + induction fuel with + | zero => + intro Q depth pref p hQ hpref hdegree hroot + simp [transformedRothRuckensteinRootPrefixesWithFuel] + have hpEq : polynomialPrefix p depth = p := + polynomialPrefix_eq_self_of_dropXPower_degreeLt_zero hdegree + rw [hpref, hpEq] + | succ fuel ih => + intro Q depth pref p hQ hpref hdegree hroot + simp [transformedRothRuckensteinRootPrefixesWithFuel] + let Qnorm := CBivariate.stripXAdicFactor Q + have hQnorm : Qnorm ≠ 0 := stripXAdicFactor_ne_zero hQ + have hrootNorm : CBivariate.composeY Qnorm (CPolynomial.dropXPower p depth) = 0 := by + dsimp [Qnorm] + exact composeY_stripXAdicFactor_eq_zero_of_composeY hQ hroot + have hcoeff0 : + (CPolynomial.dropXPower p depth).coeff 0 = p.coeff depth := by + rw [cpoly_coeff_dropXPower] + rw [show 0 + depth = depth by omega] + have hinitRoot : + CPolynomial.eval (p.coeff depth) (initialCoefficientPolynomial Qnorm) = 0 := by + rw [← hcoeff0] + rw [initialCoefficientPolynomial_eval_eq_composeY_coeff_zero] + rw [hrootNorm] + exact CPolynomial.coeff_zero 0 + have hcoeffMem : + p.coeff depth ∈ + (rootsInFieldForNonzeroEquation fieldRoots + (initialCoefficientPolynomial Qnorm)).toList := + rootsInFieldForNonzeroEquation_complete fieldRoots + (initialCoefficientPolynomial_stripXAdicFactor_ne_zero hQ) hinitRoot + have hprefNext : + extendPrefix pref depth (p.coeff depth) = + polynomialPrefix p (depth + 1) := by + rw [hpref, polynomialPrefix_succ] + have hdropRoot : + CBivariate.composeY + (transformedRothRuckensteinResidual Qnorm (p.coeff depth)) + (CPolynomial.dropXPower p (depth + 1)) = 0 := by + have hdropEq : + CPolynomial.dropXPower p depth = + CPolynomial.C (p.coeff depth) + + CPolynomial.X * CPolynomial.dropXPower p (depth + 1) := + dropXPower_eq_C_add_X_mul_dropXPower_succ p depth + apply composeY_transformedRothRuckensteinResidual_dropXPower_eq_zero + (Q := Qnorm) (a := p.coeff depth) + (p := CPolynomial.dropXPower p (depth + 1)) hQnorm + rw [← hdropEq] + exact hrootNorm + have hdegreeTail : + degreeLt (CPolynomial.dropXPower p (depth + 1)) fuel := + degreeLt_dropXPower_succ_of_degreeLt hdegree + have hrec : + p ∈ + (transformedRothRuckensteinRootPrefixesWithFuel fieldRoots fuel + (transformedRothRuckensteinResidual Qnorm (p.coeff depth)) + (depth + 1) (extendPrefix pref depth (p.coeff depth))).toList := + ih (transformedRothRuckensteinResidual Qnorm (p.coeff depth)) + (depth + 1) (extendPrefix pref depth (p.coeff depth)) p + (transformedRothRuckensteinResidual_ne_zero (p.coeff depth) hQnorm) + hprefNext hdegreeTail hdropRoot + refine ⟨?_, ?_⟩ + · simpa [Qnorm] using hQnorm + · simpa [Qnorm] using + (Array.mem_flatten_map_of_mem + (rootsInFieldForNonzeroEquation fieldRoots + (initialCoefficientPolynomial Qnorm)) + (fun coeff ↦ + transformedRothRuckensteinRootPrefixesWithFuel fieldRoots fuel + (transformedRothRuckensteinResidual Qnorm coeff) + (depth + 1) (extendPrefix pref depth coeff)) + hcoeffMem hrec) + +/-- Completeness of Roth-Ruckenstein root finding from a complete field-root backend. + +The nonzero-input hypothesis matches the backend completeness contract, which +only promises finite output for nonzero bivariate equations. +-/ +theorem rothRuckensteinRootsYDegreeLt_complete {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + {fieldRoots : FieldRootContext F} + {Q : CBivariate F} {k : Nat} {p : CPolynomial F} + (hQ : Q ≠ 0) (hdegree : degreeLt p k) (hroot : CBivariate.composeY Q p = 0) : + p ∈ (rothRuckensteinRootsYDegreeLt fieldRoots Q k).toList := by + unfold rothRuckensteinRootsYDegreeLt transformedRothRuckensteinRootsYDegreeLt + transformedRothRuckensteinRootPrefixes + have hprefix : + p ∈ (transformedRothRuckensteinRootPrefixesWithFuel fieldRoots k Q 0 default).toList := by + exact transformedRothRuckensteinRootPrefixesWithFuel_complete_aux + fieldRoots k Q 0 default p hQ + (by rw [polynomialPrefix_zero, cpoly_default_eq_zero]) + (by simpa [CPolynomial.dropXPower] using hdegree) + (by simpa [CPolynomial.dropXPower] using hroot) + simp [hprefix, isRootYDegreeLtBool_of_root hdegree hroot] + +/-- Roth-Ruckenstein roots packaged with an explicit univariate field-root backend. -/ +def rothRuckensteinRootContext (F : Type*) + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (fieldRoots : FieldRootContext F) : GSRootContext F where + rootsYDegreeLt := rothRuckensteinRootsYDegreeLt fieldRoots + sound := by + intro Q k p h + exact rothRuckensteinRootsYDegreeLt_sound h + complete := by + intro Q k p hQ hdegree hroot + exact rothRuckensteinRootsYDegreeLt_complete + (fieldRoots := fieldRoots) hQ hdegree hroot + +/-- Residual-transform Roth-Ruckenstein roots packaged as a backend. -/ +def transformedRothRuckensteinRootContext (F : Type*) + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (fieldRoots : FieldRootContext F) : GSRootContext F where + rootsYDegreeLt := transformedRothRuckensteinRootsYDegreeLt fieldRoots + sound := by + intro Q k p h + unfold transformedRothRuckensteinRootsYDegreeLt at h + simp [isRootYDegreeLtBool] at h + exact ⟨degreeLt_of_degreeLtBool h.2.1, composeY_of_composeYHorner_eq_zero h.2.2⟩ + complete := by + intro Q k p hQ hdegree hroot + simpa [rothRuckensteinRootsYDegreeLt] using + (rothRuckensteinRootsYDegreeLt_complete + (fieldRoots := fieldRoots) hQ hdegree hroot) + +end GuruswamiSudan + +end CompPoly diff --git a/CompPoly/Bivariate/GuruswamiSudan/Root/RothRuckenstein/Lemmas.lean b/CompPoly/Bivariate/GuruswamiSudan/Root/RothRuckenstein/Lemmas.lean new file mode 100644 index 00000000..1333bfb6 --- /dev/null +++ b/CompPoly/Bivariate/GuruswamiSudan/Root/RothRuckenstein/Lemmas.lean @@ -0,0 +1,640 @@ +/- +Copyright (c) 2026 CompPoly Contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ + +import CompPoly.Bivariate.GuruswamiSudan.Root.RothRuckenstein.Algorithm +import CompPoly.Bivariate.GuruswamiSudan.Root.Common.Lemmas +import CompPoly.Bivariate.GuruswamiSudan.PolynomialCorrectness +import CompPoly.Data.Array.Lemmas + +/-! +# Roth-Ruckenstein Correctness Support + +Coefficient, composition, and root-filter lemmas used by the Roth-Ruckenstein +correctness proofs. +-/ + +namespace CompPoly + +namespace GuruswamiSudan + +theorem cpoly_val_coeff_ofArray {R : Type*} [Zero R] [BEq R] [LawfulBEq R] + (coeffs : Array R) (i : Nat) : + (CPolynomial.ofArray coeffs).val.coeff i = coeffs.getD i 0 := by + unfold CPolynomial.ofArray + exact CPolynomial.Raw.Trim.coeff_eq_coeff coeffs i + +theorem cpoly_coeff_eq_zero_of_size_le {R : Type*} [Zero R] + (p : CPolynomial R) {i : Nat} (h : p.val.size ≤ i) : p.coeff i = 0 := by + unfold CPolynomial.coeff CPolynomial.Raw.coeff + rw [Array.getD_eq_getD_getElem?] + simp [Array.getElem?_eq_none h] + +theorem cpoly_size_eq_natDegree_succ_of_ne_zero {R : Type*} [Zero R] + {p : CPolynomial R} (hp : p ≠ 0) : + p.val.size = p.natDegree + 1 := by + unfold CPolynomial.natDegree + cases hs : p.val.size with + | zero => + have hval : p.val = (#[] : CPolynomial.Raw R) := Array.eq_empty_of_size_eq_zero hs + apply (hp ?_).elim + apply CPolynomial.ext + simpa using hval + | succ n => + simp + +theorem cpoly_coeff_natDegree_ne_zero_of_ne_zero {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] + {p : CPolynomial R} (hp : p ≠ 0) : + p.coeff p.natDegree ≠ 0 := by + have htoPoly : p.toPoly ≠ 0 := (CPolynomial.toPoly_eq_zero_iff p).not.mpr hp + have hlead : p.toPoly.leadingCoeff ≠ 0 := Polynomial.leadingCoeff_ne_zero.mpr htoPoly + rw [← CPolynomial.leadingCoeff_toPoly p, CPolynomial.leadingCoeff_eq_coeff_natDegree] at hlead + exact hlead + +theorem cpoly_coeff_eq_zero_of_natDegree_lt {R : Type*} [Zero R] + (p : CPolynomial R) {i : Nat} (hi : p.natDegree < i) : + p.coeff i = 0 := by + by_cases hp : p = 0 + · rw [hp] + exact CPolynomial.coeff_zero i + · have hsize := cpoly_size_eq_natDegree_succ_of_ne_zero hp + exact cpoly_coeff_eq_zero_of_size_le p (by omega) + +theorem cpoly_coeff_dropXPower {R : Type*} [Zero R] + (p : CPolynomial R) (n i : Nat) : + (CPolynomial.dropXPower p n).coeff i = p.coeff (i + n) := by + induction n generalizing p i with + | zero => + simp [CPolynomial.dropXPower] + | succ n ih => + rw [CPolynomial.dropXPower, ih, CPolynomial.coeff_divX] + have h : i + n + 1 = i + (n + 1) := by omega + rw [h] + +theorem cpoly_toPoly_eq_X_pow_mul_dropXPower_of_coeff_eq_zero_lt {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] (p : CPolynomial R) (n : Nat) + (hzero : ∀ i, i < n → p.coeff i = 0) : + p.toPoly = Polynomial.X ^ n * (CPolynomial.dropXPower p n).toPoly := by + ext i + rw [← CPolynomial.coeff_toPoly (p := p) (i := i)] + rw [Polynomial.coeff_X_pow_mul'] + by_cases hn : n ≤ i + · rw [if_pos hn] + rw [← CPolynomial.coeff_toPoly (p := CPolynomial.dropXPower p n) (i := i - n)] + rw [cpoly_coeff_dropXPower] + congr 1 + omega + · rw [if_neg hn] + exact hzero i (Nat.lt_of_not_ge hn) + +theorem cbivar_coeff_divXPower {R : Type*} [Zero R] [BEq R] [LawfulBEq R] + (Q : CBivariate R) (n i j : Nat) : + CBivariate.coeff (CBivariate.divXPower Q n) i j = CBivariate.coeff Q (i + n) j := by + unfold CBivariate.coeff CBivariate.divXPower + rw [cpoly_val_coeff_ofArray] + by_cases hj : j < Q.val.size + · rw [Array.getD_eq_getD_getElem?, Array.getElem?_map, Array.getElem?_eq_getElem hj] + have hqcoeff : Q.val.coeff j = Q.val[j] := CPolynomial.Raw.Trim.coeff_eq_getElem hj + change (CPolynomial.dropXPower Q.val[j] n).coeff i = (Q.val.coeff j).coeff (i + n) + rw [hqcoeff] + exact cpoly_coeff_dropXPower Q.val[j] n i + · have hjle : Q.val.size ≤ j := Nat.le_of_not_lt hj + have hmaple : (Q.val.map fun coeff ↦ CPolynomial.dropXPower coeff n).size ≤ j := by + simpa using hjle + rw [Array.getD_eq_getD_getElem?, Array.getElem?_eq_none hmaple] + have hqcoeff : Q.val.coeff j = (0 : CPolynomial R) := by + unfold CPolynomial.Raw.coeff + rw [Array.getD_eq_getD_getElem?, Array.getElem?_eq_none hjle] + rfl + rw [hqcoeff] + change (0 : CPolynomial R).coeff i = (0 : CPolynomial R).coeff (i + n) + rw [CPolynomial.coeff_zero, CPolynomial.coeff_zero] + +theorem cbivar_coeffY_divXPower {R : Type*} [Zero R] [BEq R] [LawfulBEq R] + (Q : CBivariate R) (n j : Nat) : + (CBivariate.divXPower Q n).val.coeff j = CPolynomial.dropXPower (Q.val.coeff j) n := by + rw [CPolynomial.eq_iff_coeff] + intro i + change CBivariate.coeff (CBivariate.divXPower Q n) i j = + (CPolynomial.dropXPower (Q.val.coeff j) n).coeff i + rw [cbivar_coeff_divXPower, cpoly_coeff_dropXPower] + rfl + +theorem cbivar_coeff_eq_zero_of_y_size_le {R : Type*} [Zero R] + (Q : CBivariate R) {i j : Nat} (hj : Q.val.size ≤ j) : + CBivariate.coeff Q i j = 0 := by + unfold CBivariate.coeff + have hqcoeff : Q.val.coeff j = (0 : CPolynomial R) := by + unfold CPolynomial.Raw.coeff + rw [Array.getD_eq_getD_getElem?, Array.getElem?_eq_none hj] + rfl + rw [hqcoeff] + exact CPolynomial.coeff_zero i + +theorem initialCoefficientPolynomial_coeff_fold {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [DecidableEq F] + (Q : CBivariate F) (j : Nat) : + ∀ (ys : List Nat) (out : CPolynomial F), + ys.Nodup → + (List.foldl + (fun out y ↦ out + CPolynomial.monomial y (CBivariate.coeff Q 0 y)) + out ys).coeff j = + out.coeff j + if j ∈ ys then CBivariate.coeff Q 0 j else 0 := by + intro ys + induction ys with + | nil => + intro out _hys + simp + | cons y ys ih => + intro out hys + have hynot : y ∉ ys := (List.nodup_cons.mp hys).1 + have hysNodup : ys.Nodup := (List.nodup_cons.mp hys).2 + simp only [List.foldl_cons] + rw [ih (out + CPolynomial.monomial y (CBivariate.coeff Q 0 y)) hysNodup] + rw [CPolynomial.coeff_add, CPolynomial.coeff_monomial] + by_cases hjy : j = y + · subst y + simp [hynot] + · have hmonomial : + (if j = y then CBivariate.coeff Q 0 y else 0) = 0 := by + simp [hjy] + rw [hmonomial] + by_cases hjmem : j ∈ ys <;> simp [hjy, hjmem] + +theorem initialCoefficientPolynomial_coeff_of_lt {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (Q : CBivariate F) {j : Nat} (hj : j < Q.val.size) : + (initialCoefficientPolynomial Q).coeff j = CBivariate.coeff Q 0 j := by + unfold initialCoefficientPolynomial + rw [initialCoefficientPolynomial_coeff_fold Q j (List.range Q.val.size) + (0 : CPolynomial F) (List.nodup_range (n := Q.val.size))] + rw [CPolynomial.coeff_zero] + simp [hj] + +theorem cpoly_xAdicOrder?_some_coeff_ne {R : Type*} [Zero R] [BEq R] [LawfulBEq R] + {p : CPolynomial R} {i : Nat} (h : CPolynomial.xAdicOrder? p = some i) : + p.coeff i ≠ 0 := by + unfold CPolynomial.xAdicOrder? at h + have hp := List.find?_some h + simpa using hp + +theorem cpoly_xAdicOrder?_some_coeff_eq_zero_of_lt {R : Type*} + [Zero R] [BEq R] [LawfulBEq R] + {p : CPolynomial R} {order i : Nat} + (h : CPolynomial.xAdicOrder? p = some order) (hi : i < order) : + p.coeff i = 0 := by + unfold CPolynomial.xAdicOrder? at h + rcases (List.find?_eq_some_iff_getElem.mp h) with + ⟨_horder, idx, hidx, hget, hbefore⟩ + have hidx_eq : idx = order := by + simpa [List.getElem_range'_1] using hget + have hiidx : i < idx := by omega + have hbeq : (p.coeff i == 0) = true := by + simpa [List.getElem_range'_1] using hbefore i hiidx + exact beq_iff_eq.mp hbeq + +theorem cpoly_xAdicOrder?_none_eq_zero {R : Type*} [Zero R] [BEq R] [LawfulBEq R] + {p : CPolynomial R} (h : CPolynomial.xAdicOrder? p = none) : p = 0 := by + rw [CPolynomial.eq_zero_iff_coeff_zero] + intro i + by_cases hi : i < p.val.size + · unfold CPolynomial.xAdicOrder? at h + have hall := (List.find?_eq_none).mp h i + have hmem : i ∈ List.range' 0 p.val.size := by + simpa [List.mem_range'] using Nat.succ_le_of_lt hi + have hnot := hall hmem + by_cases hcoeff : p.coeff i = 0 + · exact hcoeff + · have hbeqFalse : (p.coeff i == 0) = false := by + apply Bool.eq_false_iff.mpr + intro htrue + exact hcoeff (by simpa using htrue) + have hb : (!(p.coeff i == 0)) = true := by + rw [hbeqFalse] + rfl + exact (hnot hb).elim + · exact cpoly_coeff_eq_zero_of_size_le p (Nat.le_of_not_lt hi) + +def xAdicStep {R : Type*} [Zero R] [BEq R] + (Q : CBivariate R) (best : Option Nat) (y : Nat) : Option Nat := + match CPolynomial.xAdicOrder? (Q.val.coeff y) with + | none => best + | some order => + match best with + | none => some order + | some current => some (min current order) + +def xAdicWitness {R : Type*} [Zero R] (Q : CBivariate R) + (best : Option Nat) : Prop := + ∀ order, best = some order → ∃ y, y < Q.val.size ∧ CBivariate.coeff Q order y ≠ 0 + +theorem cbivar_xAdicOrder?_step_witness {R : Type*} + [Zero R] [BEq R] [LawfulBEq R] + (Q : CBivariate R) (best : Option Nat) {y : Nat} + (hw : xAdicWitness Q best) (hy : y < Q.val.size) : + xAdicWitness Q (xAdicStep Q best y) := by + intro result hresult + unfold xAdicStep at hresult + cases best with + | none => + cases horder : CPolynomial.xAdicOrder? (Q.val.coeff y) with + | none => + rw [horder] at hresult + simp at hresult + | some order => + rw [horder] at hresult + have hres : result = order := by simpa using hresult.symm + subst result + have hcoeff : CBivariate.coeff Q order y ≠ 0 := by + unfold CBivariate.coeff + exact cpoly_xAdicOrder?_some_coeff_ne horder + exact ⟨y, hy, hcoeff⟩ + | some current => + cases horder : CPolynomial.xAdicOrder? (Q.val.coeff y) with + | none => + rw [horder] at hresult + have hres : result = current := by simpa using hresult.symm + subst result + exact hw current rfl + | some order => + rw [horder] at hresult + have hres : result = min current order := by simpa using hresult.symm + subst result + have hcoeff : CBivariate.coeff Q order y ≠ 0 := by + unfold CBivariate.coeff + exact cpoly_xAdicOrder?_some_coeff_ne horder + by_cases hle : current ≤ order + · have hmin : min current order = current := Nat.min_eq_left hle + rcases hw current rfl with ⟨w, hwlt, hwcoeff⟩ + exact ⟨w, hwlt, by simpa [hmin] using hwcoeff⟩ + · have hmin : min current order = order := + Nat.min_eq_right (Nat.le_of_not_ge hle) + exact ⟨y, hy, by simpa [hmin] using hcoeff⟩ + +theorem cbivar_xAdicOrder?_fold_witness {R : Type*} + [Zero R] [BEq R] [LawfulBEq R] + (Q : CBivariate R) : + ∀ (ys : List Nat) (best : Option Nat), + xAdicWitness Q best → + (∀ y, y ∈ ys → y < Q.val.size) → + xAdicWitness Q (List.foldl (xAdicStep Q) best ys) := by + intro ys + induction ys with + | nil => + intro best hw _hys + exact hw + | cons y ys ih => + intro best hw hys + simp only [List.foldl_cons] + apply ih + · exact cbivar_xAdicOrder?_step_witness Q best hw (hys y (by simp)) + · intro z hz + exact hys z (by simp [hz]) + +theorem cbivar_xAdicOrder?_some_exists {R : Type*} + [Zero R] [BEq R] [LawfulBEq R] + {Q : CBivariate R} {order : Nat} (h : CBivariate.xAdicOrder? Q = some order) : + ∃ y, y < Q.val.size ∧ CBivariate.coeff Q order y ≠ 0 := by + unfold CBivariate.xAdicOrder? at h + change List.foldl (xAdicStep Q) none (List.range' 0 Q.val.size) = some order at h + have hw := cbivar_xAdicOrder?_fold_witness Q (List.range' 0 Q.val.size) none + (by + intro order hnone + simp at hnone) + (by + intro y hy + simpa using (List.mem_range'_1.mp hy).2) + exact hw order h + +theorem cbivar_xAdicOrder?_step_some_le_best {R : Type*} + [Zero R] [BEq R] (Q : CBivariate R) {best : Option Nat} {current result y : Nat} + (hbest : best = some current) (hstep : xAdicStep Q best y = some result) : + result ≤ current := by + subst best + unfold xAdicStep at hstep + cases hrow : CPolynomial.xAdicOrder? (Q.val.coeff y) with + | none => + rw [hrow] at hstep + have hresult : result = current := by simpa using hstep.symm + omega + | some rowOrder => + rw [hrow] at hstep + have hresult : result = min current rowOrder := by simpa using hstep.symm + subst result + exact Nat.min_le_left current rowOrder + +theorem cbivar_xAdicOrder?_step_some_le_row {R : Type*} + [Zero R] [BEq R] (Q : CBivariate R) {best : Option Nat} {rowOrder result y : Nat} + (hrow : CPolynomial.xAdicOrder? (Q.val.coeff y) = some rowOrder) + (hstep : xAdicStep Q best y = some result) : + result ≤ rowOrder := by + unfold xAdicStep at hstep + rw [hrow] at hstep + cases best with + | none => + have hresult : result = rowOrder := by simpa using hstep.symm + omega + | some current => + have hresult : result = min current rowOrder := by simpa using hstep.symm + subst result + exact Nat.min_le_right current rowOrder + +theorem cbivar_xAdicOrder?_fold_some_le_best {R : Type*} + [Zero R] [BEq R] (Q : CBivariate R) : + ∀ (ys : List Nat) (best : Option Nat) (current result : Nat), + best = some current → + List.foldl (xAdicStep Q) best ys = some result → + result ≤ current := by + intro ys + induction ys with + | nil => + intro best current result hbest hfold + rw [hbest] at hfold + have hresult : result = current := by simpa using hfold.symm + omega + | cons y ys ih => + intro best current result hbest hfold + simp only [List.foldl_cons] at hfold + cases hstep : xAdicStep Q best y with + | none => + have hnone : False := by + subst best + unfold xAdicStep at hstep + cases hrow : CPolynomial.xAdicOrder? (Q.val.coeff y) <;> rw [hrow] at hstep <;> + simp at hstep + exact hnone.elim + | some next => + rw [hstep] at hfold + have hresult_le_next := ih (some next) next result rfl hfold + have hnext_le_current := + cbivar_xAdicOrder?_step_some_le_best Q hbest hstep + exact Nat.le_trans hresult_le_next hnext_le_current + +theorem cbivar_xAdicOrder?_fold_some_le_row {R : Type*} + [Zero R] [BEq R] (Q : CBivariate R) : + ∀ (ys : List Nat) (best : Option Nat) (result y rowOrder : Nat), + List.foldl (xAdicStep Q) best ys = some result → + y ∈ ys → + CPolynomial.xAdicOrder? (Q.val.coeff y) = some rowOrder → + result ≤ rowOrder := by + intro ys + induction ys with + | nil => + intro best result y rowOrder _hfold hmem _hrow + simp at hmem + | cons z zs ih => + intro best result y rowOrder hfold hmem hrow + simp only [List.foldl_cons] at hfold + simp at hmem + cases hstep : xAdicStep Q best z with + | none => + rw [hstep] at hfold + cases hmem with + | inl hyz => + subst y + unfold xAdicStep at hstep + rw [hrow] at hstep + cases best <;> simp at hstep + | inr hyzs => + exact ih none result y rowOrder hfold hyzs hrow + | some next => + rw [hstep] at hfold + cases hmem with + | inl hyz => + subst y + have hresult_le_next := + cbivar_xAdicOrder?_fold_some_le_best Q zs (some next) next result rfl hfold + have hnext_le_row := + cbivar_xAdicOrder?_step_some_le_row Q hrow hstep + exact Nat.le_trans hresult_le_next hnext_le_row + | inr hyzs => + exact ih (some next) result y rowOrder hfold hyzs hrow + +theorem cbivar_xAdicOrder?_some_coeff_eq_zero_of_lt {R : Type*} + [Zero R] [BEq R] [LawfulBEq R] + {Q : CBivariate R} {order i y : Nat} + (h : CBivariate.xAdicOrder? Q = some order) (hi : i < order) : + CBivariate.coeff Q i y = 0 := by + by_cases hy : y < Q.val.size + · cases hrow : CPolynomial.xAdicOrder? (Q.val.coeff y) with + | none => + unfold CBivariate.coeff + rw [cpoly_xAdicOrder?_none_eq_zero hrow] + exact CPolynomial.coeff_zero i + | some rowOrder => + have hle : order ≤ rowOrder := by + unfold CBivariate.xAdicOrder? at h + change List.foldl (xAdicStep Q) none (List.range' 0 Q.val.size) = some order at h + have hmem : y ∈ List.range' 0 Q.val.size := by + simpa [List.mem_range'] using Nat.succ_le_of_lt hy + exact cbivar_xAdicOrder?_fold_some_le_row Q + (List.range' 0 Q.val.size) none order y rowOrder h hmem hrow + unfold CBivariate.coeff + exact cpoly_xAdicOrder?_some_coeff_eq_zero_of_lt hrow (Nat.lt_of_lt_of_le hi hle) + · exact cbivar_coeff_eq_zero_of_y_size_le Q (Nat.le_of_not_lt hy) + +theorem cbivar_toPoly_eq_C_X_pow_mul_divXPower_of_xAdicOrder {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + {Q : CBivariate F} {order : Nat} (horder : CBivariate.xAdicOrder? Q = some order) : + CBivariate.toPoly Q = + Polynomial.C (Polynomial.X ^ order : Polynomial F) * + CBivariate.toPoly (CBivariate.divXPower Q order) := by + ext j n + rw [CBivariate.coeff_toPoly_Y] + rw [Polynomial.coeff_C_mul] + rw [CBivariate.coeff_toPoly_Y] + rw [cbivar_coeffY_divXPower] + exact congrArg (fun P : Polynomial F ↦ P.coeff n) + (cpoly_toPoly_eq_X_pow_mul_dropXPower_of_coeff_eq_zero_lt + (Q.val.coeff j) order (fun i hi ↦ + cbivar_xAdicOrder?_some_coeff_eq_zero_of_lt (Q := Q) (y := j) horder hi)) + +theorem cbivar_xAdicOrder?_fold_none {R : Type*} + [Zero R] [BEq R] (Q : CBivariate R) : + ∀ (ys : List Nat) (best : Option Nat), + List.foldl (xAdicStep Q) best ys = none → + best = none ∧ ∀ y, y ∈ ys → CPolynomial.xAdicOrder? (Q.val.coeff y) = none := by + intro ys + induction ys with + | nil => + intro best h + exact ⟨h, by simp⟩ + | cons y ys ih => + intro best h + simp only [List.foldl_cons] at h + have htail := ih (xAdicStep Q best y) h + have hstep : xAdicStep Q best y = none := htail.1 + unfold xAdicStep at hstep + cases hrow : CPolynomial.xAdicOrder? (Q.val.coeff y) with + | some order => + rw [hrow] at hstep + cases best <;> simp at hstep + | none => + rw [hrow] at hstep + have hbest : best = none := by simpa using hstep + constructor + · exact hbest + · intro z hz + simp at hz + cases hz with + | inl hzy => + subst z + exact hrow + | inr hzTail => + exact htail.2 z hzTail + +theorem cbivar_xAdicOrder?_none_eq_zero {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] + {Q : CBivariate F} (h : CBivariate.xAdicOrder? Q = none) : Q = 0 := by + apply (CPolynomial.eq_zero_iff_coeff_zero (p := (Q : CPolynomial (CPolynomial F)))).mpr + intro y + by_cases hy : y < Q.val.size + · unfold CBivariate.xAdicOrder? at h + change List.foldl (xAdicStep Q) none (List.range' 0 Q.val.size) = none at h + have hfold := cbivar_xAdicOrder?_fold_none Q (List.range' 0 Q.val.size) none h + have hmem : y ∈ List.range' 0 Q.val.size := by + simpa [List.mem_range'] using Nat.succ_le_of_lt hy + exact cpoly_xAdicOrder?_none_eq_zero (hfold.2 y hmem) + · have hyle : Q.val.size ≤ y := Nat.le_of_not_lt hy + unfold CPolynomial.coeff CPolynomial.Raw.coeff + rw [Array.getD_eq_getD_getElem?, Array.getElem?_eq_none hyle] + rfl + +theorem cpoly_dropXPower_add {R : Type*} [Zero R] + (p : CPolynomial R) (m n : Nat) : + CPolynomial.dropXPower (CPolynomial.dropXPower p m) n = + CPolynomial.dropXPower p (m + n) := by + induction m generalizing p with + | zero => + simp [CPolynomial.dropXPower] + | succ m ih => + rw [CPolynomial.dropXPower] + rw [ih] + rw [show m + 1 + n = (m + n) + 1 by omega] + simp [CPolynomial.dropXPower] + +theorem dropXPower_eq_C_add_X_mul_dropXPower_succ {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] + (p : CPolynomial F) (depth : Nat) : + CPolynomial.dropXPower p depth = + CPolynomial.C (p.coeff depth) + + CPolynomial.X * CPolynomial.dropXPower p (depth + 1) := by + let suffix := CPolynomial.dropXPower p depth + have hcoeff : suffix.coeff 0 = p.coeff depth := by + dsimp [suffix] + rw [cpoly_coeff_dropXPower] + rw [show 0 + depth = depth by omega] + have hdrop : CPolynomial.dropXPower p (depth + 1) = CPolynomial.divX suffix := by + dsimp [suffix] + rw [← cpoly_dropXPower_add p depth 1] + rfl + calc + CPolynomial.dropXPower p depth = suffix := rfl + _ = CPolynomial.divX suffix * CPolynomial.X + CPolynomial.C (suffix.coeff 0) := by + rw [CPolynomial.divX_mul_X_add] + _ = CPolynomial.C (p.coeff depth) + + CPolynomial.X * CPolynomial.dropXPower p (depth + 1) := by + rw [hcoeff, hdrop] + ring + +theorem polynomial_monomial_substitution_term {F : Type*} [Field F] + (a coeff : F) (p : Polynomial F) (x y t : Nat) : + Polynomial.monomial x coeff * + ((Polynomial.X * p) ^ t * Polynomial.C a ^ (y - t) * + Polynomial.C (Nat.choose y t : F)) = + Polynomial.monomial (x + t) (coeff * (Nat.choose y t : F) * a ^ (y - t)) * + p ^ t := by + rw [← Polynomial.C_mul_X_pow_eq_monomial (a := coeff) (n := x)] + rw [← Polynomial.C_mul_X_pow_eq_monomial + (a := coeff * (Nat.choose y t : F) * a ^ (y - t)) (n := x + t)] + simp only [mul_pow, Polynomial.C_mul, Polynomial.C_pow] + rw [show Polynomial.X ^ (x + t) = Polynomial.X ^ x * Polynomial.X ^ t by rw [pow_add]] + ring_nf + +theorem polynomial_monomial_substitution_sum {F : Type*} [Field F] + (a coeff : F) (p : Polynomial F) (x y : Nat) : + (∑ t ∈ Finset.range (y + 1), + Polynomial.monomial (x + t) (coeff * (Nat.choose y t : F) * a ^ (y - t)) * + p ^ t) = + Polynomial.monomial x coeff * (Polynomial.C a + Polynomial.X * p) ^ y := by + rw [show Polynomial.C a + Polynomial.X * p = Polynomial.X * p + Polynomial.C a by ring] + rw [add_pow] + rw [Finset.mul_sum] + refine Finset.sum_congr rfl ?_ + intro t ht + rw [← polynomial_monomial_substitution_term (a := a) (coeff := coeff) + (p := p) (x := x) (y := y) (t := t)] + rw [← Polynomial.C_eq_natCast (R := F) (Nat.choose y t)] + +theorem foldl_cpoly_toPoly_add {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] + (f : Nat → CPolynomial F) : + ∀ (xs : List Nat) (acc : CPolynomial F) (accPoly : Polynomial F), + acc.toPoly = accPoly → + (xs.foldl (fun acc x ↦ acc + f x) acc).toPoly = + xs.foldl (fun acc x ↦ acc + (f x).toPoly) accPoly := by + intro xs + induction xs with + | nil => + intro acc accPoly hacc + simpa using hacc + | cons x xs ih => + intro acc accPoly hacc + simp only [List.foldl_cons] + apply ih + rw [CPolynomial.toPoly_add, hacc] + +theorem cpoly_monomial_substitution_sum {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (a coeff : F) (p : CPolynomial F) (x y : Nat) : + (List.range' 0 (y + 1)).foldl + (fun acc t ↦ + acc + CPolynomial.monomial (x + t) + (coeff * (Nat.choose y t : F) * a ^ (y - t)) * p ^ t) + 0 = + CPolynomial.monomial x coeff * (CPolynomial.C a + CPolynomial.X * p) ^ y := by + apply (CPolynomial.ringEquiv (R := F)).injective + change + ((List.range' 0 (y + 1)).foldl + (fun acc t ↦ + acc + CPolynomial.monomial (x + t) + (coeff * (Nat.choose y t : F) * a ^ (y - t)) * p ^ t) + 0).toPoly = + (CPolynomial.monomial x coeff * (CPolynomial.C a + CPolynomial.X * p) ^ y).toPoly + rw [foldl_cpoly_toPoly_add + (fun t ↦ + CPolynomial.monomial (x + t) (coeff * (Nat.choose y t : F) * a ^ (y - t)) * + p ^ t) + (List.range' 0 (y + 1)) 0 0 (CPolynomial.toPoly_zero (R := F))] + rw [← List.range_eq_range'] + rw [list_foldl_add_eq_sum] + rw [list_sum_map_range_eq_finset_sum] + simp only [CPolynomial.toPoly_mul, CPolynomial.toPoly_pow, CPolynomial.toPoly_add, + CPolynomial.C_toPoly, CPolynomial.X_toPoly, zero_add] + calc + ∑ x_1 ∈ Finset.range (y + 1), + (CPolynomial.monomial (x + x_1) + (coeff * (Nat.choose y x_1 : F) * a ^ (y - x_1))).toPoly * p.toPoly ^ x_1 + = ∑ x_1 ∈ Finset.range (y + 1), + (Polynomial.monomial (x + x_1) + (coeff * (Nat.choose y x_1 : F) * a ^ (y - x_1)) : Polynomial F) * + p.toPoly ^ x_1 := by + apply Finset.sum_congr rfl + intro i _hi + exact congrArg (fun q : Polynomial F ↦ q * p.toPoly ^ i) + (CPolynomial.monomial_toPoly (R := F) (x + i) + (coeff * (Nat.choose y i : F) * a ^ (y - i))) + _ = Polynomial.monomial x coeff * (Polynomial.C a + Polynomial.X * p.toPoly) ^ y := by + rw [polynomial_monomial_substitution_sum] + _ = (CPolynomial.monomial x coeff).toPoly * + (Polynomial.C a + Polynomial.X * p.toPoly) ^ y := by + rw [show (CPolynomial.monomial x coeff : CPolynomial F).toPoly = + Polynomial.monomial x coeff from + CPolynomial.monomial_toPoly (R := F) x coeff] + +end GuruswamiSudan + +end CompPoly diff --git a/CompPoly/Bivariate/GuruswamiSudan/Root/ShiftedSubstitution.lean b/CompPoly/Bivariate/GuruswamiSudan/Root/ShiftedSubstitution.lean new file mode 100644 index 00000000..f344982c --- /dev/null +++ b/CompPoly/Bivariate/GuruswamiSudan/Root/ShiftedSubstitution.lean @@ -0,0 +1,67 @@ +/- +Copyright (c) 2026 CompPoly Contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ + +import CompPoly.Bivariate.GuruswamiSudan.Polynomial + +/-! +# Shifted Substitution for Guruswami-Sudan Root Search + +Executable substitution of `Y = f(X) + X^t Y` in bivariate polynomials. +-/ + +namespace CompPoly + +namespace GuruswamiSudan + +/-- Multiply a univariate polynomial by `X^t`. -/ +def shiftPolynomialByXPower {F : Type*} + [Semiring F] [BEq F] [LawfulBEq F] [Nontrivial F] + (p : CPolynomial F) (t : Nat) : CPolynomial F := + CPolynomial.X ^ t * p + +/-- The contribution of one `Y^y` coefficient to `Y^r` after substituting +`Y = f(X) + X^t Y`. -/ +def shiftedSubstitutionCoeffTerm {F : Type*} + [Semiring F] [BEq F] [LawfulBEq F] [Nontrivial F] + (coeffY f : CPolynomial F) (t y r : Nat) : CPolynomial F := + coeffY * CPolynomial.C (Nat.choose y r : F) * f ^ (y - r) * + CPolynomial.X ^ (t * r) + +/-- Substitute `Y = f(X) + X^t Y` into a bivariate polynomial. -/ +def substituteYPolynomialPlusXPowerY {F : Type*} + [Semiring F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (Q : CBivariate F) (f : CPolynomial F) (t : Nat) : CBivariate F := + (List.range' 0 Q.val.size).foldl + (fun (out : CBivariate F) (y : Nat) ↦ + let coeffY := Q.val.coeff y + out + (List.range (y + 1)).foldl + (fun (out : CBivariate F) (r : Nat) ↦ + let contribution : CBivariate F := CPolynomial.monomial r + (shiftedSubstitutionCoeffTerm coeffY f t y r) + out + contribution) + 0) + (0 : CBivariate F) + +/-- Truncated substitution `Y = f(X) + X^t Y`, keeping only `X`-degree `< N` +after each accumulated `Y`-coefficient contribution. -/ +def substituteYPolynomialPlusXPowerYTruncated {F : Type*} + [Semiring F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (Q : CBivariate F) (f : CPolynomial F) (t N : Nat) : CBivariate F := + (List.range' 0 Q.val.size).foldl + (fun (out : CBivariate F) (y : Nat) ↦ + let coeffY := Q.val.coeff y + (List.range (y + 1)).foldl + (fun (out : CBivariate F) (r : Nat) ↦ + let term := CPolynomial.truncate + (shiftedSubstitutionCoeffTerm coeffY f t y r) N + let contribution : CBivariate F := CPolynomial.monomial r term + CBivariate.truncateX (out + contribution) N) + out) + (0 : CBivariate F) + +end GuruswamiSudan + +end CompPoly diff --git a/CompPoly/Bivariate/GuruswamiSudan/Root/ShiftedSubstitution/Lemmas.lean b/CompPoly/Bivariate/GuruswamiSudan/Root/ShiftedSubstitution/Lemmas.lean new file mode 100644 index 00000000..75ddf04c --- /dev/null +++ b/CompPoly/Bivariate/GuruswamiSudan/Root/ShiftedSubstitution/Lemmas.lean @@ -0,0 +1,123 @@ +/- +Copyright (c) 2026 CompPoly Contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ + +import CompPoly.Bivariate.GuruswamiSudan.Root.Common.Lemmas +import CompPoly.Bivariate.GuruswamiSudan.Root.ShiftedSubstitution + +/-! +# Shifted Substitution Lemmas + +Semantic proof surface for the generic shifted substitution. The executable +operation is available independently of these heavier algebraic facts. +-/ + +namespace CompPoly + +namespace GuruswamiSudan + +/-- The univariate coefficient sum produced by one `Y`-coefficient in shifted +substitution composes to the corresponding binomial expansion. -/ +theorem shiftedSubstitutionCoeffTerm_sum {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (coeffY f g : CPolynomial F) (t y : Nat) : + (List.range (y + 1)).foldl + (fun acc r ↦ acc + shiftedSubstitutionCoeffTerm coeffY f t y r * g ^ r) + 0 = + coeffY * (f + CPolynomial.X ^ t * g) ^ y := by + rw [list_foldl_add_eq_sum] + rw [list_sum_map_range_eq_finset_sum] + simp only [zero_add] + rw [show f + CPolynomial.X ^ t * g = CPolynomial.X ^ t * g + f by ring] + rw [add_pow] + rw [Finset.mul_sum] + apply Finset.sum_congr rfl + intro r _hr + unfold shiftedSubstitutionCoeffTerm + rw [mul_pow, pow_mul] + rw [CPolynomial.natCast_eq_C] + ring + +/-- Composing the bivariate contribution from one `Y`-coefficient gives the +corresponding univariate shifted-substitution term. -/ +theorem composeY_shiftedSubstitutionCoeffFold {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (coeffY f g : CPolynomial F) (t y : Nat) : + CBivariate.composeY + ((List.range (y + 1)).foldl + (fun (out : CBivariate F) r ↦ + let contribution : CBivariate F := CPolynomial.monomial r + (shiftedSubstitutionCoeffTerm coeffY f t y r) + out + contribution) + 0) + g = + (List.range (y + 1)).foldl + (fun acc r ↦ acc + shiftedSubstitutionCoeffTerm coeffY f t y r * g ^ r) + 0 := by + let contribution : Nat → CBivariate F := fun r ↦ + CPolynomial.monomial r (shiftedSubstitutionCoeffTerm coeffY f t y r) + let term : Nat → CPolynomial F := fun r ↦ + shiftedSubstitutionCoeffTerm coeffY f t y r * g ^ r + have hfold : ∀ (rs : List Nat) (out : CBivariate F) (acc : CPolynomial F), + CBivariate.composeY out g = acc → + CBivariate.composeY (rs.foldl (fun out r ↦ out + contribution r) out) g = + rs.foldl (fun acc r ↦ acc + term r) acc := by + intro rs + induction rs with + | nil => + intro out acc hacc + simpa using hacc + | cons r rs ih => + intro out acc hacc + simp only [List.foldl_cons] + apply ih + dsimp [contribution, term] + rw [composeY_add, hacc, composeY_outer_monomial] + exact hfold (List.range (y + 1)) 0 0 (composeY_zero g) + +/-- Semantic correctness of the shifted substitution `Y = f(X) + X^t Y`. -/ +theorem composeY_substituteYPolynomialPlusXPowerY {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (Q : CBivariate F) (f g : CPolynomial F) (t : Nat) : + CBivariate.composeY (substituteYPolynomialPlusXPowerY Q f t) g = + CBivariate.composeY Q (f + CPolynomial.X ^ t * g) := by + let q := f + CPolynomial.X ^ t * g + let contribution : Nat → CBivariate F := fun y ↦ + let coeffY := Q.val.coeff y + (List.range (y + 1)).foldl + (fun (out : CBivariate F) r ↦ + let term : CBivariate F := CPolynomial.monomial r + (shiftedSubstitutionCoeffTerm coeffY f t y r) + out + term) + 0 + let coeffStep : CPolynomial F → Nat → CPolynomial F := + fun acc y ↦ acc + Q.val.coeff y * q ^ y + have hfold : ∀ (ys : List Nat) (out : CBivariate F) (acc : CPolynomial F), + CBivariate.composeY out g = acc → + CBivariate.composeY (ys.foldl (fun out y ↦ out + contribution y) out) g = + ys.foldl coeffStep acc := by + intro ys + induction ys with + | nil => + intro out acc hacc + simpa using hacc + | cons y ys ih => + intro out acc hacc + simp only [List.foldl_cons] + apply ih + dsimp [contribution, coeffStep, q] + rw [composeY_add, hacc] + rw [composeY_shiftedSubstitutionCoeffFold] + rw [shiftedSubstitutionCoeffTerm_sum] + unfold substituteYPolynomialPlusXPowerY + change CBivariate.composeY + ((List.range' 0 Q.val.size).foldl (fun out y ↦ out + contribution y) 0) g = + CBivariate.composeY Q q + rw [hfold (List.range' 0 Q.val.size) 0 0 (composeY_zero g)] + rw [← composeY_eq_range_fold Q q] + +end GuruswamiSudan + +end CompPoly diff --git a/CompPoly/Bivariate/GuruswamiSudan/Util.lean b/CompPoly/Bivariate/GuruswamiSudan/Util.lean new file mode 100644 index 00000000..5094901e --- /dev/null +++ b/CompPoly/Bivariate/GuruswamiSudan/Util.lean @@ -0,0 +1,24 @@ +/- +Copyright (c) 2026 CompPoly Contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ + +/-! +# Guruswami-Sudan Shared Utilities + +Small helpers shared by the interpolation and root-finding implementation +modules. +-/ + +namespace CompPoly + +namespace GuruswamiSudan + +/-- Count nonzero entries in a dense coefficient vector. -/ +def nonzeroEntryCount {F : Type _} [Zero F] [BEq F] (v : Array F) : Nat := + v.foldl (fun count x ↦ if x == 0 then count else count + 1) 0 + +end GuruswamiSudan + +end CompPoly diff --git a/tests/CompPolyTests.lean b/tests/CompPolyTests.lean index c8cb52dc..11c47e51 100644 --- a/tests/CompPolyTests.lean +++ b/tests/CompPolyTests.lean @@ -8,6 +8,10 @@ import CompPolyTests.Bivariate.Basic import CompPolyTests.Bivariate.Degree import CompPolyTests.Bivariate.Factor import CompPolyTests.Bivariate.Deriv +import CompPolyTests.Bivariate.GuruswamiSudan.Compose +import CompPolyTests.Bivariate.GuruswamiSudan.Hasse +import CompPolyTests.Bivariate.GuruswamiSudan.Root.FieldRoots.KoalaBear +import CompPolyTests.Bivariate.GuruswamiSudan.Root.RothRuckenstein import CompPolyTests.Bivariate.Multiplicity import CompPolyTests.Bivariate.WeightedDegree import CompPolyTests.Data.MvPolynomial.Notation diff --git a/tests/CompPolyTests/Bivariate/GuruswamiSudan/Compose.lean b/tests/CompPolyTests/Bivariate/GuruswamiSudan/Compose.lean new file mode 100644 index 00000000..c26be194 --- /dev/null +++ b/tests/CompPolyTests/Bivariate/GuruswamiSudan/Compose.lean @@ -0,0 +1,37 @@ +/- +Copyright (c) 2026 CompPoly Contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ + +import CompPoly.Bivariate.GuruswamiSudan.Compose +import Mathlib.Algebra.Field.ZMod + +/-! +# Guruswami-Sudan Composition Tests + +Regression coverage for `CBivariate.composeY`. +-/ + +namespace CompPolyTests + +open CompPoly + +namespace GuruswamiSudan.Compose + +abbrev F3 := ZMod 3 + +instance : Fact (Nat.Prime 3) := + ⟨by decide⟩ + +private def q : CBivariate F3 := + CBivariate.Y + CBivariate.monomialXY 1 0 2 + +private def pX : CPolynomial F3 := + CPolynomial.monomial 1 1 + +#guard CBivariate.composeY q pX == 0 + +end GuruswamiSudan.Compose + +end CompPolyTests diff --git a/tests/CompPolyTests/Bivariate/GuruswamiSudan/Hasse.lean b/tests/CompPolyTests/Bivariate/GuruswamiSudan/Hasse.lean new file mode 100644 index 00000000..0f126160 --- /dev/null +++ b/tests/CompPolyTests/Bivariate/GuruswamiSudan/Hasse.lean @@ -0,0 +1,35 @@ +/- +Copyright (c) 2026 CompPoly Contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ + +import CompPoly.Bivariate.GuruswamiSudan.Hasse +import Mathlib.Algebra.Field.ZMod + +/-! +# Guruswami-Sudan Hasse Tests + +Regression coverage for executable Hasse derivative helpers. +-/ + +namespace CompPolyTests + +open CompPoly + +namespace GuruswamiSudan.Hasse + +abbrev F3 := ZMod 3 + +instance : Fact (Nat.Prime 3) := + ⟨by decide⟩ + +private def q : CBivariate F3 := + CBivariate.monomialXY 2 1 1 + +#guard CBivariate.coeff (CBivariate.hasseDerivative 1 0 q) 1 1 == (2 : F3) +#guard CBivariate.hasseDerivativeEval 1 0 2 1 q == (1 : F3) + +end GuruswamiSudan.Hasse + +end CompPolyTests diff --git a/tests/CompPolyTests/Bivariate/GuruswamiSudan/Root/FieldRoots/KoalaBear.lean b/tests/CompPolyTests/Bivariate/GuruswamiSudan/Root/FieldRoots/KoalaBear.lean new file mode 100644 index 00000000..9aaf0d2a --- /dev/null +++ b/tests/CompPolyTests/Bivariate/GuruswamiSudan/Root/FieldRoots/KoalaBear.lean @@ -0,0 +1,113 @@ +/- +Copyright (c) 2026 CompPoly Contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ + +import CompPoly.Bivariate.GuruswamiSudan.Root.FieldRoots.KoalaBear + +/-! +# KoalaBear GS Field-Root Tests + +Executable coverage for canonical and fast KoalaBear finite-field root backends. +-/ + +namespace CompPolyTests + +open CompPoly +open CompPoly.GuruswamiSudan + +namespace GuruswamiSudan.Root.FieldRoots.KoalaBear + +private def koalaPoly : CPolynomial _root_.KoalaBear.Field := + CPolynomial.linearFactor (3 : _root_.KoalaBear.Field) * + CPolynomial.linearFactor (5 : _root_.KoalaBear.Field) + +private def koalaRoots : Array _root_.KoalaBear.Field := + koalaBearFieldRootContext.rootsInField koalaPoly + +private def koalaNttRoots : Array _root_.KoalaBear.Field := + koalaBearNttFieldRootContext.rootsInField koalaPoly + +private def koalaNttFastRoots : Array _root_.KoalaBear.Field := + koalaBearNttFastFieldRootContext.rootsInField koalaPoly + +#guard (3 : _root_.KoalaBear.Field) ∈ koalaRoots.toList +#guard (5 : _root_.KoalaBear.Field) ∈ koalaRoots.toList +#guard koalaRoots.size = 2 +#guard koalaNttRoots == koalaRoots +#guard koalaNttFastRoots == koalaRoots + +private def koalaCounterexampleA : _root_.KoalaBear.Field := (3446241 : Nat) + +private def koalaCounterexampleB : _root_.KoalaBear.Field := (3750964 : Nat) + +private def koalaCounterexamplePoly : CPolynomial _root_.KoalaBear.Field := + CPolynomial.linearFactor koalaCounterexampleA * + CPolynomial.linearFactor koalaCounterexampleB + +private def koalaCounterexampleRoots : Array _root_.KoalaBear.Field := + koalaBearFieldRootContext.rootsInField koalaCounterexamplePoly + +private def koalaCounterexampleNttRoots : Array _root_.KoalaBear.Field := + koalaBearNttFieldRootContext.rootsInField koalaCounterexamplePoly + +private def koalaCounterexampleNttFastRoots : Array _root_.KoalaBear.Field := + koalaBearNttFastFieldRootContext.rootsInField koalaCounterexamplePoly + +#guard koalaCounterexampleA ∈ koalaCounterexampleRoots.toList +#guard koalaCounterexampleB ∈ koalaCounterexampleRoots.toList +#guard koalaCounterexampleRoots.size = 2 +#guard koalaCounterexampleNttRoots == koalaCounterexampleRoots +#guard koalaCounterexampleNttFastRoots == koalaCounterexampleRoots + +private def koalaZeroRootPoly : CPolynomial _root_.KoalaBear.Field := + CPolynomial.linearFactor (0 : _root_.KoalaBear.Field) * + CPolynomial.linearFactor (7 : _root_.KoalaBear.Field) + +private def koalaZeroRoots : Array _root_.KoalaBear.Field := + koalaBearFieldRootContext.rootsInField koalaZeroRootPoly + +#guard (0 : _root_.KoalaBear.Field) ∈ koalaZeroRoots.toList +#guard (7 : _root_.KoalaBear.Field) ∈ koalaZeroRoots.toList +#guard koalaZeroRoots.size = 2 + +private def koalaRepeatedRootPoly : CPolynomial _root_.KoalaBear.Field := + CPolynomial.linearFactor (11 : _root_.KoalaBear.Field) * + CPolynomial.linearFactor (11 : _root_.KoalaBear.Field) * + CPolynomial.linearFactor (13 : _root_.KoalaBear.Field) + +private def koalaRepeatedRoots : Array _root_.KoalaBear.Field := + koalaBearFieldRootContext.rootsInField koalaRepeatedRootPoly + +#guard (11 : _root_.KoalaBear.Field) ∈ koalaRepeatedRoots.toList +#guard (13 : _root_.KoalaBear.Field) ∈ koalaRepeatedRoots.toList +#guard koalaRepeatedRoots.size = 2 + +private def koalaNoRootPoly : CPolynomial _root_.KoalaBear.Field := + CPolynomial.ofArray #[-_root_.KoalaBear.primitiveRoot, 0, 1] + +#guard (koalaBearFieldRootContext.rootsInField koalaNoRootPoly).isEmpty + +private def fastKoalaPoly : CPolynomial _root_.KoalaBear.Fast.Field := + CPolynomial.linearFactor (3 : _root_.KoalaBear.Fast.Field) * + CPolynomial.linearFactor (5 : _root_.KoalaBear.Fast.Field) + +private def fastKoalaRoots : Array _root_.KoalaBear.Fast.Field := + fastKoalaBearFieldRootContext.rootsInField fastKoalaPoly + +private def fastKoalaNttRoots : Array _root_.KoalaBear.Fast.Field := + fastKoalaBearNttFieldRootContext.rootsInField fastKoalaPoly + +private def fastKoalaNttFastRoots : Array _root_.KoalaBear.Fast.Field := + fastKoalaBearNttFastFieldRootContext.rootsInField fastKoalaPoly + +#guard (3 : _root_.KoalaBear.Fast.Field) ∈ fastKoalaRoots.toList +#guard (5 : _root_.KoalaBear.Fast.Field) ∈ fastKoalaRoots.toList +#guard fastKoalaRoots.size = 2 +#guard fastKoalaNttRoots == fastKoalaRoots +#guard fastKoalaNttFastRoots == fastKoalaRoots + +end GuruswamiSudan.Root.FieldRoots.KoalaBear + +end CompPolyTests diff --git a/tests/CompPolyTests/Bivariate/GuruswamiSudan/Root/RothRuckenstein.lean b/tests/CompPolyTests/Bivariate/GuruswamiSudan/Root/RothRuckenstein.lean new file mode 100644 index 00000000..7bb7d495 --- /dev/null +++ b/tests/CompPolyTests/Bivariate/GuruswamiSudan/Root/RothRuckenstein.lean @@ -0,0 +1,90 @@ +/- +Copyright (c) 2026 CompPoly Contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Valerii Huhnin +-/ + +import CompPoly.Bivariate.GuruswamiSudan.Root.RothRuckenstein.Correctness +import Mathlib.Algebra.Field.ZMod + +/-! +# Roth-Ruckenstein Root Tests + +Regression coverage for the Roth-Ruckenstein root backend. +-/ + +namespace CompPolyTests + +open CompPoly +open CompPoly.GuruswamiSudan + +namespace GuruswamiSudan.Root.RothRuckenstein + +abbrev F3 := ZMod 3 +abbrev F5 := ZMod 5 + +instance : Fact (Nat.Prime 3) := + ⟨by decide⟩ + +instance : Fact (Nat.Prime 5) := + ⟨by decide⟩ + +private def f3Elements : Array F3 := + #[0, 1, 2] + +private theorem f3Elements_complete : ContainsAllFieldElements f3Elements := by + unfold ContainsAllFieldElements + intro a + fin_cases a <;> decide + +private def fieldRoots : FieldRootContext F3 := + enumeratingFieldRootContext F3 f3Elements f3Elements_complete + +private def f5Elements : Array F5 := + #[0, 1, 2, 3, 4] + +private theorem f5Elements_complete : ContainsAllFieldElements f5Elements := by + unfold ContainsAllFieldElements + intro a + fin_cases a <;> decide + +private def f5FieldRoots : FieldRootContext F5 := + enumeratingFieldRootContext F5 f5Elements f5Elements_complete + +private def qYMinusX : CBivariate F3 := + CBivariate.Y + CBivariate.monomialXY 1 0 2 + +private def pX : CPolynomial F3 := + CPolynomial.monomial 1 1 + +private def qXTimesYMinusOne : CBivariate F3 := + CBivariate.monomialXY 1 1 1 + CBivariate.monomialXY 1 0 2 + +private def pOne : CPolynomial F3 := + CPolynomial.C 1 + +#guard pX ∈ (rothRuckensteinRootsYDegreeLt fieldRoots qYMinusX 2).toList +#guard pX ∈ + ((rothRuckensteinRootContext F3 fieldRoots).rootsYDegreeLt qYMinusX 2).toList +#guard pOne ∈ (rothRuckensteinRootsYDegreeLt fieldRoots qXTimesYMinusOne 2).toList +#guard (rothRuckensteinRootsYDegreeLt fieldRoots (0 : CBivariate F3) 2).isEmpty + +private def pXPlusOne : CPolynomial F5 := + CPolynomial.ofArray #[(1 : F5), 1] + +private def pTwoXPlusTwo : CPolynomial F5 := + CPolynomial.ofArray #[(2 : F5), 2] + +private def qTwoLinearYFactors : CBivariate F5 := + CBivariate.linearYDivisor pXPlusOne * CBivariate.linearYDivisor pTwoXPlusTwo + +private def nonlinearInitialEquationRoots : Array (CPolynomial F5) := + rothRuckensteinRootsYDegreeLt f5FieldRoots qTwoLinearYFactors 2 + +#guard 2 < (initialCoefficientPolynomial qTwoLinearYFactors).val.size +#guard pXPlusOne ∈ nonlinearInitialEquationRoots.toList +#guard pTwoXPlusTwo ∈ nonlinearInitialEquationRoots.toList + +end GuruswamiSudan.Root.RothRuckenstein + +end CompPolyTests From 06ef8b531e4256cf9028e1e2f83c0fd2fd1f5725 Mon Sep 17 00:00:00 2001 From: Cody Gunton Date: Mon, 8 Jun 2026 13:10:08 -0400 Subject: [PATCH 07/14] Clean Lean 4.30 warnings in touched files --- CompPoly/Data/Nat/Bitwise.lean | 11 +++++------ CompPoly/Data/RingTheory/AlgebraTower.lean | 3 +-- CompPoly/ToMathlib/MvPolynomial/Equiv.lean | 4 ++-- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/CompPoly/Data/Nat/Bitwise.lean b/CompPoly/Data/Nat/Bitwise.lean index 4b751d17..1d5175c5 100644 --- a/CompPoly/Data/Nat/Bitwise.lean +++ b/CompPoly/Data/Nat/Bitwise.lean @@ -547,13 +547,12 @@ lemma sum_eq_xor_plus_twice_and (n : Nat) : ∀ m : ℕ, n + m = (n ^^^ m) + 2 * rw [zero_add, Nat.zero_and, mul_zero, add_zero, Nat.zero_xor] | bit bn n2 ih => intro m - let resDiv2M := Nat.boddDiv2 m - let bm := resDiv2M.fst - let m2 := resDiv2M.snd + let bm := Nat.bodd m + let m2 := Nat.div2 m have h_m2 : m2 = Nat.div2 m := by - show (Nat.boddDiv2 m).snd = Nat.div2 m; rw [Nat.boddDiv2_eq] + rfl have h_bm : bm = Nat.bodd m := by - show (Nat.boddDiv2 m).fst = Nat.bodd m; rw [Nat.boddDiv2_eq] + rfl let mVal := Nat.bit bm m2 set nVal := Nat.bit bn n2 set getBitN := bn.toNat @@ -1295,7 +1294,7 @@ lemma getBit_of_binaryFinMapToNat {n : ℕ} (m : Fin n → ℕ) (h_binary: ∀ j induction n with | zero => intro k; - simp only [Nat.pow_zero, Fin.val_eq_zero, not_lt_zero', ↓reduceDIte] + simp only [Nat.pow_zero, Fin.val_eq_zero, _root_.not_lt_zero, ↓reduceDIte] exact getBit_zero_eq_zero | succ n ih => -- Inductive step: Assume the property holds for `n`, prove it for `n+1`. diff --git a/CompPoly/Data/RingTheory/AlgebraTower.lean b/CompPoly/Data/RingTheory/AlgebraTower.lean index f3216917..17a2cfeb 100644 --- a/CompPoly/Data/RingTheory/AlgebraTower.lean +++ b/CompPoly/Data/RingTheory/AlgebraTower.lean @@ -123,8 +123,7 @@ def AlgebraTowerEquiv.toAlgEquivOverLeft (e : AlgebraTowerEquiv A B) (i j : ι) commutes' := fun r => by simp only [RingEquiv.toEquiv_eq_coe, Equiv.toFun_as_coe, EquivLike.coe_coe] unfold instAij instAiBij - simp only [AlgebraTower.toAlgebra, AlgebraTowerEquiv.toAlgebraOverLeft, - AlgebraTowerEquiv.algebraMapRightUp, RingHom.algebraMap_toAlgebra] + simp only [AlgebraTowerEquiv.algebraMapRightUp, RingHom.algebraMap_toAlgebra] simp only [RingEquiv.toRingHom_eq_coe, RingHom.coe_comp, RingHom.coe_coe, Function.comp_apply] exact Eq.symm (e.commutesLeft' i j h r) map_mul' := fun x y => by diff --git a/CompPoly/ToMathlib/MvPolynomial/Equiv.lean b/CompPoly/ToMathlib/MvPolynomial/Equiv.lean index e92074bb..9bec3468 100644 --- a/CompPoly/ToMathlib/MvPolynomial/Equiv.lean +++ b/CompPoly/ToMathlib/MvPolynomial/Equiv.lean @@ -111,8 +111,8 @@ theorem eval_eq_eval_mv_eval_finSuccEquivNth (s : Fin n → R) (y : R) congr 2 apply MvPolynomial.algHom_ext simp [Fin.forall_iff_succAbove p, aeval_X, Fin.insertNth_apply_same, Polynomial.mapAlgHom, - AlgHom.toRingHom_eq_coe, coe_aeval_eq_eval, AlgEquiv.toAlgHom_eq_coe, AlgHom.coe_comp, - Polynomial.coe_aeval_eq_eval, AlgHom.coe_mk, coe_mapRingHom, AlgHom.coe_coe, comp_apply, + AlgHom.toRingHom_eq_coe, coe_aeval_eq_eval, AlgHom.coe_comp, + Polynomial.coe_aeval_eq_eval, AlgHom.coe_mk, coe_mapRingHom, comp_apply, finSuccEquivNth_apply, eval₂Hom_X', Polynomial.map_X, Polynomial.eval_X, Fin.insertNth_apply_succAbove, Polynomial.map_C, eval_X, Polynomial.eval_C, implies_true, and_self] From fa796a1bbb79d7fe4c332b176e2cf9a3d57d0528 Mon Sep 17 00:00:00 2001 From: Cody Gunton Date: Mon, 8 Jun 2026 13:12:28 -0400 Subject: [PATCH 08/14] Clean remaining Lean 4.30 warnings --- CompPoly/Bivariate/ToPoly.lean | 8 ++++---- CompPoly/Data/Polynomial/MonomialBasis.lean | 4 ++-- CompPoly/Fields/Binary/AdditiveNTT/Intermediate.lean | 2 +- .../Fields/Binary/AdditiveNTT/NovelPolynomialBasis.lean | 2 +- CompPoly/Fields/Binary/Common.lean | 8 ++++---- CompPoly/ToMathlib/Polynomial/BivariateDegree.lean | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/CompPoly/Bivariate/ToPoly.lean b/CompPoly/Bivariate/ToPoly.lean index e031cb63..413f7ec6 100644 --- a/CompPoly/Bivariate/ToPoly.lean +++ b/CompPoly/Bivariate/ToPoly.lean @@ -210,7 +210,7 @@ theorem ofPoly_coeff {R : Type*} [BEq R] [LawfulBEq R] [Nontrivial R] [Semiring /-- The outer coefficient of `toPoly p` is `CPolynomial.coeff p n` converted to `R[X]`. -/ theorem toPoly_coeff {R : Type*} [BEq R] [LawfulBEq R] [Semiring R] (p : CBivariate R) (n : ℕ) : (toPoly p).coeff n = (CPolynomial.coeff p n).toPoly := by - rw [ CBivariate.toPoly, Polynomial.finset_sum_coeff ] + rw [ CBivariate.toPoly, Polynomial.finsetSum_coeff ] rw [ Finset.sum_eq_single n ] <;> simp +contextual [ Polynomial.coeff_monomial ] simp_all +decide [ CPolynomial.mem_support_iff ] aesop @@ -378,7 +378,7 @@ theorem evalY_toPoly {R : Type*} [BEq R] [LawfulBEq R] [Nontrivial R] [Semiring simp only [Array.length_toList, Array.getElem?_toList] symm unfold CBivariate.toPoly - simp +decide [ Polynomial.eval_finset_sum ] + simp +decide [ Polynomial.eval_finsetSum ] rw [ Finset.sum_subset ] · exact fun i hi ↦ Finset.mem_range.mpr (Nat.lt_of_lt_of_le (Finset.mem_range.mp (Finset.mem_filter.mp hi |>.1)) (by simp)) @@ -468,7 +468,7 @@ theorem natDegreeY_toPoly {R : Type*} [BEq R] [LawfulBEq R] [Nontrivial R] [Semi theorem coeff_toPoly_Y {R : Type*} [BEq R] [LawfulBEq R] [Nontrivial R] [Semiring R] (f : CBivariate R) (j : ℕ) : (toPoly f).coeff j = CPolynomial.toPoly (f.val.coeff j) := by - erw [ Polynomial.finset_sum_coeff ] + erw [ Polynomial.finsetSum_coeff ] rw [ Finset.sum_eq_single j ] <;> simp +contextual [ Polynomial.coeff_monomial ] intro hj rw [ CPolynomial.support ] at hj @@ -628,7 +628,7 @@ theorem evalX_toPoly_eval_commute {R : Type*} [BEq R] [LawfulBEq R] [Nontrivial Polynomial.eval a (∑ j ∈ s, g j * Polynomial.C (y ^ j)) = ∑ j ∈ s, Polynomial.eval a (g j) * y ^ j := by exact fun s g hg => by - rw [Polynomial.eval_finset_sum, + rw [Polynomial.eval_finsetSum, Finset.sum_congr rfl (fun j hj => h_eval_mul_C _ _ (hg j hj))] convert h_sum _ _ _ · simp +decide [Polynomial.eval_eq_sum, Polynomial.sum_def] diff --git a/CompPoly/Data/Polynomial/MonomialBasis.lean b/CompPoly/Data/Polynomial/MonomialBasis.lean index a48928e1..adb8002c 100644 --- a/CompPoly/Data/Polynomial/MonomialBasis.lean +++ b/CompPoly/Data/Polynomial/MonomialBasis.lean @@ -64,7 +64,7 @@ noncomputable def monomialBasisOfDegreeLT {n : ℕ} : Basis (Fin n) L (L⦃< n simp only [SetLike.mk_smul_mk] conv => lhs - simp only [Finsupp.sum, AddSubmonoidClass.coe_finset_sum, finset_sum_coeff, coeff_smul, + simp only [Finsupp.sum, AddSubmonoidClass.coe_finsetSum, finsetSum_coeff, coeff_smul, coeff_X_pow, smul_eq_mul, mul_ite, mul_one, mul_zero, monomials_in_submodule, v] -- ⊢ (∑ x ∈ l.support, if ↑i = ↑x then l x else 0) = l i simp_rw [Fin.val_eq_val, eq_comm] @@ -110,7 +110,7 @@ noncomputable def monomialBasisOfDegreeLT {n : ℕ} : Basis (Fin n) L (L⦃< n -- we have to convert (↑p).sum into Fin n → L form using Polynomial.sum_fin simp only [monomial_zero_right, implies_true, ←Polynomial.sum_fin (hn := h_deg_p)] -- ⊢ ↑(∑ i, c i • monomials_in_submodule i) = ∑ i, (monomial ↑i) ((↑p).coeff ↑i) - rw [AddSubmonoidClass.coe_finset_sum] -- bring both sides back to L[X] + rw [AddSubmonoidClass.coe_finsetSum] -- bring both sides back to L[X] apply Finset.sum_congr rfl intro ⟨i, hi_finN⟩ hi simp only [SetLike.mk_smul_mk, c, monomials_in_submodule] diff --git a/CompPoly/Fields/Binary/AdditiveNTT/Intermediate.lean b/CompPoly/Fields/Binary/AdditiveNTT/Intermediate.lean index 4cc5370f..cf8edf65 100644 --- a/CompPoly/Fields/Binary/AdditiveNTT/Intermediate.lean +++ b/CompPoly/Fields/Binary/AdditiveNTT/Intermediate.lean @@ -277,7 +277,7 @@ lemma getSDomainBasisCoeff_of_sum_repr [NeZero R_rate] (i : Fin (ℓ + 1)) simp only [Basis.repr_self] have hx_at_j_simplified : (∑ j_x, x_coeffs j_x • (b.repr (b j_x))) j = x_coeffs j := by - simp only [h_repr_basis, Finsupp.smul_single, smul_eq_mul, mul_one, Finsupp.coe_finset_sum, + simp only [h_repr_basis, Finsupp.smul_single, smul_eq_mul, mul_one, Finsupp.coe_finsetSum, Finset.sum_apply, Finsupp.single_apply, Finset.sum_ite_eq', Finset.mem_univ, ↓reduceIte] let x_coeffs_fs := Finsupp.equivFunOnFinite.symm x_coeffs let rhs_sum := ∑ j_x, (x_coeffs_fs j_x) • (b j_x) diff --git a/CompPoly/Fields/Binary/AdditiveNTT/NovelPolynomialBasis.lean b/CompPoly/Fields/Binary/AdditiveNTT/NovelPolynomialBasis.lean index f504e7f7..f47a7d04 100644 --- a/CompPoly/Fields/Binary/AdditiveNTT/NovelPolynomialBasis.lean +++ b/CompPoly/Fields/Binary/AdditiveNTT/NovelPolynomialBasis.lean @@ -189,7 +189,7 @@ lemma βᵢ_not_in_Uᵢ (i : Fin r) : omega else push Not at h_i - have h_i_eq_0: i = 0 := by exact Fin.le_zero_iff'.mp h_i + have h_i_eq_0: i = 0 := by exact nonpos_iff_eq_zero.mp h_i have set_empty: Set.Ico 0 i = ∅ := by rw [h_i_eq_0] simp only [Set.Ico_eq_empty_iff] diff --git a/CompPoly/Fields/Binary/Common.lean b/CompPoly/Fields/Binary/Common.lean index b4c61bc5..6e1189ee 100644 --- a/CompPoly/Fields/Binary/Common.lean +++ b/CompPoly/Fields/Binary/Common.lean @@ -360,7 +360,7 @@ lemma toPoly_ne_zero_iff_ne_zero {w : Nat} (v : BitVec w) : have h_coeff_formula : ∀ i : Fin w, (toPoly v).coeff i.val = if v.getLsb i then 1 else 0 := by intro i unfold toPoly - rw [finset_sum_coeff] + rw [finsetSum_coeff] have h_ite: ∀ b : Fin w, (if v.getLsb b then (X : (ZMod 2)[X])^b.val else 0).coeff i.val = if v.getLsb b then (if b = i then 1 else 0) else 0 := by intro b @@ -470,7 +470,7 @@ lemma BitVec_lt_two_pow_of_toPoly_degree_lt {w d : ℕ} (v : BitVec w) have h_i_lt_w : i < w := by omega have h_coeff_one : (toPoly v).coeff i = 1 := by unfold toPoly - rw [finset_sum_coeff] + rw [finsetSum_coeff] rw [Finset.sum_eq_single ⟨i, h_i_lt_w⟩] · have h_getLsb_eq_testBit : v.getLsb ⟨i, h_i_lt_w⟩ = v.toNat.testBit i := by rfl @@ -627,7 +627,7 @@ theorem toPoly_coeff {w : ℕ} (v : BitVec w) (n : ℕ) : (if h : n < w then (if v.getLsb ⟨n, h⟩ = true then (1 : ZMod 2) else 0) else 0) := by classical unfold toPoly - rw [Polynomial.finset_sum_coeff] + rw [Polynomial.finsetSum_coeff] by_cases h : n < w · -- case n < w simp only [dif_pos h] @@ -667,7 +667,7 @@ theorem toPoly_coeff {w : ℕ} (v : BitVec w) (n : ℕ) : intro hi0_not simp [Finset.mem_univ] at hi0_not -- conclude - simpa [Polynomial.finset_sum_coeff, i0] using hmain + simpa [Polynomial.finsetSum_coeff, i0] using hmain · -- case ¬ n < w simp only [dif_neg h] -- show every summand has coefficient 0 diff --git a/CompPoly/ToMathlib/Polynomial/BivariateDegree.lean b/CompPoly/ToMathlib/Polynomial/BivariateDegree.lean index 09c8aae2..7e6526df 100644 --- a/CompPoly/ToMathlib/Polynomial/BivariateDegree.lean +++ b/CompPoly/ToMathlib/Polynomial/BivariateDegree.lean @@ -321,7 +321,7 @@ theorem natDegree_sum_eq_of_unique {α : Type*} {s : Finset α} {f : α → F[X] intro y hy hym exact hcoeff_others y hy hym have hcoeff_eq : (∑ x ∈ s, f x).coeff deg = (f mx).coeff deg := by - rw [Polynomial.finset_sum_coeff (s := s) (f := f) (n := deg)] + rw [Polynomial.finsetSum_coeff (s := s) (f := f) (n := deg)] exact hsum_coeff have hcoeff_ne0 : (∑ x ∈ s, f x).coeff deg ≠ 0 := by simpa [hcoeff_eq] using hmx_coeff_ne0 From 4bdb4dd18456d1e7912df613c41e061e76f1d086 Mon Sep 17 00:00:00 2001 From: Cody Gunton Date: Mon, 8 Jun 2026 16:20:43 -0400 Subject: [PATCH 09/14] Use Nat.not_lt_zero in bitwise proof --- CompPoly/Data/Nat/Bitwise.lean | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CompPoly/Data/Nat/Bitwise.lean b/CompPoly/Data/Nat/Bitwise.lean index 1d5175c5..a82b3bba 100644 --- a/CompPoly/Data/Nat/Bitwise.lean +++ b/CompPoly/Data/Nat/Bitwise.lean @@ -1294,7 +1294,7 @@ lemma getBit_of_binaryFinMapToNat {n : ℕ} (m : Fin n → ℕ) (h_binary: ∀ j induction n with | zero => intro k; - simp only [Nat.pow_zero, Fin.val_eq_zero, _root_.not_lt_zero, ↓reduceDIte] + simp only [Nat.pow_zero, Fin.val_eq_zero, Nat.not_lt_zero, ↓reduceDIte] exact getBit_zero_eq_zero | succ n ih => -- Inductive step: Assume the property holds for `n`, prove it for `n+1`. From 4431ddf9c7b36f4625811b367d5bf5d93001f272 Mon Sep 17 00:00:00 2001 From: Cody Gunton Date: Mon, 8 Jun 2026 16:53:18 -0400 Subject: [PATCH 10/14] Minimize abstract tower Lean 4.30 proof --- .../Fields/Binary/Tower/Abstract/Algebra.lean | 8 -------- .../Fields/Binary/Tower/Abstract/Basis.lean | 17 ++--------------- 2 files changed, 2 insertions(+), 23 deletions(-) diff --git a/CompPoly/Fields/Binary/Tower/Abstract/Algebra.lean b/CompPoly/Fields/Binary/Tower/Abstract/Algebra.lean index 93e80c27..9c35e455 100644 --- a/CompPoly/Fields/Binary/Tower/Abstract/Algebra.lean +++ b/CompPoly/Fields/Binary/Tower/Abstract/Algebra.lean @@ -224,14 +224,6 @@ lemma algebraMap_adjacent_tower_def (l : ℕ) : rw [binaryTowerAlgebra_def] exact towerAlgebraMap_succ_1 l -lemma coe_eq_algebraMap_adjacent_tower (l : ℕ) (x : BTField l) : - (x : BTField (l + 1)) = (algebraMap (BTField l) (BTField (l + 1))) x := by - rw [algebraMap_adjacent_tower_def] - unfold canonicalEmbedding - simp only [BTField.eq_1, poly.eq_1, Z.eq_1, eq_mp_eq_cast] - erw [cast_eq] - rfl - lemma algebraMap_adjacent_tower_succ_eq_Adjoin_of (k : ℕ) : (algebraMap (BTField k) (BTField (k + 1))) = of (poly k) := by rw [algebraMap_adjacent_tower_def] diff --git a/CompPoly/Fields/Binary/Tower/Abstract/Basis.lean b/CompPoly/Fields/Binary/Tower/Abstract/Basis.lean index 33a64c3a..cdff668a 100644 --- a/CompPoly/Fields/Binary/Tower/Abstract/Basis.lean +++ b/CompPoly/Fields/Binary/Tower/Abstract/Basis.lean @@ -327,22 +327,9 @@ theorem multilinearBasis_apply (r : ℕ) : ∀ l : ℕ, (h_le : l ≤ r) → ∀ convert (binaryTowerAlgebra_apply_assoc (l:=l + x.val + 1) (mid:=r1) (r:=r) (h_l_le_mid:=by omega) (h_mid_le_r:=by omega) ((𝕏 (l + x.val)) ^ Nat.getBit x.val j.val)).symm using 1 - rw! (castMode:=.all) [show r = r1 + 1 by omega] - convert (coe_eq_algebraMap_adjacent_tower r1 - (((@binaryAlgebraTower (l:=l + x.val + 1) (r:=r1) (h_le:=by omega)).algebraMap) - (𝕏 (l + x.val) ^ Nat.getBit x.val j.val))) using 1 + subst r rw! (castMode:=.all) [←h_r1_eq_l_plus_prevDiff] - rw! (castMode:=.all) [hr] - simp only [eq_mp_eq_cast] - erw [cast_eq] - change (algebraMap (BTField r1) (BTField (r1 + 1))) - (((@binaryAlgebraTower (l:=l + x.val + 1) (r:=r1) (h_le:=by omega)).algebraMap) - (𝕏 (l + x.val) ^ Nat.getBit x.val j.val)) = - (AdjoinRoot.of (poly r1)) - (((@binaryAlgebraTower (l:=l + x.val + 1) (r:=r1) (h_le:=by omega)).algebraMap) - (𝕏 (l + x.val) ^ Nat.getBit x.val j.val)) - rw [algebraMap_adjacent_tower_succ_eq_Adjoin_of] - rfl + simp only end MultilinearBasis From f514bc830b8c7b0eceae16d8357546d54b4aa99c Mon Sep 17 00:00:00 2001 From: Cody Gunton Date: Mon, 8 Jun 2026 17:32:30 -0400 Subject: [PATCH 11/14] Minimize abstract basis Lean 4.30 proof --- .../Fields/Binary/Tower/Abstract/Basis.lean | 63 ++++++++++--------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/CompPoly/Fields/Binary/Tower/Abstract/Basis.lean b/CompPoly/Fields/Binary/Tower/Abstract/Basis.lean index cdff668a..4f57f766 100644 --- a/CompPoly/Fields/Binary/Tower/Abstract/Basis.lean +++ b/CompPoly/Fields/Binary/Tower/Abstract/Basis.lean @@ -299,37 +299,40 @@ theorem multilinearBasis_apply (r : ℕ) : ∀ l : ℕ, (h_le : l ≤ r) → ∀ rw [Fin.prod_univ_castSucc] -- split the prod of rhs simp only [Fin.val_cast, Fin.val_castSucc, Fin.val_last] + simp_rw [algebraMap.coe_pow] -- rhs simp_rw [algebraMap.coe_prod] -- lhs - have h_cast_j : 2 ^ (prevDiff + 1) = 2 ^ (r - l) := by - rw [h_r_sub_l] - have h_low_bits (x : Fin prevDiff) : - Nat.getBit x.val (leftModNat (m:=2 ^ prevDiff) (n:=2) - (by exact Nat.two_pow_pos prevDiff) (i:=Fin.cast h2.symm j)).val = - Nat.getBit x.val j.val := by - have hbits := bit_revFinProdFinEquiv_symm_2_pow_succ - (n:=prevDiff) (j:=Fin.cast h_cast_j.symm j) (i:=Fin.castSucc x) - simpa only [Fin.val_castSucc, Fin.val_cast, Fin.is_lt, ↓reduceIte, - revFinProdFinEquiv_symm_apply] using hbits.symm - have h_top_bit : - j.val / 2 ^ (r - l - 1) = Nat.getBit prevDiff j.val := by - have hbits := bit_revFinProdFinEquiv_symm_2_pow_succ - (n:=prevDiff) (j:=Fin.cast h_cast_j.symm j) (i:=Fin.last prevDiff) - simpa only [Fin.val_last, Fin.val_cast, lt_self_iff_false, ↓reduceIte, - revFinProdFinEquiv_symm_apply, leftDivNat, h_prevDiff] - using hbits.symm - rw! (castMode:=.all) [h_r1_eq_l_plus_prevDiff, h_top_bit] - rw! (castMode:=.all) [show l + prevDiff - l = prevDiff by omega] - congr 1 - apply congrArg (fun f : Fin prevDiff → BTField r => - (Finset.univ : Finset (Fin prevDiff)).prod f) - funext x - rw [h_low_bits x] - convert (binaryTowerAlgebra_apply_assoc (l:=l + x.val + 1) (mid:=r1) (r:=r) - (h_l_le_mid:=by omega) (h_mid_le_r:=by omega) - ((𝕏 (l + x.val)) ^ Nat.getBit x.val j.val)).symm using 1 - subst r - rw! (castMode:=.all) [←h_r1_eq_l_plus_prevDiff] - simp only + unfold Algebra.cast + conv_lhs => + rw [←Fin.prod_congr' (b:=r1-l) (a:=prevDiff) (h:=by omega)] + simp only [Fin.val_cast] + simp (config := { failIfUnchanged := false }) only [algebraMap, instAlgebraSucc] + simp only [map_pow] + ------------------ Equality of bit-based powers of generators ----------------- + --- The outtermost term + have hfinProd_msb := bit_revFinProdFinEquiv_symm_2_pow_succ (n:=prevDiff) + (i:=⟨prevDiff, by omega⟩) (j:=⟨j, by omega⟩) + simp only [lt_self_iff_false, ↓reduceIte, revFinProdFinEquiv_symm_apply] at hfinProd_msb + conv_rhs => simp only [hfinProd_msb, leftDivNat] + --- Inner-prod term: prove equality of the two factors + refine congr_arg₂ (· * ·) ?_ ?_ + · congr 1 + funext i + have hfinProd_lsb := bit_revFinProdFinEquiv_symm_2_pow_succ + (n:=prevDiff) (i:=⟨i, by omega⟩) + (j:=⟨j, by omega⟩) + simp only [Fin.is_lt, ↓reduceIte, revFinProdFinEquiv_symm_apply] at hfinProd_lsb + rw [hfinProd_lsb] + congr 1 + rw [←RingHom.comp_apply] + rw [←AlgebraTower.coherence'] + rw! (castMode:=.all) [h_r] + simp only [Fin.val_cast, Fin.val_castSucc] + · have h_exp_eq : (↑j : ℕ) / 2 ^ (r - l - 1) = (↑j : ℕ) / 2 ^ prevDiff := + congr_arg (fun d => (↑j : ℕ) / 2 ^ d) h_prevDiff.symm + refine congr_arg₂ (· ^ ·) + (algebraMap_𝕏_eq_of_index_eq r r1 (l + prevDiff) (by omega) (by omega) + h_r1_eq_l_plus_prevDiff) + h_exp_eq end MultilinearBasis From 5742c5c0f983f573f1445c318dca7df4a88a42d6 Mon Sep 17 00:00:00 2001 From: Cody Gunton Date: Mon, 8 Jun 2026 17:37:21 -0400 Subject: [PATCH 12/14] Minimize concrete basis Lean 4.30 proof --- .../Fields/Binary/Tower/Concrete/Basis.lean | 68 +++++++++---------- 1 file changed, 32 insertions(+), 36 deletions(-) diff --git a/CompPoly/Fields/Binary/Tower/Concrete/Basis.lean b/CompPoly/Fields/Binary/Tower/Concrete/Basis.lean index 68d560d1..8fa1255b 100644 --- a/CompPoly/Fields/Binary/Tower/Concrete/Basis.lean +++ b/CompPoly/Fields/Binary/Tower/Concrete/Basis.lean @@ -544,43 +544,39 @@ theorem multilinearBasis_apply (r : ℕ) : ∀ l : ℕ, (h_le : l ≤ r) → ∀ rw [Fin.prod_univ_castSucc] simp only [Fin.val_cast, Fin.val_castSucc, Fin.val_last] + simp_rw [algebraMap.coe_pow] simp_rw [algebraMap.coe_prod] - have h_cast_j : 2 ^ (prevDiff + 1) = 2 ^ (r - l) := by - rw [h_r_sub_l] - have h_low_bits (x : Fin prevDiff) : - Nat.getBit x.val (leftModNat (m:=2 ^ prevDiff) (n:=2) - (by exact Nat.two_pow_pos prevDiff) (i:=Fin.cast h2.symm j)).val = - Nat.getBit x.val j.val := by - have hbits := bit_revFinProdFinEquiv_symm_2_pow_succ - (n:=prevDiff) (j:=Fin.cast h_cast_j.symm j) (i:=Fin.castSucc x) - simpa only [Fin.val_castSucc, Fin.val_cast, Fin.is_lt, ↓reduceIte, - revFinProdFinEquiv_symm_apply] using hbits.symm - have h_top_bit : - j.val / 2 ^ (r - l - 1) = Nat.getBit prevDiff j.val := by - have hbits := bit_revFinProdFinEquiv_symm_2_pow_succ - (n:=prevDiff) (j:=Fin.cast h_cast_j.symm j) (i:=Fin.last prevDiff) - simpa only [Fin.val_last, Fin.val_cast, lt_self_iff_false, ↓reduceIte, - revFinProdFinEquiv_symm_apply, leftDivNat, h_prevDiff] - using hbits.symm - rw! (castMode:=.all) [h_r1_eq_l_plus_prevDiff, h_top_bit] - rw! (castMode:=.all) [show l + prevDiff - l = prevDiff by omega] - congr 1 - apply congrArg (fun f : Fin prevDiff → ConcreteBTField r => - (Finset.univ : Finset (Fin prevDiff)).prod f) - funext x - rw [h_low_bits x] - convert (ConcreteBTFieldAlgebra_apply_assoc (l:=l + x.val + 1) (mid:=r1) (r:=r) - (h_l_le_mid:=by omega) (h_mid_le_r:=by omega) - ((𝕏 (l + x.val)) ^ Nat.getBit x.val j.val)).symm using 1 - rw! (castMode:=.all) [show r = r1 + 1 by omega] - rw! (castMode:=.all) [←h_r1_eq_l_plus_prevDiff] - change (algebraMap (ConcreteBTField r1) (ConcreteBTField (r1 + 1))) - (((@ConcreteBTFieldAlgebra (l:=l + x.val + 1) (r:=r1) (h_le:=by omega)).algebraMap) - (𝕏 (l + x.val) ^ Nat.getBit x.val j.val)) = - (algebraMap (ConcreteBTField r1) (ConcreteBTField (r1 + 1))) - (((@ConcreteBTFieldAlgebra (l:=l + x.val + 1) (r:=r1) (h_le:=by omega)).algebraMap) - (𝕏 (l + x.val) ^ Nat.getBit x.val j.val)) - rfl + unfold Algebra.cast + conv_lhs => + rw [←Fin.prod_congr' (b:=r1 - l) (a:=prevDiff) (h:=by omega)] + simp only [Fin.val_cast] + simp (config := { failIfUnchanged := false }) only [algebraMap, instAlgebraSucc] + simp only [map_pow] + ------------------ Equality of bit-based powers of generators ----------------- + have hfinProd_msb := bit_revFinProdFinEquiv_symm_2_pow_succ (n:=prevDiff) + (i:=⟨prevDiff, by omega⟩) (j:=⟨j, by omega⟩) + simp only [lt_self_iff_false, ↓reduceIte, revFinProdFinEquiv_symm_apply] at hfinProd_msb + conv_rhs => simp only [hfinProd_msb, leftDivNat] + --- Inner-prod term: prove equality of the two factors + refine congr_arg₂ (· * ·) ?_ ?_ + · congr 1 + funext i + have hfinProd_lsb := bit_revFinProdFinEquiv_symm_2_pow_succ + (n:=prevDiff) (i:=⟨i, by omega⟩) + (j:=⟨j, by omega⟩) + simp only [Fin.is_lt, ↓reduceIte, revFinProdFinEquiv_symm_apply] at hfinProd_lsb + rw [hfinProd_lsb] + congr 1 + rw [←RingHom.comp_apply] + rw [←AlgebraTower.coherence'] + rw! (castMode:=.all) [h_r] + simp only [Fin.val_cast, Fin.val_castSucc] + · have h_exp_eq : (↑j : ℕ) / 2 ^ (r - l - 1) = (↑j : ℕ) / 2 ^ prevDiff := + congr_arg (fun d => (↑j : ℕ) / 2 ^ d) h_prevDiff.symm + refine congr_arg₂ (· ^ ·) + (algebraMap_𝕏_eq_of_index_eq r r1 (l + prevDiff) (by omega) (by omega) + h_r1_eq_l_plus_prevDiff) + h_exp_eq end ConcreteMultilinearBasis From eceb46123694e0b7794b88bab235a7af4307262d Mon Sep 17 00:00:00 2001 From: Cody Gunton Date: Mon, 8 Jun 2026 18:03:24 -0400 Subject: [PATCH 13/14] Restore simp only in MvPolynomial equiv proof --- CompPoly/ToMathlib/MvPolynomial/Equiv.lean | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CompPoly/ToMathlib/MvPolynomial/Equiv.lean b/CompPoly/ToMathlib/MvPolynomial/Equiv.lean index 9bec3468..79278f42 100644 --- a/CompPoly/ToMathlib/MvPolynomial/Equiv.lean +++ b/CompPoly/ToMathlib/MvPolynomial/Equiv.lean @@ -110,12 +110,12 @@ theorem eval_eq_eval_mv_eval_finSuccEquivNth (s : Fin n → R) (y : R) ((Polynomial.mapAlgHom (aeval s)).comp (finSuccEquivNth R p).toAlgHom) f congr 2 apply MvPolynomial.algHom_ext - simp [Fin.forall_iff_succAbove p, aeval_X, Fin.insertNth_apply_same, Polynomial.mapAlgHom, + simp only [Fin.forall_iff_succAbove p, aeval_X, Fin.insertNth_apply_same, Polynomial.mapAlgHom, AlgHom.toRingHom_eq_coe, coe_aeval_eq_eval, AlgHom.coe_comp, Polynomial.coe_aeval_eq_eval, AlgHom.coe_mk, coe_mapRingHom, comp_apply, - finSuccEquivNth_apply, eval₂Hom_X', Polynomial.map_X, Polynomial.eval_X, - Fin.insertNth_apply_succAbove, Polynomial.map_C, eval_X, Polynomial.eval_C, implies_true, - and_self] + AlgEquiv.toAlgHom_apply, finSuccEquivNth_apply, eval₂Hom_X', Polynomial.map_X, + Polynomial.eval_X, Fin.insertNth_apply_succAbove, Polynomial.map_C, eval_X, + Polynomial.eval_C, implies_true, and_self] /-- A monomial index `m` is in the support of the `i`-th coefficient of `finSuccEquivNth R p f` if and only if `m.insertNth p i` is in the support of `f`. -/ From e1d6cfef86c751a274a40c4d4d6434097814e60c Mon Sep 17 00:00:00 2001 From: Cody Gunton Date: Mon, 8 Jun 2026 22:38:56 -0400 Subject: [PATCH 14/14] Restore tower basis assoc normalization --- CompPoly/Fields/Binary/Tower/Abstract/Basis.lean | 10 +++------- CompPoly/Fields/Binary/Tower/Concrete/Basis.lean | 10 +++------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/CompPoly/Fields/Binary/Tower/Abstract/Basis.lean b/CompPoly/Fields/Binary/Tower/Abstract/Basis.lean index 4f57f766..1c6ce968 100644 --- a/CompPoly/Fields/Binary/Tower/Abstract/Basis.lean +++ b/CompPoly/Fields/Binary/Tower/Abstract/Basis.lean @@ -305,8 +305,8 @@ theorem multilinearBasis_apply (r : ℕ) : ∀ l : ℕ, (h_le : l ≤ r) → ∀ conv_lhs => rw [←Fin.prod_congr' (b:=r1-l) (a:=prevDiff) (h:=by omega)] simp only [Fin.val_cast] - simp (config := { failIfUnchanged := false }) only [algebraMap, instAlgebraSucc] - simp only [map_pow] + simp only [RingHom.map_pow] + simp only [←binaryTowerAlgebra_apply_assoc] ------------------ Equality of bit-based powers of generators ----------------- --- The outtermost term have hfinProd_msb := bit_revFinProdFinEquiv_symm_2_pow_succ (n:=prevDiff) @@ -322,11 +322,7 @@ theorem multilinearBasis_apply (r : ℕ) : ∀ l : ℕ, (h_le : l ≤ r) → ∀ (j:=⟨j, by omega⟩) simp only [Fin.is_lt, ↓reduceIte, revFinProdFinEquiv_symm_apply] at hfinProd_lsb rw [hfinProd_lsb] - congr 1 - rw [←RingHom.comp_apply] - rw [←AlgebraTower.coherence'] - rw! (castMode:=.all) [h_r] - simp only [Fin.val_cast, Fin.val_castSucc] + rfl · have h_exp_eq : (↑j : ℕ) / 2 ^ (r - l - 1) = (↑j : ℕ) / 2 ^ prevDiff := congr_arg (fun d => (↑j : ℕ) / 2 ^ d) h_prevDiff.symm refine congr_arg₂ (· ^ ·) diff --git a/CompPoly/Fields/Binary/Tower/Concrete/Basis.lean b/CompPoly/Fields/Binary/Tower/Concrete/Basis.lean index 8fa1255b..7c189ab9 100644 --- a/CompPoly/Fields/Binary/Tower/Concrete/Basis.lean +++ b/CompPoly/Fields/Binary/Tower/Concrete/Basis.lean @@ -550,8 +550,8 @@ theorem multilinearBasis_apply (r : ℕ) : ∀ l : ℕ, (h_le : l ≤ r) → ∀ conv_lhs => rw [←Fin.prod_congr' (b:=r1 - l) (a:=prevDiff) (h:=by omega)] simp only [Fin.val_cast] - simp (config := { failIfUnchanged := false }) only [algebraMap, instAlgebraSucc] - simp only [map_pow] + simp only [RingHom.map_pow] + simp only [←ConcreteBTFieldAlgebra_apply_assoc] ------------------ Equality of bit-based powers of generators ----------------- have hfinProd_msb := bit_revFinProdFinEquiv_symm_2_pow_succ (n:=prevDiff) (i:=⟨prevDiff, by omega⟩) (j:=⟨j, by omega⟩) @@ -566,11 +566,7 @@ theorem multilinearBasis_apply (r : ℕ) : ∀ l : ℕ, (h_le : l ≤ r) → ∀ (j:=⟨j, by omega⟩) simp only [Fin.is_lt, ↓reduceIte, revFinProdFinEquiv_symm_apply] at hfinProd_lsb rw [hfinProd_lsb] - congr 1 - rw [←RingHom.comp_apply] - rw [←AlgebraTower.coherence'] - rw! (castMode:=.all) [h_r] - simp only [Fin.val_cast, Fin.val_castSucc] + rfl · have h_exp_eq : (↑j : ℕ) / 2 ^ (r - l - 1) = (↑j : ℕ) / 2 ^ prevDiff := congr_arg (fun d => (↑j : ℕ) / 2 ^ d) h_prevDiff.symm refine congr_arg₂ (· ^ ·)