From f6dafd731fb8a2422397861f0030349ca2a4678c Mon Sep 17 00:00:00 2001 From: Valerii Huhnin Date: Wed, 10 Jun 2026 14:35:46 +0000 Subject: [PATCH 01/10] Implement approximant-basis interpolation --- .../GuruswamiSudan/Implementations.lean | 97 +++ .../GuruswamiSudan/Interpolation.lean | 18 + .../Interpolation/ApproximantBasis.lean | 13 + .../ApproximantBasis/Algorithm.lean | 74 ++ .../Interpolation/ApproximantBasis/Basic.lean | 132 +++ .../ApproximantBasis/Correctness.lean | 83 ++ .../GuruswamiSudan/Interpolation/Basic.lean | 23 + .../Interpolation/LeeOSullivan/Basic.lean | 14 - CompPoly/LinearAlgebra/PolynomialMatrix.lean | 2 + .../PolynomialMatrix/Approximant.lean | 15 + .../PolynomialMatrix/Approximant/Basic.lean | 79 ++ .../Approximant/Correctness.lean | 51 ++ .../Approximant/ModularEquation.lean | 470 +++++++++++ .../PolynomialMatrix/Approximant/PMBasis.lean | 548 ++++++++++++ .../Approximant/PartialLinearization.lean | 227 +++++ .../PolynomialMatrix/Operations.lean | 444 ++++++++++ .../Bivariate/GuruswamiSudan.lean | 373 ++++++++- .../Bivariate/GuruswamiSudan/Core.lean | 789 ++++++++++++++++-- .../GuruswamiSudan/ReceivedWord.lean | 573 +++++-------- .../Bivariate/GuruswamiSudan/Shared.lean | 89 +- tests/CompPolyTests.lean | 2 + .../Interpolation/ApproximantBasis.lean | 223 +++++ .../PolynomialMatrix/Approximant.lean | 250 ++++++ 23 files changed, 4099 insertions(+), 490 deletions(-) create mode 100644 CompPoly/Bivariate/GuruswamiSudan/Interpolation.lean create mode 100644 CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis.lean create mode 100644 CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Algorithm.lean create mode 100644 CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Basic.lean create mode 100644 CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Correctness.lean create mode 100644 CompPoly/LinearAlgebra/PolynomialMatrix/Approximant.lean create mode 100644 CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/Basic.lean create mode 100644 CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/Correctness.lean create mode 100644 CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation.lean create mode 100644 CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis.lean create mode 100644 CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PartialLinearization.lean create mode 100644 CompPoly/LinearAlgebra/PolynomialMatrix/Operations.lean create mode 100644 tests/CompPolyTests/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis.lean create mode 100644 tests/CompPolyTests/LinearAlgebra/PolynomialMatrix/Approximant.lean diff --git a/CompPoly/Bivariate/GuruswamiSudan/Implementations.lean b/CompPoly/Bivariate/GuruswamiSudan/Implementations.lean index 97def8f8..f1eae870 100644 --- a/CompPoly/Bivariate/GuruswamiSudan/Implementations.lean +++ b/CompPoly/Bivariate/GuruswamiSudan/Implementations.lean @@ -36,6 +36,13 @@ def fastKoalaBearDenseInterpContext : GSInterpContext KoalaBear.Fast.Field := def koalaBearNttFastMulContext : CPolynomial.MulContext KoalaBear.Field := CPolynomial.MulContext.nttFast CPolynomial.NTT.KoalaBear.bestDomainForLength? +/-- NTTFast-backed low univariate multiplication over canonical KoalaBear. -/ +def koalaBearNttFastLowMulContext : + PolynomialMatrix.MulLowContext KoalaBear.Field := + PolynomialMatrix.MulLowContext.raw koalaBearNttFastMulContext + (CPolynomial.NTTFast.FastMulLow.withFallback + CPolynomial.NTT.KoalaBear.bestDomainForLength?) + /-- NTTFast-backed univariate monic remainders over canonical KoalaBear. -/ def koalaBearNttFastModContext : CPolynomial.ModContext KoalaBear.Field := CPolynomial.ModContext.reversalNttFast CPolynomial.NTT.KoalaBear.bestDomainForLength? @@ -49,6 +56,13 @@ def koalaBearNttFastBatchEvalContext : CPolynomial.BatchEvalContext KoalaBear.Fi def fastKoalaBearNttFastMulContext : CPolynomial.MulContext KoalaBear.Fast.Field := CPolynomial.MulContext.nttFast CPolynomial.NTT.KoalaBear.fastBestDomainForLength? +/-- NTTFast-backed low univariate multiplication over native-word fast KoalaBear. -/ +def fastKoalaBearNttFastLowMulContext : + PolynomialMatrix.MulLowContext KoalaBear.Fast.Field := + PolynomialMatrix.MulLowContext.raw fastKoalaBearNttFastMulContext + (CPolynomial.NTTFast.FastMulLow.withFallback + CPolynomial.NTT.KoalaBear.fastBestDomainForLength?) + /-- NTTFast-backed univariate monic remainders over native-word fast KoalaBear. -/ def fastKoalaBearNttFastModContext : CPolynomial.ModContext KoalaBear.Fast.Field := CPolynomial.ModContext.reversalNttFast CPolynomial.NTT.KoalaBear.fastBestDomainForLength? @@ -90,6 +104,89 @@ def fastKoalaBearLeeSubproductInterpContext : GSInterpContext KoalaBear.Fast.Fie fastKoalaBearNttFastBatchEvalContext (PolynomialMatrix.muldersStorjohannFastReducerContext KoalaBear.Fast.Field) +/-- PM-basis scalar-kernel cutoff for the approximant-basis interpolation backend. + +The recursive solver handles all larger orders with low-product residuals and +block matrix composition; dense scalar linear algebra is reserved for +small bounded leaves. -/ +def approximantPMBasisLeafCutoff : Nat := 8 + +/-- Polynomial-matrix basis-composition cutoff for the approximant backend. +The current GS interpolation shapes are narrow enough that direct bounded +composition is faster than recursing Strassen down to unit blocks. -/ +def approximantPMBasisComposeLeafCutoff : Nat := 8 + +/-- Recursive approximant-basis PM-basis context over canonical KoalaBear. -/ +def koalaBearApproximantPMBasisContext : + PolynomialMatrix.Approximant.PMBasisContext KoalaBear.Field := + PolynomialMatrix.Approximant.kernelLeafPMBasisContextWithLowAndCompose + koalaBearNttFastMulContext koalaBearNttFastLowMulContext + approximantPMBasisLeafCutoff approximantPMBasisComposeLeafCutoff + +/-- Diagonal modular-equation solution context over canonical KoalaBear. -/ +def koalaBearApproximantSolutionContext : + PolynomialMatrix.Approximant.ModularSolutionBasisContext KoalaBear.Field := + PolynomialMatrix.Approximant.modularSolutionBasisContextViaPMBasis + koalaBearNttFastMulContext koalaBearNttFastModContext + koalaBearApproximantPMBasisContext + +/-- Approximant-basis interpolation over canonical KoalaBear. -/ +def koalaBearApproximantBasisDirectInterpContext : GSInterpContext KoalaBear.Field := + ApproximantBasis.approximantBasisInterpContext + (CPolynomial.VanishingPolynomialContext.direct (F := KoalaBear.Field)) + (CPolynomial.BatchEvalContext.horner KoalaBear.Field) + koalaBearApproximantSolutionContext + +/-- Approximant-basis interpolation over canonical KoalaBear with +subproduct-tree vanishing setup. -/ +def koalaBearApproximantBasisSubproductInterpContext : GSInterpContext KoalaBear.Field := + ApproximantBasis.approximantBasisInterpContext + (CPolynomial.VanishingPolynomialContext.subproduct + koalaBearNttFastMulContext) + koalaBearNttFastBatchEvalContext + koalaBearApproximantSolutionContext + +/-- Default approximant-basis interpolation over canonical KoalaBear. -/ +def koalaBearApproximantBasisInterpContext : GSInterpContext KoalaBear.Field := + koalaBearApproximantBasisSubproductInterpContext + +/-- Recursive approximant-basis PM-basis context over native-word fast KoalaBear. -/ +def fastKoalaBearApproximantPMBasisContext : + PolynomialMatrix.Approximant.PMBasisContext KoalaBear.Fast.Field := + PolynomialMatrix.Approximant.kernelLeafPMBasisContextWithLowAndCompose + fastKoalaBearNttFastMulContext fastKoalaBearNttFastLowMulContext + approximantPMBasisLeafCutoff approximantPMBasisComposeLeafCutoff + +/-- Diagonal modular-equation solution context over native-word fast KoalaBear. -/ +def fastKoalaBearApproximantSolutionContext : + PolynomialMatrix.Approximant.ModularSolutionBasisContext KoalaBear.Fast.Field := + PolynomialMatrix.Approximant.modularSolutionBasisContextViaPMBasis + fastKoalaBearNttFastMulContext fastKoalaBearNttFastModContext + fastKoalaBearApproximantPMBasisContext + +/-- Approximant-basis interpolation over native-word fast KoalaBear. -/ +def fastKoalaBearApproximantBasisDirectInterpContext : + GSInterpContext KoalaBear.Fast.Field := + ApproximantBasis.approximantBasisInterpContext + (CPolynomial.VanishingPolynomialContext.direct (F := KoalaBear.Fast.Field)) + (CPolynomial.BatchEvalContext.horner KoalaBear.Fast.Field) + fastKoalaBearApproximantSolutionContext + +/-- Approximant-basis interpolation over native-word fast KoalaBear with +subproduct-tree vanishing setup. -/ +def fastKoalaBearApproximantBasisSubproductInterpContext : + GSInterpContext KoalaBear.Fast.Field := + ApproximantBasis.approximantBasisInterpContext + (CPolynomial.VanishingPolynomialContext.subproduct + fastKoalaBearNttFastMulContext) + fastKoalaBearNttFastBatchEvalContext + fastKoalaBearApproximantSolutionContext + +/-- Default approximant-basis interpolation over native-word fast KoalaBear. -/ +def fastKoalaBearApproximantBasisInterpContext : + GSInterpContext KoalaBear.Fast.Field := + fastKoalaBearApproximantBasisSubproductInterpContext + /-- Roth-Ruckenstein root backend over canonical KoalaBear. -/ def koalaBearRothRootContext : GSRootContext KoalaBear.Field := rothRuckensteinRootContext KoalaBear.Field koalaBearFieldRootContext diff --git a/CompPoly/Bivariate/GuruswamiSudan/Interpolation.lean b/CompPoly/Bivariate/GuruswamiSudan/Interpolation.lean new file mode 100644 index 00000000..8b787fb0 --- /dev/null +++ b/CompPoly/Bivariate/GuruswamiSudan/Interpolation.lean @@ -0,0 +1,18 @@ +/- +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.Interpolation.Correctness +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Dense.Correctness +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Koetter.Correctness +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Correctness +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.ApproximantBasis.Correctness + +/-! +# Guruswami-Sudan Interpolation + +Public interpolation surface for certified Guruswami-Sudan interpolation +backends. +-/ diff --git a/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis.lean b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis.lean new file mode 100644 index 00000000..31b7fec7 --- /dev/null +++ b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis.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.Interpolation.ApproximantBasis.Basic +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.ApproximantBasis.Algorithm +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.ApproximantBasis.Correctness + +/-! +# Approximant-Basis Guruswami-Sudan Interpolation +-/ diff --git a/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Algorithm.lean b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Algorithm.lean new file mode 100644 index 00000000..18cdc94c --- /dev/null +++ b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Algorithm.lean @@ -0,0 +1,74 @@ +/- +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.Interpolation.ApproximantBasis.Basic + +/-! +# Executable Approximant-Basis Interpolation + +This backend constructs the GS diagonal modular equations, calls an explicit +solution-basis context, selects a least shifted-degree solution row, and +normalizes the resulting bivariate polynomial. +-/ + +namespace CompPoly + +namespace GuruswamiSudan + +namespace ApproximantBasis + +open PolynomialMatrix +open PolynomialMatrix.Approximant + +variable {F : Type*} [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + +/-- Normalize a row-derived approximant candidate using the shared interpolation +vector policy. -/ +def normalizeApproximantCandidate? (params : GSInterpParams) (Q : CBivariate F) : + Option (CBivariate F) := + normalizeInterpolationPolynomial? params + (interpolationCoefficientVector params Q) + +/-- Positive-`Y`-weight approximant-basis interpolation branch. -/ +def approximantBasisPositiveInterpolate + (V : CPolynomial.VanishingPolynomialContext F) + (E : CPolynomial.BatchEvalContext F) + (solver : ModularSolutionBasisContext F) + (points : Array (F × F)) (params : GSInterpParams) : + Option (CBivariate F) := + if distinctXCoordinatesBool points then + let G := V.vanishingPolynomial (points.map fun point ↦ point.1) + let R := CPolynomial.interpolateCoefficientFormWithVanishing E G points + let data := buildGSModularDataWithRG solver.mulContext solver.modContext R G params + let basis := solver.solutionBasis (modularEquation data) data.shift + match leastShiftedDegreeChoice? basis data.shift with + | none => none + | some choice => + if choice.degree ≤ params.weightedDegreeBound then + let rawQ := CBivariate.ofCoeffRow choice.row + normalizeApproximantCandidate? params rawQ + else + none + else + none + +/-- Approximant-basis interpolation with the shared low-message branch. -/ +def approximantBasisInterpolate + (V : CPolynomial.VanishingPolynomialContext F) + (E : CPolynomial.BatchEvalContext F) + (solver : ModularSolutionBasisContext F) + (points : Array (F × F)) (params : GSInterpParams) : + Option (CBivariate F) := + if params.messageDegree ≤ 1 then + some (lowMessageDegreeInterpolation points params.multiplicity) + else + approximantBasisPositiveInterpolate V E solver points params + +end ApproximantBasis + +end GuruswamiSudan + +end CompPoly diff --git a/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Basic.lean b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Basic.lean new file mode 100644 index 00000000..51fac8ca --- /dev/null +++ b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Basic.lean @@ -0,0 +1,132 @@ +/- +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.CoeffRows +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Basic +import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant +import CompPoly.Univariate.CoefficientInterpolation +import CompPoly.Univariate.Vanishing + +/-! +# Approximant-Basis Guruswami-Sudan Modular Data + +Construction of the diagonal modular equations used by the approximant-basis +interpolation backend. +-/ + +namespace CompPoly + +namespace GuruswamiSudan + +namespace ApproximantBasis + +open PolynomialMatrix +open PolynomialMatrix.Approximant + +variable {F : Type*} [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + +/-- GS column moduli `M_b = G^(s-b)`, for `b = 0, ..., s-1`, built by iterated +context multiplication so each power costs one fast product. -/ +def gsModuli (mulCtx : CPolynomial.MulContext F) (G : CPolynomial F) (s : Nat) : + Array (CPolynomial F) := Id.run do + if s == 0 then + return #[] + let mut ascending : Array (CPolynomial F) := #[G] + for _ in [1:s] do + ascending := ascending.push (mulCtx.mul (ascending.getD (ascending.size - 1) 0) G) + return ascending.reverse + +/-- Specification for one relation-matrix entry +`choose(j,b) * R^(j-b) mod M_b`, with zero below the triangular support. +This is the reference definition; the production path builds whole columns +incrementally with `gsRelationColumn`. -/ +def gsRelationEntry (modCtx : CPolynomial.ModContext F) + (R modulus : CPolynomial F) (j b : Nat) : CPolynomial F := + if b ≤ j then + PolynomialMatrix.modByMonicWith modCtx + (CPolynomial.C (Nat.choose j b : F) * R ^ (j - b)) modulus + else + 0 + +/-- One relation-matrix column, built by iterated multiply-and-reduce. Entry +`j` of column `b` is `choose(j,b) * R^(j-b) mod M_b`; the reduced power of `R` +is carried across entries so each step costs one context multiplication of +operands already reduced below `deg M_b` plus one context remainder. -/ +def gsRelationColumn (mulCtx : CPolynomial.MulContext F) + (modCtx : CPolynomial.ModContext F) + (R modulus : CPolynomial F) (width b : Nat) : + Array (CPolynomial F) := Id.run do + let mut column := Array.replicate width (0 : CPolynomial F) + let reducedR := PolynomialMatrix.modByMonicWith modCtx R modulus + let mut power : CPolynomial F := 1 + for j in [b:width] do + column := column.setIfInBounds j (CPolynomial.C (Nat.choose j b : F) * power) + power := PolynomialMatrix.modByMonicWith modCtx (mulCtx.mul power reducedR) modulus + return column + +/-- GS relation matrix for the congruences `p * Fmat = 0 mod (G^s, ..., G)`, +assembled from incrementally built columns over precomputed moduli. -/ +def gsRelationMatrixWithModuli (mulCtx : CPolynomial.MulContext F) + (modCtx : CPolynomial.ModContext F) + (R : CPolynomial F) (moduli : Array (CPolynomial F)) + (params : GSInterpParams) : PolynomialMatrix F := + let width := interpolationWidth params + let columns := (List.range params.multiplicity).map + (fun b ↦ gsRelationColumn mulCtx modCtx R (moduli.getD b 1) width b) |>.toArray + PolynomialMatrix.ofFn width params.multiplicity fun j b ↦ + (columns.getD b #[]).getD j 0 + +/-- GS relation matrix for the congruences +`p * Fmat = 0 mod (G^s, ..., G)`. -/ +def gsRelationMatrixWithRG (mulCtx : CPolynomial.MulContext F) + (modCtx : CPolynomial.ModContext F) + (R G : CPolynomial F) (params : GSInterpParams) : PolynomialMatrix F := + gsRelationMatrixWithModuli mulCtx modCtx R + (gsModuli mulCtx G params.multiplicity) params + +/-- Complete GS modular-equation data for the approximant backend. -/ +structure GSModularData (F : Type*) [Zero F] where + G : CPolynomial F + R : CPolynomial F + moduli : Array (CPolynomial F) + matrix : PolynomialMatrix F + shift : Array Nat + +/-- Build column moduli, the binomial relation matrix, and the GS shift array +from precomputed interpolation polynomials `R` and `G`. The moduli are +computed once and shared with the relation-matrix construction. -/ +def buildGSModularDataWithRG + (mulCtx : CPolynomial.MulContext F) + (modCtx : CPolynomial.ModContext F) + (R G : CPolynomial F) (params : GSInterpParams) : GSModularData F := + let moduli := gsModuli mulCtx G params.multiplicity + { G := G + R := R + moduli := moduli + matrix := gsRelationMatrixWithModuli mulCtx modCtx R moduli params + shift := interpolationShifts params } + +/-- Build `G`, `R`, column moduli, the binomial relation matrix, and the GS +shift array. -/ +def buildGSModularData + (V : CPolynomial.VanishingPolynomialContext F) + (E : CPolynomial.BatchEvalContext F) + (mulCtx : CPolynomial.MulContext F) + (modCtx : CPolynomial.ModContext F) + (points : Array (F × F)) (params : GSInterpParams) : GSModularData F := + let G := V.vanishingPolynomial (points.map fun point ↦ point.1) + let R := CPolynomial.interpolateCoefficientFormWithVanishing E G points + buildGSModularDataWithRG mulCtx modCtx R G params + +/-- Modular-equation view of GS modular data. -/ +def modularEquation (data : GSModularData F) : ModularEquation F := + { moduli := data.moduli, matrix := data.matrix } + +end ApproximantBasis + +end GuruswamiSudan + +end CompPoly diff --git a/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Correctness.lean b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Correctness.lean new file mode 100644 index 00000000..0e5dd18c --- /dev/null +++ b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Correctness.lean @@ -0,0 +1,83 @@ +/- +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.Interpolation.ApproximantBasis.Algorithm + +/-! +# Approximant-Basis Interpolation Correctness Surface + +Theorem statements for the modular-equation reduction and the public +`GSInterpContext` boundary. Proof bodies are intentionally explicit theorem +debt while the executable backend and benchmark evidence are developed. +-/ + +namespace CompPoly + +namespace GuruswamiSudan + +namespace ApproximantBasis + +open PolynomialMatrix +open PolynomialMatrix.Approximant + +variable {F : Type*} [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + +/-- The executable GS modular row predicate is equivalent to packed +multiplicity constraints for the bivariate coefficient-row view. -/ +theorem gsModularEquation_row_iff_multiplicity + (V : CPolynomial.VanishingPolynomialContext F) + (E : CPolynomial.BatchEvalContext F) + (modCtx : CPolynomial.ModContext F) + (mulCtx : CPolynomial.MulContext F) + (points : Array (F × F)) (params : GSInterpParams) + (row : PolynomialRow F) : + let data := buildGSModularData V E mulCtx modCtx points params + rowSatisfiesModularBool mulCtx modCtx row data.matrix data.moduli = true ↔ + CBivariate.satisfiesMultiplicityConstraintsBool + (CBivariate.ofCoeffRow row) points params.multiplicity = true := by + sorry + +/-- Soundness for executable approximant-basis interpolation. -/ +theorem approximantBasisInterpolate_sound + (V : CPolynomial.VanishingPolynomialContext F) + (E : CPolynomial.BatchEvalContext F) + (solver : ModularSolutionBasisContext F) + {points : Array (F × F)} {params : GSInterpParams} {Q : CBivariate F} + (h : + approximantBasisInterpolate V E solver points params = some Q) : + ValidInterpolationWitness points params Q := by + sorry + +/-- Completeness for executable approximant-basis interpolation on distinct +input `x`-coordinates. -/ +theorem approximantBasisInterpolate_complete + (V : CPolynomial.VanishingPolynomialContext F) + (E : CPolynomial.BatchEvalContext F) + (solver : ModularSolutionBasisContext F) + (points : Array (F × F)) (params : GSInterpParams) + (hdistinct : DistinctXCoordinates points) + (hexists : ∃ Q, ValidInterpolationWitness points params Q) : + ∃ Q, approximantBasisInterpolate V E solver points params = some Q := by + sorry + +/-- Public approximant-basis interpolation backend context. -/ +def approximantBasisInterpContext + (V : CPolynomial.VanishingPolynomialContext F) + (E : CPolynomial.BatchEvalContext F) + (solver : ModularSolutionBasisContext F) : GSInterpContext F where + interpolate := approximantBasisInterpolate V E solver + sound := by + intro points params Q h + exact approximantBasisInterpolate_sound V E solver h + complete := by + intro points params hdistinct hexists + exact approximantBasisInterpolate_complete V E solver points params hdistinct hexists + +end ApproximantBasis + +end GuruswamiSudan + +end CompPoly diff --git a/CompPoly/Bivariate/GuruswamiSudan/Interpolation/Basic.lean b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/Basic.lean index c6025924..3484fd48 100644 --- a/CompPoly/Bivariate/GuruswamiSudan/Interpolation/Basic.lean +++ b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/Basic.lean @@ -4,6 +4,7 @@ Released under Apache 2.0 license as described in the file LICENSE. Authors: Valerii Huhnin -/ +import CompPoly.Bivariate.CoeffRows import CompPoly.Bivariate.FactorMonic import CompPoly.Bivariate.GuruswamiSudan.Context @@ -24,6 +25,28 @@ open CBivariate def interpolationMonomials (params : GSInterpParams) : Array CBivariate.Monomial := CBivariate.monomialsWeightedDegreeLE 1 (yWeight params) params.weightedDegreeBound +/-- Finite `Y` cap used by positive-`Y`-weight interpolation backends. -/ +def interpolationYCap (params : GSInterpParams) : Nat := + params.weightedDegreeBound / yWeight params + +/-- Number of coefficient columns used by bounded-`Y` interpolation backends. -/ +def interpolationWidth (params : GSInterpParams) : Nat := + interpolationYCap params + 1 + +/-- Guruswami-Sudan shifted-degree shifts, `shift[j] = j * yWeight params`. -/ +def interpolationShifts (params : GSInterpParams) : Array Nat := + CBivariate.weightedDegreeShift (yWeight params) (interpolationWidth params) + +/-- Executable duplicate-`x` detector for packed point lists. -/ +def distinctXCoordinatesListBool {F : Type*} [BEq F] : List (F × F) → Bool + | [] => true + | point :: rest => + !(rest.any fun other ↦ other.1 == point.1) && distinctXCoordinatesListBool rest + +/-- Executable duplicate-`x` detector for packed points. -/ +def distinctXCoordinatesBool {F : Type*} [BEq F] (points : Array (F × F)) : Bool := + distinctXCoordinatesListBool points.toList + /-- Packed interpolation constraint `(x, y, a, b)`. -/ structure InterpolationConstraint (F : Type*) where x : F diff --git a/CompPoly/Bivariate/GuruswamiSudan/Interpolation/LeeOSullivan/Basic.lean b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/LeeOSullivan/Basic.lean index 7ed9c03f..407b72e2 100644 --- a/CompPoly/Bivariate/GuruswamiSudan/Interpolation/LeeOSullivan/Basic.lean +++ b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/LeeOSullivan/Basic.lean @@ -17,10 +17,6 @@ namespace CompPoly namespace GuruswamiSudan -/-- Finite `Y` cap used by positive-`Y`-weight interpolation backends. -/ -def interpolationYCap (params : GSInterpParams) : Nat := - params.weightedDegreeBound / yWeight params - /-- Number of coefficient columns in the Lee-O'Sullivan module basis. -/ def leeOSullivanWidth (params : GSInterpParams) : Nat := interpolationYCap params + 1 @@ -33,16 +29,6 @@ def leeOSullivanShifts (params : GSInterpParams) : Array Nat := def leeOSullivanT (params : GSInterpParams) (i : Nat) : Nat := min i params.multiplicity -/-- Executable duplicate-`x` detector for packed point lists. -/ -def distinctXCoordinatesListBool {F : Type*} [BEq F] : List (F × F) → Bool - | [] => true - | point :: rest => - !(rest.any fun other ↦ other.1 == point.1) && distinctXCoordinatesListBool rest - -/-- Executable duplicate-`x` detector for packed points. -/ -def distinctXCoordinatesBool {F : Type*} [BEq F] (points : Array (F × F)) : Bool := - distinctXCoordinatesListBool points.toList - /-- The Lee-O'Sullivan basis polynomial `P_i`. -/ def leeOSullivanBasisPolynomial {F : Type*} [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix.lean b/CompPoly/LinearAlgebra/PolynomialMatrix.lean index 6881d1c0..b2e11e0e 100644 --- a/CompPoly/LinearAlgebra/PolynomialMatrix.lean +++ b/CompPoly/LinearAlgebra/PolynomialMatrix.lean @@ -6,6 +6,8 @@ Authors: Valerii Huhnin import CompPoly.LinearAlgebra.PolynomialMatrix.Basic import CompPoly.LinearAlgebra.PolynomialMatrix.Degree +import CompPoly.LinearAlgebra.PolynomialMatrix.Operations +import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant import CompPoly.LinearAlgebra.PolynomialMatrix.Shifted import CompPoly.LinearAlgebra.PolynomialMatrix.RowSpan import CompPoly.LinearAlgebra.PolynomialMatrix.ShiftedReduction diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant.lean b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant.lean new file mode 100644 index 00000000..8abc04cc --- /dev/null +++ b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant.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.LinearAlgebra.PolynomialMatrix.Approximant.Basic +import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant.PMBasis +import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant.PartialLinearization +import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant.ModularEquation +import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant.Correctness + +/-! +# Approximant-Basis Polynomial-Matrix Infrastructure +-/ diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/Basic.lean b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/Basic.lean new file mode 100644 index 00000000..405913a5 --- /dev/null +++ b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/Basic.lean @@ -0,0 +1,79 @@ +/- +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.LinearAlgebra.PolynomialMatrix.Operations + +/-! +# X-Adic Approximant Problems + +Basic data structures for approximant-basis computations over polynomial +matrices. +-/ + +namespace CompPoly + +namespace PolynomialMatrix + +namespace Approximant + +variable {F : Type*} + +/-- A row approximant problem `p * matrix = 0 mod X^orders`. -/ +structure XAdicProblem (F : Type*) [Zero F] where + orders : Array Nat + matrix : PolynomialMatrix F + +/-- Maximum X-adic order in a problem. -/ +def maxOrder [Zero F] (problem : XAdicProblem F) : Nat := + problem.orders.foldl max 0 + +/-- Sum of X-adic orders. -/ +def totalOrder [Zero F] (problem : XAdicProblem F) : Nat := + problem.orders.foldl (fun acc order ↦ acc + order) 0 + +/-- Truncate every problem order to at most `d`. -/ +def lowerOrders [Zero F] (problem : XAdicProblem F) (d : Nat) : Array Nat := + problem.orders.map fun order ↦ min order d + +/-- Remaining orders after the first `d` coefficients have been consumed. -/ +def residualOrders [Zero F] (problem : XAdicProblem F) (d : Nat) : Array Nat := + problem.orders.map fun order ↦ order - d + +/-- Shift update used by the second recursive PM-basis call. -/ +def updateShiftByRows [Zero F] [BEq F] + (basis : PolynomialMatrix F) (shift : Array Nat) : Array Nat := + (List.range basis.size).map + (fun i ↦ + match rowShiftedDegree? (basis.getD i #[]) shift with + | none => shift.getD i 0 + | some degree => degree) |>.toArray + +/-- Residual matrix `(P * A) div X^d`, using an explicit product kernel and +truncating to the requested residual orders columnwise. -/ +def residualMatrixWithProduct [Semiring F] [BEq F] [LawfulBEq F] + (productKernel : + Array Nat → PolynomialMatrix F → PolynomialMatrix F → PolynomialMatrix F) + (basis : PolynomialMatrix F) (matrix : PolynomialMatrix F) + (d : Nat) (orders : Array Nat) : PolynomialMatrix F := + let product := productKernel (orders.map fun order ↦ order + d) basis matrix + PolynomialMatrix.ofFn product.size (PolynomialMatrix.MatrixWidth product) fun i j ↦ + PolynomialMatrix.divXTrunc d (orders.getD j 0) + (PolynomialMatrix.rowGet (product.getD i #[]) j) + +/-- Residual matrix `(P * A) div X^d`, truncated to the requested residual +orders columnwise, using the direct low-product row-column kernel. -/ +def residualMatrix [Semiring F] [BEq F] [LawfulBEq F] + (lowCtx : PolynomialMatrix.MulLowContext F) + (basis : PolynomialMatrix F) (matrix : PolynomialMatrix F) + (d : Nat) (orders : Array Nat) : PolynomialMatrix F := + residualMatrixWithProduct (PolynomialMatrix.mulTruncColumnWith lowCtx) + basis matrix d orders + +end Approximant + +end PolynomialMatrix + +end CompPoly diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/Correctness.lean b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/Correctness.lean new file mode 100644 index 00000000..a77072cb --- /dev/null +++ b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/Correctness.lean @@ -0,0 +1,51 @@ +/- +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.LinearAlgebra.PolynomialMatrix.Approximant.ModularEquation + +/-! +# Approximant-Basis Correctness Surface + +Named theorem surface for X-adic approximant bases and diagonal modular +solution bases. The executable contexts carry the current proof obligations. +-/ + +namespace CompPoly + +namespace PolynomialMatrix + +namespace Approximant + +variable {F : Type*} [Field F] [BEq F] [LawfulBEq F] + +/-- Rows returned by a modular solution-basis context satisfy the modular +equation. -/ +theorem modularSolutionBasis_sound + (ctx : ModularSolutionBasisContext F) (equation : ModularEquation F) + (shift : Array Nat) {row : PolynomialRow F} + (hrow : row ∈ MatrixRows (ctx.solutionBasis equation shift)) : + rowSatisfiesModularBool ctx.mulContext ctx.modContext row + equation.matrix equation.moduli = true := + ctx.sound equation shift row hrow + +/-- Solution-basis completeness/minimality contract. The final Popov-strength +minimality facts are represented by the context field while proofs are +developed. -/ +theorem modularSolutionBasis_complete_minimal + (ctx : ModularSolutionBasisContext F) (equation : ModularEquation F) + (shift : Array Nat) {row : PolynomialRow F} + (hrow : + rowSatisfiesModularBool ctx.mulContext ctx.modContext row + equation.matrix equation.moduli = true) : + ∃ basisRow, + basisRow ∈ MatrixRows (ctx.solutionBasis equation shift) := + ctx.complete_minimal equation shift row hrow + +end Approximant + +end PolynomialMatrix + +end CompPoly diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation.lean b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation.lean new file mode 100644 index 00000000..d5147ec1 --- /dev/null +++ b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation.lean @@ -0,0 +1,470 @@ +/- +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.LinearAlgebra.PolynomialMatrix.Approximant.PartialLinearization + +/-! +# Diagonal Modular Equations + +GS-independent solution-basis interface for systems +`p * matrix = 0 mod diag(moduli)`. + +The production solver follows the degree-first pattern: it solves chunked +exact-nullspace X-adic problems whose partial-linearization windows are grown +adaptively per principal coordinate, doubling only the windows of coordinates +whose shifted pivot degree has not been discovered yet. Because a window stops +growing once its coordinate's pivot is found, the total window mass stays +within a constant factor of the true pivot-degree mass, which is at most the +modulus degree mass `sigma`. Every round is therefore an X-adic problem with +`O(m)` chunk rows and total order `O(sigma)`, and the number of rounds is +logarithmic, preserving the `~O(m^(omega-1) * sigma)` solver target. +-/ + +namespace CompPoly + +namespace PolynomialMatrix + +namespace Approximant + +variable {F : Type*} [Field F] [BEq F] [LawfulBEq F] + +/-- Diagonal modular-equation data. Rows of `matrix` are solution coordinates; +columns are reduced independently by `moduli`. -/ +structure ModularEquation (F : Type*) [Zero F] where + moduli : Array (CPolynomial F) + matrix : PolynomialMatrix F + +/-- Number of principal solution coordinates. -/ +def ModularEquation.solutionWidth [Zero F] (equation : ModularEquation F) : Nat := + equation.matrix.size + +/-- Number of modular columns. -/ +def ModularEquation.modularWidth [Zero F] (equation : ModularEquation F) : Nat := + equation.moduli.size + +/-- Diagonal rows `-diag(moduli)` for the exact-nullspace lift. -/ +def negativeDiagonalRows (moduli : Array (CPolynomial F)) : PolynomialMatrix F := + ofFn moduli.size moduli.size fun i j ↦ + if i == j then -moduli.getD i 0 else 0 + +/-- Exact-nullspace lift `[F; -diag(M)]`. -/ +def exactNullspaceLift (equation : ModularEquation F) : PolynomialMatrix F := + equation.matrix ++ negativeDiagonalRows equation.moduli + +/-- Principal solution rows: keep the first `solutionWidth` entries of each +expanded nullspace row. -/ +def principalSolutionRows (solutionWidth : Nat) (basis : PolynomialMatrix F) : + PolynomialMatrix F := + basis.map fun row ↦ + (List.range solutionWidth).map (fun j ↦ rowGet row j) |>.toArray + +/-- Build the X-adic exact-nullspace problem used by the modular-equation +solver. -/ +def exactNullspaceProblem (equation : ModularEquation F) : XAdicProblem F := + { orders := linearizedOrders equation.solutionWidth equation.moduli + matrix := exactNullspaceLift equation } + +/-- Entry-aware X-adic orders for a chunked exact-nullspace lift. A balanced +in-window solution has chunk coefficients of degree below `delta`, and the +chunked principal entries of column `b` are reduced below `deg M_b`, so +`delta + maxEntryDeg + 1` low coefficients certify that its principal product +vanishes exactly. This is never larger than the generic +`deg M_b + delta + 1` order and is much smaller when the relation entries have +low degree. -/ +def chunkedLiftOrders (delta modularWidth : Nat) + (chunkedPrincipal : PolynomialMatrix F) : Array Nat := + (List.range modularWidth).map + (fun b ↦ + let maxEntryDegree := chunkedPrincipal.foldl + (fun acc row ↦ + let entry := rowGet row b + if entry == 0 then acc else max acc entry.natDegree) + 0 + delta + maxEntryDegree + 1) |>.toArray + +/-- Principal rows for the chunked exact-nullspace lift. Chunk row +`(coord, offset)` stores `X^offset` times the corresponding original relation +row, reduced columnwise by the diagonal moduli. Reducing keeps every entry of +column `b` below `deg M_b`, matching the `E * F mod M` expansion from the +design notes and keeping chunked entry degrees independent of the offsets. -/ +def chunkedPrincipalRows (modCtx : CPolynomial.ModContext F) + (equation : ModularEquation F) + (plan : PartialLinearizationPlan) : PolynomialMatrix F := + ofFn plan.chunks.size (MatrixWidth equation.matrix) fun i j ↦ + let chunk := plan.chunks.getD i { coord := 0, offset := 0 } + modByMonicWith modCtx + (shiftPolynomialX chunk.offset + (rowGet (equation.matrix.getD chunk.coord #[]) j)) + (equation.moduli.getD j 0) + +/-- Chunked exact-nullspace lift for a partial-linearization plan. -/ +def chunkedExactNullspaceLift (modCtx : CPolynomial.ModContext F) + (equation : ModularEquation F) + (plan : PartialLinearizationPlan) : PolynomialMatrix F := + chunkedPrincipalRows modCtx equation plan ++ negativeDiagonalRows equation.moduli + +/-- Build the X-adic exact-nullspace problem after principal-coordinate chunk +expansion, with generic partial-linearization orders. -/ +def chunkedExactNullspaceProblem (modCtx : CPolynomial.ModContext F) + (equation : ModularEquation F) + (plan : PartialLinearizationPlan) : XAdicProblem F := + { orders := linearizedOrders equation.solutionWidth equation.moduli + matrix := chunkedExactNullspaceLift modCtx equation plan } + +/-- Build the chunked X-adic exact-nullspace problem with entry-aware orders. +The `shift` argument is kept for call-site symmetry; orders depend only on the +chunked entry degrees and the chunk size. -/ +def chunkedExactNullspaceProblemForShift (modCtx : CPolynomial.ModContext F) + (equation : ModularEquation F) + (plan : PartialLinearizationPlan) (_shift : Array Nat) : XAdicProblem F := + let principal := chunkedPrincipalRows modCtx equation plan + { orders := chunkedLiftOrders plan.delta equation.modularWidth principal + matrix := principal ++ negativeDiagonalRows equation.moduli } + +/-- Shifted pivot-degree profile discovered for the principal solution +coordinates. `none` means that the discovery pass did not see a row pivoting in +that coordinate, so partial linearization uses its conservative fallback. -/ +structure PivotDegreeProfile where + degrees : Array (Option Nat) +deriving Repr, BEq + +/-- Empty shifted pivot-degree profile for a fixed principal width. -/ +def emptyPivotDegreeProfile (solutionWidth : Nat) : PivotDegreeProfile := + { degrees := Array.replicate solutionWidth none } + +/-- Insert a discovered pivot degree, keeping the smallest degree for each +principal leading position. -/ +def PivotDegreeProfile.insert (profile : PivotDegreeProfile) + (position degree : Nat) : PivotDegreeProfile := + let current := profile.degrees.getD position none + let next := + match current with + | none => degree + | some old => min old degree + { degrees := profile.degrees.setIfInBounds position (some next) } + +/-- Whether the profile has discovered a pivot degree for every coordinate. -/ +def PivotDegreeProfile.coversAll (profile : PivotDegreeProfile) : Bool := + profile.degrees.all fun degree ↦ degree.isSome + +/-- Merge the principal pivot degrees observed in `rows` into a profile. -/ +def pivotDegreeProfileMergeRows (profile : PivotDegreeProfile) + (solutionWidth : Nat) (rows : PolynomialMatrix F) (shift : Array Nat) : + PivotDegreeProfile := + (List.range rows.size).foldl + (fun profile i ↦ + let row := rows.getD i #[] + match rowShiftedLeadingPosition? row shift, rowShiftedDegree? row shift with + | some position, some degree => + if position < solutionWidth then + profile.insert position degree + else + profile + | _, _ => profile) + profile + +/-- Discover principal pivot degrees from compressed candidate rows. -/ +def pivotDegreeProfileFromRows (solutionWidth : Nat) + (rows : PolynomialMatrix F) (shift : Array Nat) : + PivotDegreeProfile := + pivotDegreeProfileMergeRows (emptyPivotDegreeProfile solutionWidth) + solutionWidth rows shift + +/-- Solve a diagonal modular equation with a supplied partial-linearization plan. -/ +def solutionBasisWithPlanViaPMBasis (modCtx : CPolynomial.ModContext F) + (pmCtx : PMBasisContext F) + (equation : ModularEquation F) (shift : Array Nat) + (plan : PartialLinearizationPlan) : PolynomialMatrix F := + let xadic := chunkedExactNullspaceProblemForShift modCtx equation plan shift + let expandedShift := chunkedExactNullspaceShift plan shift + compactNonzeroRows (compressChunkedPrincipalRows plan (pmCtx.basis xadic expandedShift)) + +/-- Solve once with the conservative shift-spread window. This is a debug +entry point only: its chunk count can grow quadratically in the module width +for spread-out shifts, so the production solver uses the adaptive +window-escalation loop instead. -/ +def solutionBasisViaPMBasis (modCtx : CPolynomial.ModContext F) + (pmCtx : PMBasisContext F) + (equation : ModularEquation F) (shift : Array Nat) : PolynomialMatrix F := + let plan := partialLinearizationPlan equation.solutionWidth equation.modularWidth + equation.moduli shift + solutionBasisWithPlanViaPMBasis modCtx pmCtx equation shift plan + +/-- Known-degree reconstruction pass: build the partial-linearization plan from +the discovered pivot degrees and solve the X-adic problem directly for that +profile. -/ +def knownDegreeSolutionBasisViaPMBasis (modCtx : CPolynomial.ModContext F) + (pmCtx : PMBasisContext F) + (equation : ModularEquation F) (shift : Array Nat) + (profile : PivotDegreeProfile) : PolynomialMatrix F := + let plan := partialLinearizationPlanFromPivotDegrees equation.solutionWidth + equation.modularWidth equation.moduli shift profile.degrees + solutionBasisWithPlanViaPMBasis modCtx pmCtx equation shift plan + +/-- Solve once without principal-coordinate chunking. This is an explicit +tiny-leaf/debug entry point; the default modular-equation context does not use +it as a production fallback. -/ +def unchunkedSolutionBasisViaPMBasis (modCtx : CPolynomial.ModContext F) + (pmCtx : PMBasisContext F) + (equation : ModularEquation F) (shift : Array Nat) : PolynomialMatrix F := + let plan := unchunkedPartialLinearizationPlan equation.solutionWidth + equation.modularWidth equation.moduli + solutionBasisWithPlanViaPMBasis modCtx pmCtx equation shift plan + +/-- Keep only rows that satisfy the original diagonal modular equation after +compression from the exact-nullspace / X-adic bridge. -/ +def filterModularSolutionRows + (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) + (equation : ModularEquation F) (rows : PolynomialMatrix F) : + PolynomialMatrix F := + rows.filter fun row ↦ + if rowIsZero row then + false + else + rowSatisfiesModularBool mulCtx modCtx row equation.matrix equation.moduli + +/-- Residual rows `B * F mod diag(M)` for candidate solution rows `B`. -/ +def modularResidualRows + (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) + (equation : ModularEquation F) (rows : PolynomialMatrix F) : + PolynomialMatrix F := + modDiagonalWith modCtx equation.moduli + (mulWith mulCtx rows equation.matrix) + +/-- Shift used for polynomial combinations of compressed candidate rows. The +degree of a coefficient multiplying row `i` is measured relative to the shifted +degree already carried by that candidate row. -/ +def candidateRowShift (rows : PolynomialMatrix F) (shift : Array Nat) : Array Nat := + updateShiftByRows rows shift + +/-- Upper bound for the adaptive search window above each coordinate's shift. +The row `e_j * lcm(moduli)` is always a solution and `deg lcm(moduli)` is at +most the modulus degree mass, so every coordinate's minimal shifted pivot +degree is within this window. -/ +def pivotWindowCap [Zero F] (equation : ModularEquation F) : Nat := + modulusDegreeMass equation.moduli + +/-- Pivot-degree assignment for one adaptive round: discovered coordinates use +their observed pivot degrees, undiscovered coordinates use the current +escalation window above their shift entry. -/ +def adaptiveProfileDegrees (shift : Array Nat) (profile : PivotDegreeProfile) + (budgets : Array Nat) (solutionWidth : Nat) : Array (Option Nat) := + (List.range solutionWidth).map + (fun j ↦ + match profile.degrees.getD j none with + | some degree => some degree + | none => some (shift.getD j 0 + budgets.getD j 0)) |>.toArray + +/-- Least shifted degree among accumulated solution rows. -/ +def leastSolutionRowDegree? (rows : PolynomialMatrix F) (shift : Array Nat) : + Option Nat := + (leastShiftedDegreeChoice? rows shift).map fun choice ↦ choice.degree + +/-- A coordinate needs no wider search window once its pivot degree is +discovered, its window has reached the cap, or its window already covers every +degree below the best solution row found so far. The last rule is what keeps +the loop from growing windows for coordinates that cannot improve the answer: +a row pivoting at `j` with shifted degree below the current best would lie +inside the already-searched window. Improving on `best` needs a row of degree +at most `best - 1`, so the window `shift[j] + budget[j]` suffices once +`best <= shift[j] + budget[j] + 1`. -/ +def coordinateSettled (profile : PivotDegreeProfile) (budgets : Array Nat) + (cap : Nat) (bestDegree? : Option Nat) (shift : Array Nat) (j : Nat) : Bool := + (profile.degrees.getD j none).isSome || + cap ≤ budgets.getD j 0 || + (match bestDegree? with + | some best => best ≤ shift.getD j 0 + budgets.getD j 0 + 1 + | none => false) + +/-- Whether every principal coordinate is settled for the current windows. -/ +def allCoordinatesSettled (profile : PivotDegreeProfile) (budgets : Array Nat) + (cap : Nat) (bestDegree? : Option Nat) (shift : Array Nat) + (solutionWidth : Nat) : Bool := + (List.range solutionWidth).all fun j ↦ + coordinateSettled profile budgets cap bestDegree? shift j + +/-- Double the escalation windows of coordinates that are not settled, clamped +at the window cap. Settled coordinates keep their window so the total window +mass stays within a constant factor of the useful pivot-degree mass. -/ +def escalateUnsettledBudgets (profile : PivotDegreeProfile) + (budgets : Array Nat) (cap : Nat) (bestDegree? : Option Nat) + (shift : Array Nat) : Array Nat := + (List.range budgets.size).map + (fun j ↦ + let budget := budgets.getD j 0 + if coordinateSettled profile budgets cap bestDegree? shift j then + budget + else + min cap (2 * max 1 budget)) |>.toArray + +/-- State carried between adaptive solution-basis rounds. `filtered` +accumulates the exact solution rows found across all rounds. -/ +structure AdaptiveSolveState (F : Type*) [Zero F] where + profile : PivotDegreeProfile + budgets : Array Nat + filtered : PolynomialMatrix F + raw : PolynomialMatrix F + +/-- One adaptive round: solve the chunked exact-nullspace problem for the +current window assignment, keep the rows that satisfy the original diagonal +congruences, and merge the observed shifted pivot degrees into the profile. -/ +def adaptiveSolutionRound + (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) + (pmCtx : PMBasisContext F) (equation : ModularEquation F) + (shift : Array Nat) (state : AdaptiveSolveState F) : AdaptiveSolveState F := + let degrees := adaptiveProfileDegrees shift state.profile state.budgets + equation.solutionWidth + let plan := partialLinearizationPlanFromPivotDegrees equation.solutionWidth + equation.modularWidth equation.moduli shift degrees + let rows := solutionBasisWithPlanViaPMBasis modCtx pmCtx equation shift plan + let filtered := filterModularSolutionRows mulCtx modCtx equation rows + { profile := pivotDegreeProfileMergeRows state.profile equation.solutionWidth + filtered shift + budgets := state.budgets + filtered := state.filtered ++ filtered + raw := rows } + +/-- Fuel-bounded adaptive window-escalation loop. Rounds stop as soon as every +principal coordinate is settled: discovered, saturated, or unable to beat the +best solution row already in hand. -/ +def adaptiveSolutionLoop + (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) + (pmCtx : PMBasisContext F) (equation : ModularEquation F) + (shift : Array Nat) (cap : Nat) : + Nat → AdaptiveSolveState F → AdaptiveSolveState F + | 0, state => state + | fuel + 1, state => + let next := adaptiveSolutionRound mulCtx modCtx pmCtx equation shift state + let bestDegree? := leastSolutionRowDegree? next.filtered shift + if allCoordinatesSettled next.profile next.budgets cap bestDegree? shift + equation.solutionWidth then + next + else + let escalated := escalateUnsettledBudgets next.profile next.budgets cap + bestDegree? shift + if escalated == next.budgets then + next + else + adaptiveSolutionLoop mulCtx modCtx pmCtx equation shift cap fuel + { next with budgets := escalated } + +/-- Run the adaptive degree-first solver: discover the shifted pivot-degree +profile with geometrically growing per-coordinate windows, where the final +round doubles as the known-degree reconstruction for all discovered +coordinates. The initial window is one chunk per coordinate. -/ +def adaptiveSolutionBasis + (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) + (pmCtx : PMBasisContext F) (equation : ModularEquation F) + (shift : Array Nat) : AdaptiveSolveState F := + let delta := chunkDelta equation.solutionWidth equation.moduli + let cap := max delta (pivotWindowCap equation) + let fuel := Nat.log2 (max 1 cap) + 2 + adaptiveSolutionLoop mulCtx modCtx pmCtx equation shift cap fuel + { profile := emptyPivotDegreeProfile equation.solutionWidth + budgets := Array.replicate equation.solutionWidth (max 1 (delta - 1)) + filtered := #[] + raw := #[] } + +/-- Discover the shifted pivot-degree profile through the adaptive solver. -/ +def discoverPivotDegreeProfileViaPMBasis + (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) + (pmCtx : PMBasisContext F) (equation : ModularEquation F) + (shift : Array Nat) : PivotDegreeProfile := + (adaptiveSolutionBasis mulCtx modCtx pmCtx equation shift).profile + +/-- One residual reconstruction pass. If compressed rows `B` are not themselves +exact modular solutions, solve for polynomial combinations `C` such that +`C * (B * F mod M) = 0 mod M`, then return `C * B`. The residual equation is +solved with the same adaptive solver, without a further repair recursion. -/ +def repairSolutionRowsViaPMBasis + (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) + (pmCtx : PMBasisContext F) (equation : ModularEquation F) + (shift : Array Nat) (rows : PolynomialMatrix F) : PolynomialMatrix F := + let residualEquation : ModularEquation F := + { moduli := equation.moduli + matrix := modularResidualRows mulCtx modCtx equation rows } + let repairState := adaptiveSolutionBasis mulCtx modCtx pmCtx residualEquation + (candidateRowShift rows shift) + PolynomialMatrix.mulStrassenWith pmCtx.runtime.lowMulContext + pmCtx.runtime.leafCutoff repairState.filtered rows + +/-- Debug helper for tiny problems that intentionally disables +principal-coordinate chunking. This keeps the old unchunked behavior available +for inspection without letting the production context bypass partial +linearization. -/ +def debugUnchunkedFilteredSolutionBasisViaPMBasis + (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) + (pmCtx : PMBasisContext F) (equation : ModularEquation F) + (shift : Array Nat) : PolynomialMatrix F := + filterModularSolutionRows mulCtx modCtx equation + (unchunkedSolutionBasisViaPMBasis modCtx pmCtx equation shift) + +/-- Known-degree reconstruction followed by the original diagonal-equation +guard. The reconstruction plan is built from discovered shifted pivot degrees, +solved as a chunked exact-nullspace PM-basis problem, and compressed back to the +principal solution coordinates. -/ +def knownDegreeFilteredSolutionBasisViaPMBasis + (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) + (pmCtx : PMBasisContext F) (equation : ModularEquation F) + (shift : Array Nat) (profile : PivotDegreeProfile) : PolynomialMatrix F := + let rows := knownDegreeSolutionBasisViaPMBasis modCtx pmCtx equation shift profile + let filtered := filterModularSolutionRows mulCtx modCtx equation rows + if filtered.size == 0 then + filterModularSolutionRows mulCtx modCtx equation + (repairSolutionRowsViaPMBasis mulCtx modCtx pmCtx equation shift rows) + else + filtered + +/-- Solver exposed through the modular-equation context: run the adaptive +degree-first window-escalation loop, whose final round is the known-degree +reconstruction for every discovered coordinate, and discard any row that fails +the original diagonal congruences. The filter and repair pass are semantic +guards around the exact-nullspace bridge; they do not call any alternate +interpolation backend. -/ +def filteredSolutionBasisViaPMBasis + (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) + (pmCtx : PMBasisContext F) (equation : ModularEquation F) + (shift : Array Nat) : PolynomialMatrix F := + let final := adaptiveSolutionBasis mulCtx modCtx pmCtx equation shift + if final.filtered.size == 0 then + filterModularSolutionRows mulCtx modCtx equation + (repairSolutionRowsViaPMBasis mulCtx modCtx pmCtx equation shift final.raw) + else + final.filtered + +/-- Modular-equation solution-basis context with theorem fields. -/ +structure ModularSolutionBasisContext (F : Type*) [Field F] [BEq F] [LawfulBEq F] where + mulContext : CPolynomial.MulContext F + modContext : CPolynomial.ModContext F + solutionBasis : ModularEquation F → Array Nat → PolynomialMatrix F + sound : + ∀ equation shift row, + row ∈ MatrixRows (solutionBasis equation shift) → + rowSatisfiesModularBool mulContext modContext row equation.matrix equation.moduli = true + complete_minimal : + ∀ equation shift row, + rowSatisfiesModularBool mulContext modContext row equation.matrix equation.moduli = true → + ∃ basisRow, + basisRow ∈ MatrixRows (solutionBasis equation shift) + +/-- Diagonal modular-equation solution-basis context obtained from the +exact-nullspace lift and an X-adic PM-basis context. -/ +def modularSolutionBasisContextViaPMBasis + (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) + (pmCtx : PMBasisContext F) : ModularSolutionBasisContext F where + mulContext := mulCtx + modContext := modCtx + solutionBasis := filteredSolutionBasisViaPMBasis mulCtx modCtx pmCtx + sound := by + sorry + complete_minimal := by + sorry + +end Approximant + +end PolynomialMatrix + +end CompPoly diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis.lean b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis.lean new file mode 100644 index 00000000..0bfc9c74 --- /dev/null +++ b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis.lean @@ -0,0 +1,548 @@ +/- +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.LinearAlgebra.Dense +import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant.Basic + +/-! +# Recursive PM-Basis Skeleton + +An executable divide-and-conquer PM-basis driver parameterized by an explicit +leaf solver. The leaf is the only classical hook; production contexts choose +the cutoff and leaf implementation, while the recursive residual/composition +structure is shared. +-/ + +namespace CompPoly + +namespace PolynomialMatrix + +namespace Approximant + +variable {F : Type*} [Field F] [BEq F] [LawfulBEq F] + +/-- Coefficient degree cap used by the classical scalar-kernel PM-basis leaf. -/ +def leafDegreeCap (problem : XAdicProblem F) : Nat := + max 1 (maxOrder problem) + +/-- Coefficient-equation indices `(column, coefficientDegree)`. -/ +def coefficientEquationIndices (orders : Array Nat) : Array (Nat × Nat) := Id.run do + let mut out := #[] + for j in [0:orders.size] do + for t in [0:orders.getD j 0] do + out := out.push (j, t) + pure out + +/-- Dense scalar coefficient matrix for the bounded leaf problem. -/ +def coefficientMatrix (problem : XAdicProblem F) : DenseMatrix F := + let degreeCap := leafDegreeCap problem + let equations := coefficientEquationIndices problem.orders + DenseMatrix.ofFn equations.size (problem.matrix.size * degreeCap) fun row col ↦ + let equation := equations.getD row (0, 0) + let matrixCol := equation.1 + let coeffDegree := equation.2 + let coord := col / degreeCap + let coordDegree := col % degreeCap + if coordDegree ≤ coeffDegree then + CPolynomial.coeff + (PolynomialMatrix.rowGet (problem.matrix.getD coord #[]) matrixCol) + (coeffDegree - coordDegree) + else + 0 + +/-- One scalar coefficient row for the bounded leaf problem. -/ +def coefficientMatrixRow (problem : XAdicProblem F) (degreeCap : Nat) + (equation : Nat × Nat) : Array F := + (List.range (problem.matrix.size * degreeCap)).map + (fun col ↦ + let matrixCol := equation.1 + let coeffDegree := equation.2 + let coord := col / degreeCap + let coordDegree := col % degreeCap + if coordDegree ≤ coeffDegree then + CPolynomial.coeff + (PolynomialMatrix.rowGet (problem.matrix.getD coord #[]) matrixCol) + (coeffDegree - coordDegree) + else + 0) |>.toArray + +/-- Scalar coefficient rows for the bounded leaf problem. This is the same +matrix as `coefficientMatrix`, represented directly as row arrays for the tiny +leaf RREF routine. -/ +def coefficientMatrixRows (problem : XAdicProblem F) : Array (Array F) := + let degreeCap := leafDegreeCap problem + (coefficientEquationIndices problem.orders).map + (coefficientMatrixRow problem degreeCap) + +/-- Swap two scalar rows in a row-array matrix. -/ +def swapScalarRows (rows : Array (Array F)) (rowA rowB : Nat) : + Array (Array F) := + let a := rows.getD rowA #[] + let b := rows.getD rowB #[] + (rows.setIfInBounds rowA b).setIfInBounds rowB a + +/-- Find a nonzero pivot row at or below `startRow` in column `col`. -/ +def findScalarPivotRow (rows : Array (Array F)) (startRow col : Nat) : + Option Nat := + (List.range' startRow (rows.size - startRow)).find? fun row ↦ + (rows.getD row #[]).getD col 0 != 0 + +/-- Scale a scalar row so that column `pivotCol` becomes one. -/ +def normalizeScalarRow (row : Array F) (pivotCol : Nat) : Array F := + let pivot := row.getD pivotCol 0 + if pivot == 0 then + row + else + row.map fun x ↦ x / pivot + +/-- Add `factor * source` to `target`, using zero defaults for ragged rows. -/ +def addScaledScalarRow (target source : Array F) (factor : F) : Array F := + (List.range (max target.size source.size)).map + (fun col ↦ target.getD col 0 + factor * source.getD col 0) |>.toArray + +/-- Normalize one pivot row and clear the pivot column in all other rows. -/ +def normalizeAndEliminateScalarRows (rows : Array (Array F)) + (pivotRow pivotCol : Nat) : Array (Array F) := + let pivot := (rows.getD pivotRow #[]).getD pivotCol 0 + if pivot == 0 then + rows + else + let pivotVector := normalizeScalarRow (rows.getD pivotRow #[]) pivotCol + let rows := rows.setIfInBounds pivotRow pivotVector + (List.range rows.size).foldl + (fun rows row ↦ + if row == pivotRow then + rows + else + let factor := -((rows.getD row #[]).getD pivotCol 0) + if factor == 0 then + rows + else + rows.setIfInBounds row + (addScaledScalarRow (rows.getD row #[]) pivotVector factor)) + rows + +/-- RREF result for a scalar row-array matrix. -/ +structure ScalarRrefResult where + rows : Array (Array F) + pivots : Array Nat + +/-- Fuel-bounded row-array RREF for tiny scalar coefficient matrices. -/ +def scalarRrefRowsLoop (cols : Nat) : + Nat → Nat → Nat → Array (Array F) → Array Nat → ScalarRrefResult (F := F) + | 0, _col, _row, rows, pivots => { rows := rows, pivots := pivots } + | fuel + 1, col, row, rows, pivots => + if col >= cols || row >= rows.size then + { rows := rows, pivots := pivots } + else + match findScalarPivotRow rows row col with + | none => scalarRrefRowsLoop cols fuel (col + 1) row rows pivots + | some pivotRow => + let swapped := swapScalarRows rows pivotRow row + let reduced := normalizeAndEliminateScalarRows swapped row col + scalarRrefRowsLoop cols fuel (col + 1) (row + 1) reduced + (pivots.push col) + +/-- Row-array RREF for tiny scalar coefficient matrices. -/ +def scalarRrefRows (rows : Array (Array F)) (cols : Nat) : + ScalarRrefResult (F := F) := + scalarRrefRowsLoop cols (cols + 1) 0 0 rows #[] + +/-- Kernel basis vector for one free column of a row-array RREF matrix. -/ +def basisVectorForFreeColumnRows (rows : Array (Array F)) + (pivots : Array Nat) (cols free : Nat) : Array F := + Array.ofFn fun i : Fin cols ↦ + if i.val == free then + 1 + else + match DenseMatrix.pivotRowOfColumn? pivots i.val with + | none => 0 + | some row => -((rows.getD row #[]).getD free 0) + +/-- Homogeneous scalar-kernel basis for a row-array matrix. -/ +def homogeneousKernelBasisRows (rows : Array (Array F)) (cols : Nat) : + Array (Array F) := + let R := scalarRrefRows rows cols + (DenseMatrix.freeColumns cols R.pivots).map + (basisVectorForFreeColumnRows R.rows R.pivots cols) + +/-- Convert one scalar kernel vector back into a polynomial row. -/ +def vectorToPolynomialRow (degreeCap solutionWidth : Nat) (v : Array F) : + PolynomialRow F := + (List.range solutionWidth).map + (fun coord ↦ + CPolynomial.ofArray + ((List.range degreeCap).map + (fun degree ↦ v.getD (coord * degreeCap + degree) 0) |>.toArray)) |>.toArray + +/-- Polynomial `c * X^d`, built without the `CPolynomial.monomial` +`DecidableEq` assumption. -/ +def coeffXPower (c : F) (d : Nat) : CPolynomial F := + CPolynomial.ofArray ((Array.replicate d 0).push c) + +/-- Multiply a row by `c * X^d`, using the coefficient-array monomial builder. -/ +def polynomialScaleCoeffX (c : F) (d : Nat) (p : CPolynomial F) : + CPolynomial F := + if c == 0 then + 0 + else if p == 0 then + 0 + else + CPolynomial.ofArray + ((List.replicate d 0 ++ p.val.toList.map (fun a ↦ c * a)).toArray) + +/-- Multiply a row by `c * X^d`, using coefficient shifting instead of generic +polynomial multiplication by a monomial. -/ +def rowScaleCoeffX (c : F) (d : Nat) (row : PolynomialRow F) : + PolynomialRow F := + row.map fun p ↦ polynomialScaleCoeffX c d p + +/-- Monomial row `X^d * e_i`, used to complete bounded-kernel leaves to a full +approximant basis. -/ +def monomialUnitRow (width i d : Nat) : PolynomialRow F := + (List.range width).map + (fun j ↦ if i == j then coeffXPower 1 d else 0) |>.toArray + +/-- Trivial high-degree approximants present in every X-adic problem. These +rows are essential when the bounded scalar kernel has fewer rows than the module +rank. -/ +def kernelLeafCompletionRows (problem : XAdicProblem F) : + PolynomialMatrix F := + let degreeCap := leafDegreeCap problem + (List.range problem.matrix.size).map + (fun i ↦ monomialUnitRow problem.matrix.size i degreeCap) |>.toArray + +/-- Whether a row set already contains a row with a given shifted leading +position. -/ +def rowsContainLeadingPosition (rows : PolynomialMatrix F) + (shift : Array Nat) (position : Nat) : Bool := + rows.any fun row ↦ + match rowShiftedLeadingPosition? row shift with + | some p => p == position + | none => false + +/-- High monomial rows for shifted leading positions not represented by `rows`. +These rows are always valid approximants and keep recursive residual problems +from losing coordinates after compact row reduction. -/ +def missingCompletionRows (problem : XAdicProblem F) + (shift : Array Nat) (rows : PolynomialMatrix F) : + PolynomialMatrix F := + let degreeCap := leafDegreeCap problem + (List.range problem.matrix.size).filterMap + (fun i ↦ + if rowsContainLeadingPosition rows shift i then + none + else + some (monomialUnitRow problem.matrix.size i degreeCap)) |>.toArray + +/-- Add high monomial approximants for missing pivot positions. -/ +def completeMissingPivotRows (problem : XAdicProblem F) + (shift : Array Nat) (rows : PolynomialMatrix F) : + PolynomialMatrix F := + rows ++ missingCompletionRows problem shift rows + +/-- Cancel the shifted leading term of `target` by `reducer`, when their shifted +leading positions agree. This is the small-leaf analogue of polynomial-matrix +row reduction; it is used only after the bounded scalar kernel has already been +computed. -/ +def cancelKernelLeafLeadingTerm + (target reducer : PolynomialRow F) (shift : Array Nat) : PolynomialRow F := + match rowShiftedLeadingTerm? target shift, rowShiftedLeadingTerm? reducer shift with + | some t, some r => + if t.position == r.position then + if r.coeff == 0 then + target + else + rowSub target (rowScaleCoeffX (t.coeff / r.coeff) (t.degree - r.degree) reducer) + else + target + | _, _ => target + +/-- One inner-loop update for finding a leading-position conflict in a bounded +kernel leaf. -/ +def kernelLeafConflictInRowStep? (rows : PolynomialMatrix F) (shift : Array Nat) + (i : Nat) (found : Option (Nat × Nat)) (j : Nat) : Option (Nat × Nat) := + match found with + | some _ => found + | none => + match rowShiftedLeadingPosition? (rows.getD i #[]) shift, + rowShiftedLeadingPosition? (rows.getD j #[]) shift with + | some pi, some pj => if pi == pj then some (i, j) else none + | _, _ => none + +/-- Scan one row for a shifted-leading-position conflict in a bounded kernel +leaf. -/ +def kernelLeafConflictInRow? (rows : PolynomialMatrix F) (shift : Array Nat) + (i : Nat) (found : Option (Nat × Nat)) : Option (Nat × Nat) := + (List.range' (i + 1) (rows.size - (i + 1))).foldl + (kernelLeafConflictInRowStep? rows shift i) found + +/-- Scan all row pairs for the first shifted-leading-position conflict. -/ +def kernelLeafConflictFrom? (rows : PolynomialMatrix F) (shift : Array Nat) + (found : Option (Nat × Nat)) : Option (Nat × Nat) := + (List.range rows.size).foldl + (fun acc i ↦ kernelLeafConflictInRow? rows shift i acc) found + +/-- First pair of nonzero bounded-kernel rows with the same shifted leading +position. -/ +def kernelLeafConflict? (rows : PolynomialMatrix F) (shift : Array Nat) : + Option (Nat × Nat) := + kernelLeafConflictFrom? rows shift none + +/-- One shifted-reduction step for bounded scalar-kernel rows. -/ +def reduceKernelLeafStep (rows : PolynomialMatrix F) (shift : Array Nat) + (i j : Nat) : PolynomialMatrix F := + let rowI := rows.getD i #[] + let rowJ := rows.getD j #[] + match rowShiftedDegree? rowI shift, rowShiftedDegree? rowJ shift with + | some degI, some degJ => + if degI ≤ degJ then + replaceRow rows j (cancelKernelLeafLeadingTerm rowJ rowI shift) + else + replaceRow rows i (cancelKernelLeafLeadingTerm rowI rowJ shift) + | _, _ => rows + +/-- Fuel for bounded-kernel shifted reduction. The scalar leaf is already a +small base case, so this conservative degree-width bound is acceptable here. -/ +def reduceKernelLeafFuel (rows : PolynomialMatrix F) (shift : Array Nat) : Nat := + let maxDegree := (List.range rows.size).foldl + (fun acc i ↦ + match rowShiftedDegree? (rows.getD i #[]) shift with + | none => acc + | some degree => max acc degree) + 0 + (rows.size + 1) * (MatrixWidth rows + 1) * (maxDegree + 1) + +/-- Extract the nonempty pivot rows from a leading-position table. -/ +def pivotRows (pivots : Array (Option (PolynomialRow F))) : + PolynomialMatrix F := + pivots.toList.filterMap id |>.toArray + +/-- Insert one row into a shifted weak-Popov pivot table. Conflicts are resolved +only at the current leading position, avoiding the repeated global pair scans +used by the simple reference reducer. -/ +def insertKernelLeafPivotRowWithFuel : + Nat → Array (Option (PolynomialRow F)) → Array Nat → PolynomialRow F → + Array (Option (PolynomialRow F)) + | 0, pivots, _shift, _row => pivots + | fuel + 1, pivots, shift, row => + match rowShiftedLeadingTerm? row shift with + | none => pivots + | some target => + match pivots.getD target.position none with + | none => pivots.setIfInBounds target.position (some row) + | some pivot => + match rowShiftedLeadingTerm? pivot shift with + | none => pivots.setIfInBounds target.position (some row) + | some reducer => + if target.shiftedDegree < reducer.shiftedDegree then + let reducedPivot := cancelKernelLeafLeadingTerm pivot row shift + let pivots := pivots.setIfInBounds target.position (some row) + insertKernelLeafPivotRowWithFuel fuel pivots shift reducedPivot + else + let reducedRow := cancelKernelLeafLeadingTerm row pivot shift + insertKernelLeafPivotRowWithFuel fuel pivots shift reducedRow + +/-- Pivot-table shifted reduction for bounded scalar-kernel rows. -/ +def reduceKernelLeafRowsByPivots (rows : PolynomialMatrix F) (shift : Array Nat) : + PolynomialMatrix F := + let fuel := reduceKernelLeafFuel rows shift + let pivots := rows.foldl + (fun pivots row ↦ insertKernelLeafPivotRowWithFuel fuel pivots shift row) + (Array.replicate (MatrixWidth rows) none) + pivotRows pivots + +/-- Shift-reduce the bounded scalar-kernel rows before compacting them. This +keeps one low representative per shifted leading position instead of selecting +arbitrary low-degree kernel vectors. -/ +def reduceKernelLeafWithFuel : + Nat → PolynomialMatrix F → Array Nat → PolynomialMatrix F + | 0, rows, _shift => rows + | fuel + 1, rows, shift => + match kernelLeafConflict? rows shift with + | none => rows + | some (i, j) => + reduceKernelLeafWithFuel fuel (reduceKernelLeafStep rows shift i j) shift + +/-- Shift-reduced bounded scalar-kernel rows for the PM-basis leaf. -/ +def reduceKernelLeafRows (rows : PolynomialMatrix F) (shift : Array Nat) : + PolynomialMatrix F := + reduceKernelLeafRowsByPivots rows shift + +/-- Insert one bounded-kernel row into a small shifted-reduced leaf basis. This +keeps the live reduction matrix near the module width instead of reducing the +entire scalar kernel at once. -/ +def insertKernelLeafRowIncremental (basis : PolynomialMatrix F) + (shift : Array Nat) (row : PolynomialRow F) : PolynomialMatrix F := + (reduceKernelLeafRows (basis.push row) shift).filter fun row ↦ !rowIsZero row + +/-- Shift-reduce all bounded scalar-kernel rows incrementally. The dense scalar +kernel can have many rows, but after every insertion the weak-Popov conflict loop +works on the current reduced basis plus one candidate row. -/ +def reduceKernelLeafRowsIncremental (rows : PolynomialMatrix F) + (shift : Array Nat) : PolynomialMatrix F := + rows.foldl (fun basis row ↦ insertKernelLeafRowIncremental basis shift row) #[] + +/-- Classical scalar-kernel leaf for small X-adic approximant problems. -/ +def kernelLeafBasis (problem : XAdicProblem F) (shift : Array Nat) : + PolynomialMatrix F := + let degreeCap := leafDegreeCap problem + let scalarRows := (homogeneousKernelBasisRows (coefficientMatrixRows problem) + (problem.matrix.size * degreeCap)).map + (vectorToPolynomialRow degreeCap problem.matrix.size) + completeMissingPivotRows problem shift + (reduceKernelLeafRowsIncremental + (scalarRows ++ kernelLeafCompletionRows problem) shift) + +/-- Remove zero rows before a recursively computed approximant basis is used as +the coordinate system for the next residual problem. -/ +def compactNonzeroRows (rows : PolynomialMatrix F) : PolynomialMatrix F := + rows.filter fun row ↦ !rowIsZero row + +/-- Runtime data for recursive PM-basis computation. -/ +structure PMBasisRuntime (F : Type*) [Field F] [BEq F] [LawfulBEq F] where + mulContext : CPolynomial.MulContext F + lowMulContext : PolynomialMatrix.MulLowContext F + composeBasis : PolynomialMatrix F → PolynomialMatrix F → PolynomialMatrix F + residualProduct : + Array Nat → PolynomialMatrix F → PolynomialMatrix F → PolynomialMatrix F + leafCutoff : Nat + leafBasis : XAdicProblem F → Array Nat → PolynomialMatrix F + +/-- Recursive PM-basis runtime using the scalar dense-kernel routine as its +small-leaf solver and an independently tuned basis-composition cutoff. -/ +def kernelLeafRuntimeWithLowAndCompose (mulCtx : CPolynomial.MulContext F) + (lowCtx : PolynomialMatrix.MulLowContext F) + (leafCutoff composeLeafCutoff : Nat) : + PMBasisRuntime F where + mulContext := mulCtx + lowMulContext := lowCtx + composeBasis := PolynomialMatrix.mulStrassenWith lowCtx composeLeafCutoff + residualProduct := PolynomialMatrix.mulTruncColumnStrassenWith lowCtx + composeLeafCutoff + leafCutoff := leafCutoff + leafBasis := kernelLeafBasis + +/-- Recursive PM-basis runtime using the scalar dense-kernel routine as its +small-leaf solver. -/ +def kernelLeafRuntimeWithLow (mulCtx : CPolynomial.MulContext F) + (lowCtx : PolynomialMatrix.MulLowContext F) (leafCutoff : Nat) : + PMBasisRuntime F := + kernelLeafRuntimeWithLowAndCompose mulCtx lowCtx leafCutoff leafCutoff + +/-- Compatibility runtime whose low products are obtained by truncating full +products. -/ +def kernelLeafRuntime (mulCtx : CPolynomial.MulContext F) (leafCutoff : Nat) : + PMBasisRuntime F := + kernelLeafRuntimeWithLow mulCtx (PolynomialMatrix.MulLowContext.fromMulContext mulCtx) + leafCutoff + +/-- Fuel-bounded recursive PM-basis driver, compacting zero rows after each +leaf and composition step. + +Internal nodes return the composed product `P₂ * P₁` without re-reduction: +composition of minimal half-bases under the updated shift is itself a basis of +the full-order approximant module, so re-reducing at every node would only add +work that is quadratic in the row degrees and outside the PM-basis cost model. +A single weak-Popov normalization pass runs once at the root entry points. -/ +def pmBasisWithFuelCore (runtime : PMBasisRuntime F) : + Nat → XAdicProblem F → Array Nat → PolynomialMatrix F + | 0, problem, shift => compactNonzeroRows (runtime.leafBasis problem shift) + | fuel + 1, problem, shift => + let order := maxOrder problem + if order ≤ runtime.leafCutoff || order ≤ 1 then + compactNonzeroRows (runtime.leafBasis problem shift) + else + let d₁ := order / 2 + let lower : XAdicProblem F := + { orders := lowerOrders problem d₁, matrix := problem.matrix } + let P₁ := pmBasisWithFuelCore runtime fuel lower shift + let residualOrders := residualOrders problem d₁ + let residual : XAdicProblem F := + { orders := residualOrders + matrix := residualMatrixWithProduct runtime.residualProduct P₁ + problem.matrix d₁ residualOrders } + let shifted := updateShiftByRows P₁ shift + let P₂ := pmBasisWithFuelCore runtime fuel residual shifted + compactNonzeroRows (runtime.composeBasis P₂ P₁) + +/-- Root normalization for a recursively composed approximant basis: one +weak-Popov reduction pass plus completion rows for any leading position that +lost its representative. When the recursion preserved minimality this pass +performs no cascading cancellations; it is a semantic guard, not part of the +recursive cost model. -/ +def pmBasisNormalizeRoot (problem : XAdicProblem F) (shift : Array Nat) + (basis : PolynomialMatrix F) : PolynomialMatrix F := + completeMissingPivotRows problem shift + (compactNonzeroRows (reduceKernelLeafRows basis shift)) + +/-- Fuel-bounded recursive PM-basis driver with root normalization. -/ +def pmBasisWithFuel (runtime : PMBasisRuntime F) + (fuel : Nat) (problem : XAdicProblem F) (shift : Array Nat) : + PolynomialMatrix F := + pmBasisNormalizeRoot problem shift (pmBasisWithFuelCore runtime fuel problem shift) + +/-- Default fuel choice, large enough to split each positive order down to the +leaf cutoff. -/ +def pmBasisFuel [Zero F] (problem : XAdicProblem F) : Nat := + maxOrder problem + 1 + +/-- Recursive PM-basis entry point. -/ +def pmBasis (runtime : PMBasisRuntime F) + (problem : XAdicProblem F) (shift : Array Nat) : PolynomialMatrix F := + pmBasisWithFuel runtime (pmBasisFuel problem) problem shift + +/-- Context packaging the executable PM-basis operation with theorem fields. -/ +structure PMBasisContext (F : Type*) [Field F] [BEq F] [LawfulBEq F] where + runtime : PMBasisRuntime F + basis : XAdicProblem F → Array Nat → PolynomialMatrix F := pmBasis runtime + sound : + ∀ problem shift row, + row ∈ MatrixRows (basis problem shift) → + ∀ j, j < problem.orders.size → + truncateX (problem.orders.getD j 0) + (rowGet (rowMulMatrixWith runtime.mulContext row problem.matrix) j) = 0 + complete_minimal : + ∀ problem shift row, + (∀ j, j < problem.orders.size → + truncateX (problem.orders.getD j 0) + (rowGet (rowMulMatrixWith runtime.mulContext row problem.matrix) j) = 0) → + ∃ basisRow, + basisRow ∈ MatrixRows (basis problem shift) + +/-- PM-basis context backed by the recursive driver, scalar dense-kernel leaves, +and an independently tuned basis-composition cutoff. -/ +def kernelLeafPMBasisContextWithLowAndCompose (mulCtx : CPolynomial.MulContext F) + (lowCtx : PolynomialMatrix.MulLowContext F) + (leafCutoff composeLeafCutoff : Nat) : PMBasisContext F where + runtime := kernelLeafRuntimeWithLowAndCompose mulCtx lowCtx leafCutoff + composeLeafCutoff + basis := pmBasis (kernelLeafRuntimeWithLowAndCompose mulCtx lowCtx leafCutoff + composeLeafCutoff) + sound := by + sorry + complete_minimal := by + sorry + +/-- PM-basis context backed by the recursive driver and scalar dense-kernel +leaves. -/ +def kernelLeafPMBasisContextWithLow (mulCtx : CPolynomial.MulContext F) + (lowCtx : PolynomialMatrix.MulLowContext F) + (leafCutoff : Nat) : PMBasisContext F := + kernelLeafPMBasisContextWithLowAndCompose mulCtx lowCtx leafCutoff leafCutoff + +/-- PM-basis context whose low products are obtained by truncating full +products. -/ +def kernelLeafPMBasisContext (mulCtx : CPolynomial.MulContext F) + (leafCutoff : Nat) : PMBasisContext F := + kernelLeafPMBasisContextWithLow mulCtx + (PolynomialMatrix.MulLowContext.fromMulContext mulCtx) leafCutoff + +end Approximant + +end PolynomialMatrix + +end CompPoly diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PartialLinearization.lean b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PartialLinearization.lean new file mode 100644 index 00000000..48b52fe5 --- /dev/null +++ b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PartialLinearization.lean @@ -0,0 +1,227 @@ +/- +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.LinearAlgebra.PolynomialMatrix.Approximant.PMBasis + +/-! +# Partial-Linearization Parameters + +Small executable helpers used by diagonal modular-equation solvers to size the +expanded X-adic problem without using one global oversized order. +-/ + +namespace CompPoly + +namespace PolynomialMatrix + +namespace Approximant + +variable {F : Type*} + +/-- Ceiling division with `1` as the zero-width fallback. -/ +def ceilDivFallback (n d : Nat) : Nat := + if d == 0 then 1 else (n + d - 1) / d + +/-- Degree mass of diagonal moduli. -/ +def modulusDegreeMass [Zero F] (moduli : Array (CPolynomial F)) : Nat := + moduli.foldl (fun acc modulus ↦ acc + modulus.natDegree) 0 + +/-- Chunk size `Delta = ceil(sigma / m)` used for partial linearization. -/ +def chunkDelta [Zero F] (solutionWidth : Nat) + (moduli : Array (CPolynomial F)) : Nat := + max 1 (ceilDivFallback (modulusDegreeMass moduli) solutionWidth) + +/-- X-adic orders for the exact-nullspace lift, using the local chunk size. -/ +def linearizedOrders [Zero F] (solutionWidth : Nat) + (moduli : Array (CPolynomial F)) : Array Nat := + let delta := chunkDelta solutionWidth moduli + moduli.map fun modulus ↦ modulus.natDegree + delta + 1 + +/-- Maximum shifted-degree offset in a principal-coordinate shift. -/ +def maxShiftDegree (shift : Array Nat) : Nat := + (List.range shift.size).foldl + (fun acc i ↦ max acc (shift.getD i 0)) 0 + +/-- Offset added to every principal coordinate when lifting to exact-nullspace +coordinates. Adding the same positive offset to every principal coordinate +preserves their relative shifted degrees; quotient coordinates are shifted +separately above the principal search window. -/ +def principalShiftOffset (_shift : Array Nat) (delta : Nat) : Nat := + delta + 1 + +/-- Quotient-coordinate shift used by the exact-nullspace lift. It is kept at +the chunk degree so quotient coordinates do not dominate the shifted degree of +principal relation rows. Rows that compress to zero are removed later by the +principal-row filter. -/ +def quotientShift (_shift : Array Nat) (delta : Nat) : Nat := + delta + +/-- Principal-coordinate shift used inside the exact-nullspace lift. -/ +def liftedPrincipalShift (shift : Array Nat) (delta : Nat) : Array Nat := + shift.map fun degree ↦ degree + principalShiftOffset shift delta + +/-- Extend the principal solution shift with low quotient shifts. -/ +def exactNullspaceShift (shift : Array Nat) (quotientWidth delta : Nat) : + Array Nat := + liftedPrincipalShift shift delta ++ + Array.replicate quotientWidth (quotientShift shift delta) + +/-- One chunk of a principal solution coordinate. It represents +`X^offset * chunkPoly` in coordinate `coord`. -/ +structure PrincipalChunk where + coord : Nat + offset : Nat +deriving Repr, BEq, DecidableEq + +/-- Number of chunks used for one principal coordinate from a shifted-degree +profile. -/ +def principalChunkCount (solutionWidth : Nat) (shift : Array Nat) + (delta j : Nat) : Nat := + let maxShift := (List.range solutionWidth).foldl + (fun acc i ↦ max acc (shift.getD i 0)) 0 + max 1 (ceilDivFallback (maxShift + 1 - shift.getD j 0) delta) + +/-- Principal chunks induced by the known shifted-degree profile. -/ +def principalChunks (solutionWidth : Nat) (shift : Array Nat) + (delta : Nat) : Array PrincipalChunk := Id.run do + let mut chunks := #[] + for j in [0:solutionWidth] do + let count := principalChunkCount solutionWidth shift delta j + for c in [0:count] do + chunks := chunks.push { coord := j, offset := c * delta } + pure chunks + +/-- One unshifted chunk for each principal coordinate. -/ +def principalUnitChunks (solutionWidth : Nat) : Array PrincipalChunk := + (List.range solutionWidth).map + (fun coord ↦ { coord := coord, offset := 0 }) |>.toArray + +/-- Fallback shifted pivot degree used before discovery has produced a degree for +one coordinate. -/ +def fallbackPivotDegree (solutionWidth : Nat) (shift : Array Nat) : Nat := + (List.range solutionWidth).foldl + (fun acc i ↦ max acc (shift.getD i 0)) 0 + +/-- Pivot degree for one coordinate, falling back to the shift spread when the +profile has not discovered that coordinate. -/ +def pivotDegreeAt (solutionWidth : Nat) (shift : Array Nat) + (pivotDegrees : Array (Option Nat)) (j : Nat) : Nat := + match pivotDegrees.getD j none with + | some degree => degree + | none => fallbackPivotDegree solutionWidth shift + +/-- Number of chunks for one principal coordinate from a discovered shifted +pivot-degree profile. -/ +def principalChunkCountFromPivotDegree (shift : Array Nat) + (delta j pivotDegree : Nat) : Nat := + max 1 (ceilDivFallback (pivotDegree + 1 - shift.getD j 0) delta) + +/-- Principal chunks induced by a discovered shifted pivot-degree profile. -/ +def principalChunksFromPivotDegrees (solutionWidth : Nat) (shift : Array Nat) + (delta : Nat) (pivotDegrees : Array (Option Nat)) : + Array PrincipalChunk := Id.run do + let mut chunks := #[] + for j in [0:solutionWidth] do + let degree := pivotDegreeAt solutionWidth shift pivotDegrees j + let count := principalChunkCountFromPivotDegree shift delta j degree + for c in [0:count] do + chunks := chunks.push { coord := j, offset := c * delta } + pure chunks + +/-- Executable partial-linearization plan for the exact-nullspace lift. -/ +structure PartialLinearizationPlan where + solutionWidth : Nat + quotientWidth : Nat + delta : Nat + chunks : Array PrincipalChunk + +/-- Build a chunk plan from the modular shape and shifted-degree profile. -/ +def partialLinearizationPlan [Zero F] (solutionWidth quotientWidth : Nat) + (moduli : Array (CPolynomial F)) (shift : Array Nat) : + PartialLinearizationPlan := + let delta := chunkDelta solutionWidth moduli + { solutionWidth := solutionWidth + quotientWidth := quotientWidth + delta := delta + chunks := principalChunks solutionWidth shift delta } + +/-- Build a chunk plan from a discovered shifted pivot-degree profile. -/ +def partialLinearizationPlanFromPivotDegrees [Zero F] + (solutionWidth quotientWidth : Nat) (moduli : Array (CPolynomial F)) + (shift : Array Nat) (pivotDegrees : Array (Option Nat)) : + PartialLinearizationPlan := + let delta := chunkDelta solutionWidth moduli + { solutionWidth := solutionWidth + quotientWidth := quotientWidth + delta := delta + chunks := principalChunksFromPivotDegrees solutionWidth shift delta pivotDegrees } + +/-- Plan with partial linearization disabled for the principal coordinates. -/ +def unchunkedPartialLinearizationPlan [Zero F] + (solutionWidth quotientWidth : Nat) (moduli : Array (CPolynomial F)) : + PartialLinearizationPlan := + { solutionWidth := solutionWidth + quotientWidth := quotientWidth + delta := chunkDelta solutionWidth moduli + chunks := principalUnitChunks solutionWidth } + +/-- Monomial `X^offset` as a canonical polynomial, built without requiring the +`CPolynomial.X` nontriviality instance. -/ +def xPowPolynomial [Zero F] [One F] [BEq F] [LawfulBEq F] + (offset : Nat) : CPolynomial F := + CPolynomial.ofArray ((Array.replicate offset 0).push 1) + +/-- Multiply a polynomial by `X^offset` by shifting its coefficient array. +This is an `O(offset + deg p)` array operation; it must not go through generic +polynomial multiplication, which would cost `O(offset * deg p)`. -/ +def shiftPolynomialX [Semiring F] [BEq F] [LawfulBEq F] + (offset : Nat) (p : CPolynomial F) : CPolynomial F := + if p == 0 then + 0 + else + CPolynomial.ofArray ((List.replicate offset (0 : F) ++ p.val.toList).toArray) + +/-- Shift every entry of a row by `X^offset`. -/ +def shiftRowX [Semiring F] [BEq F] [LawfulBEq F] + (offset : Nat) (row : PolynomialRow F) : PolynomialRow F := + row.map fun p ↦ shiftPolynomialX offset p + +/-- Shift for the chunked exact-nullspace problem. -/ +def chunkedExactNullspaceShift (plan : PartialLinearizationPlan) + (shift : Array Nat) : Array Nat := + plan.chunks.map + (fun chunk ↦ + shift.getD chunk.coord 0 + chunk.offset + + principalShiftOffset shift plan.delta) ++ + Array.replicate plan.quotientWidth (quotientShift shift plan.delta) + +/-- Compress one row in chunked coordinates back to the principal solution +coordinates. -/ +def compressChunkedPrincipalRow [Semiring F] [BEq F] [LawfulBEq F] + (plan : PartialLinearizationPlan) (row : PolynomialRow F) : + PolynomialRow F := + (List.range plan.solutionWidth).map + (fun coord ↦ + (List.range plan.chunks.size).foldl + (fun acc chunkIdx ↦ + let chunk := plan.chunks.getD chunkIdx { coord := 0, offset := 0 } + if chunk.coord == coord then + acc + shiftPolynomialX chunk.offset (rowGet row chunkIdx) + else + acc) + 0) |>.toArray + +/-- Compress every row in a chunked basis back to the principal coordinates. -/ +def compressChunkedPrincipalRows [Semiring F] [BEq F] [LawfulBEq F] + (plan : PartialLinearizationPlan) (basis : PolynomialMatrix F) : + PolynomialMatrix F := + basis.map fun row ↦ compressChunkedPrincipalRow plan row + +end Approximant + +end PolynomialMatrix + +end CompPoly diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix/Operations.lean b/CompPoly/LinearAlgebra/PolynomialMatrix/Operations.lean new file mode 100644 index 00000000..ca577fa6 --- /dev/null +++ b/CompPoly/LinearAlgebra/PolynomialMatrix/Operations.lean @@ -0,0 +1,444 @@ +/- +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.LinearAlgebra.PolynomialMatrix.Shifted +import CompPoly.Univariate.Context + +/-! +# Polynomial-Matrix Operations + +Reusable executable operations for polynomial rows and row-major polynomial +matrices. The multiplication and reduction entry points take explicit +univariate operation contexts so concrete fields can supply fast polynomial +arithmetic. +-/ + +namespace CompPoly + +namespace PolynomialMatrix + +variable {F : Type*} + +/-- Polynomial-matrix low-product backend. The full multiplication context is +kept beside the low-product operation because recursive PM-basis still needs +ordinary basis composition. -/ +structure MulLowContext (F : Type*) [Semiring F] [BEq F] [LawfulBEq F] where + mulContext : CPolynomial.MulContext F + mulLow : Nat → CPolynomial F → CPolynomial F → CPolynomial F + +namespace MulLowContext + +/-- Low-product backend obtained by truncating a full univariate product. -/ +def fromMulContext [Semiring F] [BEq F] [LawfulBEq F] + (mulCtx : CPolynomial.MulContext F) : MulLowContext F where + mulContext := mulCtx + mulLow order p q := + CPolynomial.ofArray + ((List.range order).map (fun i ↦ CPolynomial.coeff (mulCtx.mul p q) i) |>.toArray) + +/-- Low-product backend backed directly by a raw low-product implementation. -/ +def raw [Semiring F] [BEq F] [LawfulBEq F] + (mulCtx : CPolynomial.MulContext F) + (rawCtx : CPolynomial.Raw.MulLowContext F) : MulLowContext F where + mulContext := mulCtx + mulLow order p q := CPolynomial.ofArray (rawCtx.mulLow order p.val q.val) + +end MulLowContext + +/-- Build a row-major polynomial matrix from an indexed entry function. -/ +def ofFn [Zero F] (rows width : Nat) (entry : Nat → Nat → CPolynomial F) : + PolynomialMatrix F := + (List.range rows).map + (fun i ↦ (List.range width).map (fun j ↦ entry i j) |>.toArray) |>.toArray + +/-- The zero matrix of a fixed shape. -/ +def zero [Zero F] (rows width : Nat) : PolynomialMatrix F := + ofFn rows width fun _ _ ↦ 0 + +/-- The polynomial identity matrix of size `n`. -/ +def identity [Semiring F] [BEq F] [LawfulBEq F] [Nontrivial F] (n : Nat) : + PolynomialMatrix F := + ofFn n n fun i j ↦ if i == j then 1 else 0 + +/-- Matrix transpose, using zero defaults for ragged input rows. -/ +def transpose [Zero F] (M : PolynomialMatrix F) : PolynomialMatrix F := + ofFn (MatrixWidth M) M.size fun i j ↦ rowGet (M.getD j #[]) i + +/-- Dot product of two polynomial rows using an explicit univariate +multiplication context. -/ +def rowDotWith [Semiring F] [BEq F] [LawfulBEq F] + (mulCtx : CPolynomial.MulContext F) (a b : PolynomialRow F) : + CPolynomial F := + (List.range (max a.size b.size)).foldl + (fun acc k ↦ acc + mulCtx.mul (rowGet a k) (rowGet b k)) 0 + +/-- Row-by-matrix product using an explicit univariate multiplication context. -/ +def rowMulMatrixWith [Semiring F] [BEq F] [LawfulBEq F] + (mulCtx : CPolynomial.MulContext F) (row : PolynomialRow F) + (M : PolynomialMatrix F) : PolynomialRow F := + (List.range (MatrixWidth M)).map + (fun j ↦ + (List.range row.size).foldl + (fun acc k ↦ acc + mulCtx.mul (rowGet row k) (rowGet (M.getD k #[]) j)) 0) |>.toArray + +/-- Matrix product using an explicit univariate multiplication context. -/ +def mulWith [Semiring F] [BEq F] [LawfulBEq F] + (mulCtx : CPolynomial.MulContext F) (A B : PolynomialMatrix F) : + PolynomialMatrix F := + A.map fun row ↦ rowMulMatrixWith mulCtx row B + +/-- Matrix product backed by canonical univariate multiplication. -/ +def mul [Semiring F] [BEq F] [LawfulBEq F] + (A B : PolynomialMatrix F) : PolynomialMatrix F := + mulWith CPolynomial.MulContext.naive A B + +/-- Pointwise matrix addition, using zero defaults for ragged inputs. -/ +def add [Semiring F] [BEq F] [LawfulBEq F] + (A B : PolynomialMatrix F) : PolynomialMatrix F := + ofFn (max A.size B.size) (max (MatrixWidth A) (MatrixWidth B)) fun i j ↦ + rowGet (A.getD i #[]) j + rowGet (B.getD i #[]) j + +/-- Pointwise matrix subtraction, using zero defaults for ragged inputs. -/ +def sub [Ring F] [BEq F] [LawfulBEq F] + (A B : PolynomialMatrix F) : PolynomialMatrix F := + ofFn (max A.size B.size) (max (MatrixWidth A) (MatrixWidth B)) fun i j ↦ + rowGet (A.getD i #[]) j - rowGet (B.getD i #[]) j + +/-- Extract a rectangular block with zero defaults for out-of-range entries. -/ +def block [Zero F] (M : PolynomialMatrix F) + (rowStart rowCount colStart colCount : Nat) : PolynomialMatrix F := + ofFn rowCount colCount fun i j ↦ + rowGet (M.getD (rowStart + i) #[]) (colStart + j) + +/-- Join four equally sized square blocks into one square matrix. -/ +def joinSquareBlocks [Zero F] (half : Nat) + (C₁₁ C₁₂ C₂₁ C₂₂ : PolynomialMatrix F) : PolynomialMatrix F := + ofFn (2 * half) (2 * half) fun i j ↦ + if i < half then + if j < half then + rowGet (C₁₁.getD i #[]) j + else + rowGet (C₁₂.getD i #[]) (j - half) + else + if j < half then + rowGet (C₂₁.getD (i - half) #[]) j + else + rowGet (C₂₂.getD (i - half) #[]) (j - half) + +/-- Fuel-bounded doubling loop for the smallest power of two at least `target`. -/ +def nextPowerOfTwoAtLeastLoop (target : Nat) : + Nat → Nat → Nat + | 0, current => current + | fuel + 1, current => + if target ≤ current then + current + else + nextPowerOfTwoAtLeastLoop target fuel (2 * current) + +/-- Smallest power of two at least `target`, with `1` returned for `0`. -/ +def nextPowerOfTwoAtLeast (target : Nat) : Nat := + nextPowerOfTwoAtLeastLoop target (target + 1) 1 + +/-- Runtime dimension controlling rectangular polynomial-matrix multiplication. -/ +def multiplicationDimension [Zero F] (A B : PolynomialMatrix F) : Nat := + max A.size (max (MatrixWidth A) (max B.size (MatrixWidth B))) + +/-- Pad a matrix to an `n × n` square using the zero-default block extractor. -/ +def padSquare [Zero F] (n : Nat) (M : PolynomialMatrix F) : + PolynomialMatrix F := + block M 0 n 0 n + +/-- Trim a matrix to a rectangular output shape. -/ +def trimShape [Zero F] (rows width : Nat) (M : PolynomialMatrix F) : + PolynomialMatrix F := + block M 0 rows 0 width + +/-- Slice `count` natural-number entries, using zero defaults out of bounds. -/ +def natArraySlice (values : Array Nat) (start count : Nat) : Array Nat := + (List.range count).map (fun i ↦ values.getD (start + i) 0) |>.toArray + +/-- Pointwise maximum of two natural-number arrays. -/ +def maxNatArrays (a b : Array Nat) : Array Nat := + (List.range (max a.size b.size)).map + (fun i ↦ max (a.getD i 0) (b.getD i 0)) |>.toArray + +/-- Number of coefficients needed to represent a polynomial exactly. -/ +def polynomialCoeffCap [Zero F] [BEq F] (p : CPolynomial F) : Nat := + if p == 0 then 0 else p.natDegree + 1 + +/-- Number of low coefficients sufficient for one product term exactly. -/ +def productCoeffCap [Zero F] [BEq F] (p q : CPolynomial F) : Nat := + if p == 0 || q == 0 then 0 else p.natDegree + q.natDegree + 1 + +/-- Per-entry coefficient cap for one row-by-matrix product entry. -/ +def rowMulMatrixEntryCoeffCap [Zero F] [BEq F] + (row : PolynomialRow F) (M : PolynomialMatrix F) (j : Nat) : Nat := + (List.range row.size).foldl + (fun acc k ↦ max acc + (productCoeffCap (rowGet row k) (rowGet (M.getD k #[]) j))) 0 + +/-- Keep the coefficients of degree `< order`. -/ +def truncateX [Zero F] [BEq F] [LawfulBEq F] (order : Nat) + (p : CPolynomial F) : CPolynomial F := + CPolynomial.ofArray + ((List.range order).map (fun i ↦ CPolynomial.coeff p i) |>.toArray) + +/-- Truncate one row with independent output-column orders. -/ +def rowTruncateColumns [Zero F] [BEq F] [LawfulBEq F] + (orders : Array Nat) (row : PolynomialRow F) : PolynomialRow F := + (List.range row.size).map + (fun j ↦ truncateX (orders.getD j 0) (rowGet row j)) |>.toArray + +/-- Truncate a matrix with independent output-column orders. -/ +def truncateColumns [Zero F] [BEq F] [LawfulBEq F] + (orders : Array Nat) (M : PolynomialMatrix F) : PolynomialMatrix F := + M.map fun row ↦ rowTruncateColumns orders row + +/-- Multiply and retain only coefficients of degree `< order`. -/ +def mulTruncXWith [Semiring F] [BEq F] [LawfulBEq F] + (mulCtx : CPolynomial.MulContext F) (order : Nat) + (p q : CPolynomial F) : CPolynomial F := + truncateX order (mulCtx.mul p q) + +/-- Low-product entry point for the first `order` coefficients. -/ +def mulLowXWith [Semiring F] [BEq F] [LawfulBEq F] + (lowCtx : MulLowContext F) (order : Nat) + (p q : CPolynomial F) : CPolynomial F := + lowCtx.mulLow order p q + +/-- Divide by `X^shift` and keep `order` coefficients. -/ +def divXTrunc [Zero F] [BEq F] [LawfulBEq F] (shift order : Nat) + (p : CPolynomial F) : CPolynomial F := + CPolynomial.ofArray + ((List.range order).map (fun i ↦ CPolynomial.coeff p (i + shift)) |>.toArray) + +/-- Reduce by a monic modulus when one is present. A zero modulus is treated as +an absent modulus and leaves the input unchanged. -/ +def modByMonicWith [Field F] [BEq F] [LawfulBEq F] + (modCtx : CPolynomial.ModContext F) (p modulus : CPolynomial F) : + CPolynomial F := + if modulus == 0 then p else modCtx.modByMonic p modulus + +/-- Reduce a row by independent diagonal moduli. The output width is the number +of supplied moduli. -/ +def rowModDiagonalWith [Field F] [BEq F] [LawfulBEq F] + (modCtx : CPolynomial.ModContext F) (moduli : Array (CPolynomial F)) + (row : PolynomialRow F) : PolynomialRow F := + (List.range moduli.size).map + (fun j ↦ modByMonicWith modCtx (rowGet row j) (moduli.getD j 0)) |>.toArray + +/-- Reduce every matrix row by independent diagonal moduli. -/ +def modDiagonalWith [Field F] [BEq F] [LawfulBEq F] + (modCtx : CPolynomial.ModContext F) (moduli : Array (CPolynomial F)) + (M : PolynomialMatrix F) : PolynomialMatrix F := + M.map fun row ↦ rowModDiagonalWith modCtx moduli row + +/-- Row-by-matrix product followed by diagonal modular reduction. -/ +def rowMulMatrixModDiagonalWith [Field F] [BEq F] [LawfulBEq F] + (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) + (row : PolynomialRow F) (M : PolynomialMatrix F) + (moduli : Array (CPolynomial F)) : PolynomialRow F := + rowModDiagonalWith modCtx moduli (rowMulMatrixWith mulCtx row M) + +/-- Row-by-matrix product with independent output-column truncation. Column +`j` keeps coefficients of degree `< orders[j]`; this is the residual-window +primitive used by recursive PM-basis. -/ +def rowMulMatrixTruncColumnWith [Semiring F] [BEq F] [LawfulBEq F] + (lowCtx : MulLowContext F) (orders : Array Nat) + (row : PolynomialRow F) (M : PolynomialMatrix F) : PolynomialRow F := + (List.range (MatrixWidth M)).map + (fun j ↦ + let order := orders.getD j 0 + (List.range row.size).foldl + (fun acc k ↦ + acc + mulLowXWith lowCtx order + (rowGet row k) (rowGet (M.getD k #[]) j)) 0) |>.toArray + +/-- Matrix product with independent output-column truncation. -/ +def mulTruncColumnWith [Semiring F] [BEq F] [LawfulBEq F] + (lowCtx : MulLowContext F) (orders : Array Nat) + (A B : PolynomialMatrix F) : PolynomialMatrix F := + A.map fun row ↦ rowMulMatrixTruncColumnWith lowCtx orders row B + +/-- Fuel-bounded Strassen-style matrix product with independent output-column +truncation orders. -/ +def mulTruncColumnStrassenWithFuel [Ring F] [BEq F] [LawfulBEq F] + (lowCtx : MulLowContext F) (leafCutoff : Nat) : + Nat → Array Nat → PolynomialMatrix F → PolynomialMatrix F → PolynomialMatrix F + | 0, orders, A, B => mulTruncColumnWith lowCtx orders A B + | fuel + 1, orders, A, B => + let n := A.size + let dimension := multiplicationDimension A B + if dimension ≤ leafCutoff || dimension ≤ 1 then + mulTruncColumnWith lowCtx orders A B + else if + MatrixWidth A == n && B.size == n && MatrixWidth B == n && + n % 2 == 0 then + let h := n / 2 + let leftOrders := natArraySlice orders 0 h + let rightOrders := natArraySlice orders h h + let pairOrders := maxNatArrays leftOrders rightOrders + let A₁₁ := block A 0 h 0 h + let A₁₂ := block A 0 h h h + let A₂₁ := block A h h 0 h + let A₂₂ := block A h h h h + let B₁₁ := block B 0 h 0 h + let B₁₂ := block B 0 h h h + let B₂₁ := block B h h 0 h + let B₂₂ := block B h h h h + let recMul := mulTruncColumnStrassenWithFuel lowCtx leafCutoff fuel + let M₁ := recMul pairOrders (add A₁₁ A₂₂) (add B₁₁ B₂₂) + let M₂ := recMul pairOrders (add A₂₁ A₂₂) B₁₁ + let M₃ := recMul rightOrders A₁₁ (sub B₁₂ B₂₂) + let M₄ := recMul leftOrders A₂₂ (sub B₂₁ B₁₁) + let M₅ := recMul pairOrders (add A₁₁ A₁₂) B₂₂ + let M₆ := recMul rightOrders (sub A₂₁ A₁₁) (add B₁₁ B₁₂) + let M₇ := recMul leftOrders (sub A₁₂ A₂₂) (add B₂₁ B₂₂) + let C₁₁ := add (sub (add M₁ M₄) M₅) M₇ + let C₁₂ := add M₃ M₅ + let C₂₁ := add M₂ M₄ + let C₂₂ := add (sub (add M₁ M₃) M₂) M₆ + truncateColumns orders (joinSquareBlocks h C₁₁ C₁₂ C₂₁ C₂₂) + else + let paddedSize := nextPowerOfTwoAtLeast dimension + let paddedOrders := natArraySlice orders 0 paddedSize + let product := mulTruncColumnStrassenWithFuel lowCtx leafCutoff fuel + paddedOrders (padSquare paddedSize A) (padSquare paddedSize B) + trimShape A.size (MatrixWidth B) product + +/-- Strassen-style matrix product with independent output-column truncation +orders and conservative default fuel. -/ +def mulTruncColumnStrassenWith [Ring F] [BEq F] [LawfulBEq F] + (lowCtx : MulLowContext F) (leafCutoff : Nat) + (orders : Array Nat) (A B : PolynomialMatrix F) : PolynomialMatrix F := + let n := multiplicationDimension A B + mulTruncColumnStrassenWithFuel lowCtx leafCutoff (n + 1) orders A B + +/-- Row-by-matrix product with per-output-entry degree caps inferred from input +degree profiles. This reconstructs the exact row product while routing every +term through low-product multiplication. -/ +def rowMulMatrixBoundedWith [Semiring F] [BEq F] [LawfulBEq F] + (lowCtx : MulLowContext F) (row : PolynomialRow F) + (M : PolynomialMatrix F) : PolynomialRow F := + (List.range (MatrixWidth M)).map + (fun j ↦ + let order := rowMulMatrixEntryCoeffCap row M j + (List.range row.size).foldl + (fun acc k ↦ + acc + mulLowXWith lowCtx order + (rowGet row k) (rowGet (M.getD k #[]) j)) 0) |>.toArray + +/-- Matrix product reconstructed from inferred per-entry degree caps. Recursive +composition uses this only as its small-leaf and fuel-exhausted fallback. -/ +def mulBoundedWith [Semiring F] [BEq F] [LawfulBEq F] + (lowCtx : MulLowContext F) (A B : PolynomialMatrix F) : + PolynomialMatrix F := + A.map fun row ↦ rowMulMatrixBoundedWith lowCtx row B + +/-- Fuel-bounded Strassen-style matrix product. + +Small inputs and exhausted fuel use the bounded row-column product. Larger +rectangular or odd-sized inputs are padded to square power-of-two shape, routed +through the recursive block product, and trimmed back to the requested output +shape. -/ +def mulStrassenWithFuel [Ring F] [BEq F] [LawfulBEq F] + (lowCtx : MulLowContext F) (leafCutoff : Nat) : + Nat → PolynomialMatrix F → PolynomialMatrix F → PolynomialMatrix F + | 0, A, B => mulBoundedWith lowCtx A B + | fuel + 1, A, B => + let n := A.size + let dimension := multiplicationDimension A B + if dimension ≤ leafCutoff || dimension ≤ 1 then + mulBoundedWith lowCtx A B + else if + MatrixWidth A == n && B.size == n && MatrixWidth B == n && + n % 2 == 0 then + let h := n / 2 + let A₁₁ := block A 0 h 0 h + let A₁₂ := block A 0 h h h + let A₂₁ := block A h h 0 h + let A₂₂ := block A h h h h + let B₁₁ := block B 0 h 0 h + let B₁₂ := block B 0 h h h + let B₂₁ := block B h h 0 h + let B₂₂ := block B h h h h + let recMul := mulStrassenWithFuel lowCtx leafCutoff fuel + let M₁ := recMul (add A₁₁ A₂₂) (add B₁₁ B₂₂) + let M₂ := recMul (add A₂₁ A₂₂) B₁₁ + let M₃ := recMul A₁₁ (sub B₁₂ B₂₂) + let M₄ := recMul A₂₂ (sub B₂₁ B₁₁) + let M₅ := recMul (add A₁₁ A₁₂) B₂₂ + let M₆ := recMul (sub A₂₁ A₁₁) (add B₁₁ B₁₂) + let M₇ := recMul (sub A₁₂ A₂₂) (add B₂₁ B₂₂) + let C₁₁ := add (sub (add M₁ M₄) M₅) M₇ + let C₁₂ := add M₃ M₅ + let C₂₁ := add M₂ M₄ + let C₂₂ := add (sub (add M₁ M₃) M₂) M₆ + joinSquareBlocks h C₁₁ C₁₂ C₂₁ C₂₂ + else + let paddedSize := nextPowerOfTwoAtLeast dimension + let product := mulStrassenWithFuel lowCtx leafCutoff fuel + (padSquare paddedSize A) (padSquare paddedSize B) + trimShape A.size (MatrixWidth B) product + +/-- Strassen-style matrix product with a conservative default fuel. -/ +def mulStrassenWith [Ring F] [BEq F] [LawfulBEq F] + (lowCtx : MulLowContext F) (leafCutoff : Nat) + (A B : PolynomialMatrix F) : PolynomialMatrix F := + let n := multiplicationDimension A B + mulStrassenWithFuel lowCtx leafCutoff (n + 1) A B + +/-- Executable modular-equation row predicate. -/ +def rowSatisfiesModularBool [Field F] [BEq F] [LawfulBEq F] + (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) + (row : PolynomialRow F) (M : PolynomialMatrix F) + (moduli : Array (CPolynomial F)) : Bool := + (rowMulMatrixModDiagonalWith mulCtx modCtx row M moduli).all fun p ↦ p == 0 + +/-- Shifted row-degree profile for all rows. -/ +def shiftedRowDegreeProfile [Zero F] [BEq F] (M : PolynomialMatrix F) + (shift : Array Nat) : Array (Option Nat) := + M.map fun row ↦ rowShiftedDegree? row shift + +/-- Candidate row selected by least-shifted-degree scanning. -/ +structure RowChoice (F : Type*) [Zero F] where + index : Nat + row : PolynomialRow F + degree : Nat + +/-- Tie-breaking order for least-shifted-degree row selection. -/ +def betterRowChoice [Zero F] (candidate current : RowChoice F) : Bool := + candidate.degree < current.degree || + (candidate.degree == current.degree && candidate.index < current.index) + +/-- One left-to-right scan step for least-shifted-degree row selection. -/ +def leastShiftedDegreeRowStep? [Zero F] [BEq F] + (M : PolynomialMatrix F) (shift : Array Nat) + (best : Option (RowChoice F)) (i : Nat) : Option (RowChoice F) := + let row := M.getD i #[] + match rowShiftedDegree? row shift with + | none => best + | some degree => + let candidate : RowChoice F := { index := i, row := row, degree := degree } + match best with + | none => some candidate + | some current => + if betterRowChoice candidate current then some candidate else best + +/-- Scan row indices for the best least-shifted-degree candidate. -/ +def leastShiftedDegreeChoice? [Zero F] [BEq F] + (M : PolynomialMatrix F) (shift : Array Nat) : Option (RowChoice F) := + (List.range M.size).foldl (leastShiftedDegreeRowStep? M shift) none + +/-- Select a nonzero row of least shifted degree. -/ +def leastShiftedDegreeRow? [Zero F] [BEq F] + (M : PolynomialMatrix F) (shift : Array Nat) : + Option (PolynomialRow F) := + (leastShiftedDegreeChoice? M shift).map fun choice ↦ choice.row + +end PolynomialMatrix + +end CompPoly diff --git a/bench/CompPolyBench/Bivariate/GuruswamiSudan.lean b/bench/CompPolyBench/Bivariate/GuruswamiSudan.lean index 702d6cd6..9751f238 100644 --- a/bench/CompPolyBench/Bivariate/GuruswamiSudan.lean +++ b/bench/CompPolyBench/Bivariate/GuruswamiSudan.lean @@ -10,9 +10,9 @@ import CompPolyBench.Bivariate.GuruswamiSudan.ReceivedWord /-! # Guruswami-Sudan Benchmarks -KoalaBear cost-center benchmarks for dense and Lee-O'Sullivan interpolation, -Roth-Ruckenstein and Alekhnovich root finding, packed distance filtering, and -backend-parametric `gsCore` and `gsFilteredCore`. +KoalaBear cost-center benchmarks for the dense interpolation path, +Koetter interpolation path, Roth-Ruckenstein and Alekhnovich root finding, and +full backend-parametric `gsCore` and `gsFilteredCore`. -/ open CompPoly @@ -90,20 +90,35 @@ private def runGsInterpolationSmallKoala (preset : BenchPreset) (gen : StdGen) : let fastPoints := gsSmallBenchmarkPoints fastMessage let warmup := gsWarmupIterations preset let denseMeasured := preset.selectNat 1 1 1 - let leeDirectMeasured := preset.selectNat 100 15 3 - let leeSubproductMeasured := preset.selectNat 90 13 3 + let koetterMeasured := preset.selectNat 10 2 1 + let leeDirectMeasured := preset.selectNat 60 8 2 + let leeSubproductMeasured := preset.selectNat 50 7 1 + let approximantDirectMeasured := preset.selectNat 30 5 1 + let approximantSubproductMeasured := preset.selectNat 1 1 1 let fastDenseMeasured := preset.selectNat 2 1 1 - let fastLeeDirectMeasured := preset.selectNat 600 90 20 - let fastLeeSubproductMeasured := preset.selectNat 400 60 10 + let fastKoetterMeasured := preset.selectNat 70 10 2 + let fastLeeDirectMeasured := preset.selectNat 400 60 10 + let fastLeeSubproductMeasured := preset.selectNat 300 40 10 + let fastApproximantDirectMeasured := preset.selectNat 200 30 5 + let fastApproximantSubproductMeasured := preset.selectNat 1 1 1 let checksumIterations := groupChecksumIterations denseMeasured [ - leeDirectMeasured, leeSubproductMeasured, fastDenseMeasured, - fastLeeDirectMeasured, fastLeeSubproductMeasured + koetterMeasured, leeDirectMeasured, leeSubproductMeasured, fastDenseMeasured, + approximantDirectMeasured, approximantSubproductMeasured, fastKoetterMeasured, + fastLeeDirectMeasured, fastLeeSubproductMeasured, fastApproximantDirectMeasured, + fastApproximantSubproductMeasured ] let denseRow <- runTimed "guruswami-sudan-interp-dense-small" "CBivariate" "Dense linear" "KoalaBear.Field" gsSmallInterpInputShape preset warmup denseMeasured - (fun _ ↦ koalaBearDenseInterpContext.interpolate points gsSmallParams) + (fun _ ↦ denseInterpolate points gsSmallParams) + (checksumInterpolationValidityOption points gsSmallParams) + checksumIterations + let koetterRow <- runTimed + "guruswami-sudan-interp-koetter-small" "CBivariate" + "Koetter" + "KoalaBear.Field" gsSmallInterpInputShape preset warmup koetterMeasured + (fun _ ↦ koetterInterpolate points gsSmallParams) (checksumInterpolationValidityOption points gsSmallParams) checksumIterations let leeDirectRow <- runTimed @@ -120,11 +135,34 @@ private def runGsInterpolationSmallKoala (preset : BenchPreset) (gen : StdGen) : (fun _ ↦ koalaBearLeeSubproductInterpContext.interpolate points gsSmallParams) (checksumInterpolationValidityOption points gsSmallParams) checksumIterations + let approximantDirectRow <- runTimed + "guruswami-sudan-interp-approximant-direct-small" "CBivariate" + "Approximant basis direct" + "KoalaBear.Field" gsSmallInterpInputShape preset warmup approximantDirectMeasured + (fun _ ↦ koalaBearApproximantBasisDirectInterpContext.interpolate points + gsSmallParams) + (checksumInterpolationValidityOption points gsSmallParams) + checksumIterations + let approximantSubproductRow <- runTimed + "guruswami-sudan-interp-approximant-subproduct-small" "CBivariate" + "Approximant basis subproduct" + "KoalaBear.Field" gsSmallInterpInputShape preset warmup approximantSubproductMeasured + (fun _ ↦ koalaBearApproximantBasisSubproductInterpContext.interpolate points + gsSmallParams) + (checksumInterpolationValidityOption points gsSmallParams) + checksumIterations let fastDenseRow <- runTimed "guruswami-sudan-interp-dense-small-fast" "CBivariate" "Dense linear" "KoalaBear.Fast.Field" gsSmallInterpInputShape preset warmup fastDenseMeasured - (fun _ ↦ fastKoalaBearDenseInterpContext.interpolate fastPoints gsSmallParams) + (fun _ ↦ denseInterpolate fastPoints gsSmallParams) + (checksumInterpolationValidityOption fastPoints gsSmallParams) + checksumIterations + let fastKoetterRow <- runTimed + "guruswami-sudan-interp-koetter-small-fast" "CBivariate" + "Koetter" + "KoalaBear.Fast.Field" gsSmallInterpInputShape preset warmup fastKoetterMeasured + (fun _ ↦ koetterInterpolate fastPoints gsSmallParams) (checksumInterpolationValidityOption fastPoints gsSmallParams) checksumIterations let fastLeeDirectRow <- runTimed @@ -138,21 +176,302 @@ private def runGsInterpolationSmallKoala (preset : BenchPreset) (gen : StdGen) : let fastLeeSubproductRow <- runTimed "guruswami-sudan-interp-lee-subproduct-small-fast" "CBivariate" "Lee-O'Sullivan subproduct" - "KoalaBear.Fast.Field" gsSmallInterpInputShape preset warmup - fastLeeSubproductMeasured + "KoalaBear.Fast.Field" gsSmallInterpInputShape preset warmup fastLeeSubproductMeasured (fun _ ↦ fastKoalaBearLeeSubproductInterpContext.interpolate fastPoints gsSmallParams) (checksumInterpolationValidityOption fastPoints gsSmallParams) checksumIterations + let fastApproximantDirectRow <- runTimed + "guruswami-sudan-interp-approximant-direct-small-fast" "CBivariate" + "Approximant basis direct" + "KoalaBear.Fast.Field" gsSmallInterpInputShape preset warmup + fastApproximantDirectMeasured + (fun _ ↦ fastKoalaBearApproximantBasisDirectInterpContext.interpolate fastPoints + gsSmallParams) + (checksumInterpolationValidityOption fastPoints gsSmallParams) + checksumIterations + let fastApproximantSubproductRow <- runTimed + "guruswami-sudan-interp-approximant-subproduct-small-fast" "CBivariate" + "Approximant basis subproduct" + "KoalaBear.Fast.Field" gsSmallInterpInputShape preset warmup + fastApproximantSubproductMeasured + (fun _ ↦ fastKoalaBearApproximantBasisSubproductInterpContext.interpolate fastPoints + gsSmallParams) + (checksumInterpolationValidityOption fastPoints gsSmallParams) + checksumIterations pure ({ groupKey := "guruswami-sudan-interp-small-koalabear", title := "Guruswami-Sudan interpolation, small (KoalaBear)", records := #[ - denseRow, leeDirectRow, leeSubproductRow, - fastDenseRow, fastLeeDirectRow, fastLeeSubproductRow + denseRow, koetterRow, leeDirectRow, leeSubproductRow, + approximantDirectRow, approximantSubproductRow, + fastDenseRow, fastKoetterRow, fastLeeDirectRow, fastLeeSubproductRow, + fastApproximantDirectRow, fastApproximantSubproductRow + ] + }, gen) + +private def runGsInterpolationLargeKoala (preset : BenchPreset) (gen : StdGen) : + IO (Prod BenchGroup StdGen) := do + let (coeffs, gen) := (koalaBearArray gsLargeInterpMessageDegree false).run gen + let message := cpolyOfArray coeffs + let fastMessage := cpolyOfArray (koalaBearFastArray coeffs) + let points := gsLargeBenchmarkPoints message + let fastPoints := gsLargeBenchmarkPoints fastMessage + let warmup := gsWarmupIterations preset + let koetterMeasured := preset.selectNat 1 1 1 + let leeMeasured := preset.selectNat 1 1 1 + let approximantDirectMeasured := preset.selectNat 1 1 1 + let approximantSubproductMeasured := preset.selectNat 1 1 1 + let fastKoetterMeasured := preset.selectNat 1 1 1 + let fastLeeMeasured := preset.selectNat 1 1 1 + let fastApproximantDirectMeasured := preset.selectNat 1 1 1 + let fastApproximantSubproductMeasured := preset.selectNat 1 1 1 + let checksumIterations := groupChecksumIterations koetterMeasured [ + leeMeasured, leeMeasured, approximantDirectMeasured, approximantSubproductMeasured, + fastKoetterMeasured, fastLeeMeasured, fastLeeMeasured, + fastApproximantDirectMeasured, fastApproximantSubproductMeasured + ] + let koetterRow <- runTimed + "guruswami-sudan-interp-koetter" "CBivariate" + "Koetter" + "KoalaBear.Field" gsLargeInterpInputShape preset warmup koetterMeasured + (fun _ ↦ koetterInterpolate points gsLargeInterpParams) + (checksumInterpolationValidityOption points gsLargeInterpParams) + checksumIterations + let leeDirectRow <- runTimed + "guruswami-sudan-interp-lee-direct" "CBivariate" + "Lee-O'Sullivan direct" + "KoalaBear.Field" gsLargeInterpInputShape preset warmup leeMeasured + (fun _ ↦ koalaBearLeeDirectInterpContext.interpolate points gsLargeInterpParams) + (checksumInterpolationValidityOption points gsLargeInterpParams) + checksumIterations + let leeSubproductRow <- runTimed + "guruswami-sudan-interp-lee-subproduct" "CBivariate" + "Lee-O'Sullivan subproduct" + "KoalaBear.Field" gsLargeInterpInputShape preset warmup leeMeasured + (fun _ ↦ koalaBearLeeSubproductInterpContext.interpolate points gsLargeInterpParams) + (checksumInterpolationValidityOption points gsLargeInterpParams) + checksumIterations + let approximantDirectRow <- runTimed + "guruswami-sudan-interp-approximant-direct" "CBivariate" + "Approximant basis direct" + "KoalaBear.Field" gsLargeInterpInputShape preset warmup approximantDirectMeasured + (fun _ ↦ koalaBearApproximantBasisDirectInterpContext.interpolate points + gsLargeInterpParams) + (checksumInterpolationValidityOption points gsLargeInterpParams) + checksumIterations + let approximantSubproductRow <- runTimed + "guruswami-sudan-interp-approximant-subproduct" "CBivariate" + "Approximant basis subproduct" + "KoalaBear.Field" gsLargeInterpInputShape preset warmup approximantSubproductMeasured + (fun _ ↦ koalaBearApproximantBasisSubproductInterpContext.interpolate points + gsLargeInterpParams) + (checksumInterpolationValidityOption points gsLargeInterpParams) + checksumIterations + let fastKoetterRow <- runTimed + "guruswami-sudan-interp-koetter-fast" "CBivariate" + "Koetter" + "KoalaBear.Fast.Field" gsLargeInterpInputShape preset warmup fastKoetterMeasured + (fun _ ↦ koetterInterpolate fastPoints gsLargeInterpParams) + (checksumInterpolationValidityOption fastPoints gsLargeInterpParams) + checksumIterations + let fastLeeDirectRow <- runTimed + "guruswami-sudan-interp-lee-direct-fast" "CBivariate" + "Lee-O'Sullivan direct" + "KoalaBear.Fast.Field" gsLargeInterpInputShape preset warmup fastLeeMeasured + (fun _ ↦ fastKoalaBearLeeDirectInterpContext.interpolate fastPoints + gsLargeInterpParams) + (checksumInterpolationValidityOption fastPoints gsLargeInterpParams) + checksumIterations + let fastLeeSubproductRow <- runTimed + "guruswami-sudan-interp-lee-subproduct-fast" "CBivariate" + "Lee-O'Sullivan subproduct" + "KoalaBear.Fast.Field" gsLargeInterpInputShape preset warmup fastLeeMeasured + (fun _ ↦ fastKoalaBearLeeSubproductInterpContext.interpolate fastPoints + gsLargeInterpParams) + (checksumInterpolationValidityOption fastPoints gsLargeInterpParams) + checksumIterations + let fastApproximantDirectRow <- runTimed + "guruswami-sudan-interp-approximant-direct-fast" "CBivariate" + "Approximant basis direct" + "KoalaBear.Fast.Field" gsLargeInterpInputShape preset warmup + fastApproximantDirectMeasured + (fun _ ↦ fastKoalaBearApproximantBasisDirectInterpContext.interpolate fastPoints + gsLargeInterpParams) + (checksumInterpolationValidityOption fastPoints gsLargeInterpParams) + checksumIterations + let fastApproximantSubproductRow <- runTimed + "guruswami-sudan-interp-approximant-subproduct-fast" "CBivariate" + "Approximant basis subproduct" + "KoalaBear.Fast.Field" gsLargeInterpInputShape preset warmup + fastApproximantSubproductMeasured + (fun _ ↦ fastKoalaBearApproximantBasisSubproductInterpContext.interpolate fastPoints + gsLargeInterpParams) + (checksumInterpolationValidityOption fastPoints gsLargeInterpParams) + checksumIterations + pure ({ + groupKey := "guruswami-sudan-interp-large-koalabear", + title := "Guruswami-Sudan interpolation, large (KoalaBear)", + records := #[ + koetterRow, leeDirectRow, leeSubproductRow, + approximantDirectRow, approximantSubproductRow, + fastKoetterRow, fastLeeDirectRow, fastLeeSubproductRow, + fastApproximantDirectRow, fastApproximantSubproductRow ] }, gen) +private def runGsLeeSetupLargeKoala (preset : BenchPreset) (gen : StdGen) : + IO (Prod BenchGroup StdGen) := do + let (coeffs, gen) := (koalaBearArray gsLargeInterpMessageDegree false).run gen + let message := cpolyOfArray coeffs + let fastMessage := cpolyOfArray (koalaBearFastArray coeffs) + let points := gsLargeBenchmarkPoints message + let fastPoints := gsLargeBenchmarkPoints fastMessage + let directV : CPolynomial.VanishingPolynomialContext KoalaBear.Field := + CPolynomial.VanishingPolynomialContext.direct + let subproductV : CPolynomial.VanishingPolynomialContext KoalaBear.Field := + CPolynomial.VanishingPolynomialContext.subproduct koalaBearNttFastMulContext + let hornerE : CPolynomial.BatchEvalContext KoalaBear.Field := + CPolynomial.BatchEvalContext.horner KoalaBear.Field + let subproductE : CPolynomial.BatchEvalContext KoalaBear.Field := + koalaBearNttFastBatchEvalContext + let fastDirectV : CPolynomial.VanishingPolynomialContext KoalaBear.Fast.Field := + CPolynomial.VanishingPolynomialContext.direct + let fastSubproductV : CPolynomial.VanishingPolynomialContext KoalaBear.Fast.Field := + CPolynomial.VanishingPolynomialContext.subproduct fastKoalaBearNttFastMulContext + let fastHornerE : CPolynomial.BatchEvalContext KoalaBear.Fast.Field := + CPolynomial.BatchEvalContext.horner KoalaBear.Fast.Field + let fastSubproductE : CPolynomial.BatchEvalContext KoalaBear.Fast.Field := + fastKoalaBearNttFastBatchEvalContext + let warmup := gsWarmupIterations preset + let measured := preset.selectNat 5 1 1 + let fastMeasured := preset.selectNat 25 4 1 + let checksumIterations := groupChecksumIterations measured [ + measured, fastMeasured, fastMeasured + ] + let directRow <- runTimed + "guruswami-sudan-lee-setup-direct" "PolynomialMatrix" + "Lee-O'Sullivan basis setup (direct vanishing)" + "KoalaBear.Field" gsLargeInterpInputShape preset warmup measured + (fun _ ↦ leeOSullivanBasisRows directV hornerE points gsLargeInterpParams) + (checksumPolynomialMatrix checksumKoalaBear) checksumIterations + let subproductRow <- runTimed + "guruswami-sudan-lee-setup-subproduct" "PolynomialMatrix" + "Lee-O'Sullivan basis setup (subproduct vanishing)" + "KoalaBear.Field" gsLargeInterpInputShape preset warmup measured + (fun _ ↦ leeOSullivanBasisRows subproductV subproductE points gsLargeInterpParams) + (checksumPolynomialMatrix checksumKoalaBear) checksumIterations + let fastDirectRow <- runTimed + "guruswami-sudan-lee-setup-direct-fast" "PolynomialMatrix" + "Lee-O'Sullivan basis setup (direct vanishing)" + "KoalaBear.Fast.Field" gsLargeInterpInputShape preset warmup fastMeasured + (fun _ ↦ leeOSullivanBasisRows fastDirectV fastHornerE fastPoints + gsLargeInterpParams) + (checksumPolynomialMatrix checksumKoalaBearFast) checksumIterations + let fastSubproductRow <- runTimed + "guruswami-sudan-lee-setup-subproduct-fast" "PolynomialMatrix" + "Lee-O'Sullivan basis setup (subproduct vanishing)" + "KoalaBear.Fast.Field" gsLargeInterpInputShape preset warmup fastMeasured + (fun _ ↦ leeOSullivanBasisRows fastSubproductV fastSubproductE fastPoints + gsLargeInterpParams) + (checksumPolynomialMatrix checksumKoalaBearFast) checksumIterations + pure ({ + groupKey := "guruswami-sudan-lee-setup-large-koalabear", + title := "Guruswami-Sudan Lee-O'Sullivan setup, large (KoalaBear)", + records := #[directRow, subproductRow, fastDirectRow, fastSubproductRow] + }, gen) + +private def runGsHasseKoala (preset : BenchPreset) (gen : StdGen) : + IO (Prod BenchGroup StdGen) := do + let (coeffs, gen) := (koalaBearArray gsMessageDegree false).run gen + let message := cpolyOfArray coeffs + let fastMessage := cpolyOfArray (koalaBearFastArray coeffs) + let Q := multiplicityBenchmarkQ message + let fastQ := multiplicityBenchmarkQ fastMessage + let points := codewordPoints message + let fastPoints := codewordPoints fastMessage + let warmup := gsWarmupIterations preset + let measured := preset.selectNat 80 10 2 + let fastMeasured := preset.selectNat 500 70 15 + let checksumIterations := groupChecksumIterations measured [ + measured, + measured, + fastMeasured, + fastMeasured, + fastMeasured + ] + let row <- runTimed + "guruswami-sudan-hasse-check" "CBivariate" "Hasse multiplicity check" + "KoalaBear.Field" gsMultiplicityShape preset warmup measured + (fun _ ↦ CBivariate.satisfiesMultiplicityConstraintsBool Q points gsCheckMultiplicity) + checksumBool checksumIterations + let genericRow <- runTimed + "guruswami-sudan-hasse-check-generic" "CBivariate" + "Generic multiplicity check" + "KoalaBear.Field" gsMultiplicityShape preset warmup measured + (fun _ ↦ points.all fun point ↦ + CBivariate.checkMultiplicity Q gsCheckMultiplicity point.1 point.2) + checksumBool checksumIterations + let shiftOnceRow <- runTimed + "guruswami-sudan-hasse-check-shift-once" "CBivariate" + "Shift-once multiplicity check" + "KoalaBear.Field" gsMultiplicityShape preset warmup measured + (fun _ ↦ points.all fun point ↦ + checkMultiplicityShiftOnce Q gsCheckMultiplicity point.1 point.2) + checksumBool checksumIterations + let fastRow <- runTimed + "guruswami-sudan-hasse-check-fast" "CBivariate" "Hasse multiplicity check" + "KoalaBear.Fast.Field" gsMultiplicityShape preset warmup fastMeasured + (fun _ ↦ CBivariate.satisfiesMultiplicityConstraintsBool fastQ fastPoints + gsCheckMultiplicity) + checksumBool checksumIterations + let fastGenericRow <- runTimed + "guruswami-sudan-hasse-check-generic-fast" "CBivariate" + "Generic multiplicity check" + "KoalaBear.Fast.Field" gsMultiplicityShape preset warmup fastMeasured + (fun _ ↦ fastPoints.all fun point ↦ + CBivariate.checkMultiplicity fastQ gsCheckMultiplicity point.1 point.2) + checksumBool checksumIterations + let fastShiftOnceRow <- runTimed + "guruswami-sudan-hasse-check-shift-once-fast" "CBivariate" + "Shift-once multiplicity check" + "KoalaBear.Fast.Field" gsMultiplicityShape preset warmup fastMeasured + (fun _ ↦ fastPoints.all fun point ↦ + checkMultiplicityShiftOnce fastQ gsCheckMultiplicity point.1 point.2) + checksumBool checksumIterations + pure ({ + groupKey := "guruswami-sudan-hasse-koalabear", + title := "Guruswami-Sudan Hasse multiplicity checking (KoalaBear)", + records := #[row, genericRow, shiftOnceRow, fastRow, fastGenericRow, fastShiftOnceRow] + }, gen) + +private def runGsComposeKoala (preset : BenchPreset) (gen : StdGen) : + IO (Prod BenchGroup StdGen) := do + let (coeffs, gen) := (koalaBearArray gsMessageDegree false).run gen + let message := cpolyOfArray coeffs + let fastMessage := cpolyOfArray (koalaBearFastArray coeffs) + let Q := multiplicityBenchmarkQ message + let fastQ := multiplicityBenchmarkQ fastMessage + let warmup := gsWarmupIterations preset + let measured := preset.selectNat 80 10 2 + let fastMeasured := preset.selectNat 500 70 15 + let checksumIterations := groupChecksumIterations measured [fastMeasured] + let row <- runTimed + "guruswami-sudan-compose-y" "CPolynomial" "Composition in Y" + "KoalaBear.Field" gsMultiplicityShape preset warmup measured + (fun _ ↦ CBivariate.composeY Q message) + (checksumCPolynomial checksumKoalaBear) checksumIterations + let fastRow <- runTimed + "guruswami-sudan-compose-y-fast" "CPolynomial" "Composition in Y" + "KoalaBear.Fast.Field" gsMultiplicityShape preset warmup fastMeasured + (fun _ ↦ CBivariate.composeY fastQ fastMessage) + (checksumCPolynomial checksumKoalaBearFast) checksumIterations + pure ({ + groupKey := "guruswami-sudan-compose-koalabear", + title := "Guruswami-Sudan composition in Y (KoalaBear)", + records := #[row, fastRow] + }, gen) + private def runGsRootKoala (preset : BenchPreset) (gen : StdGen) : IO (Prod BenchGroup StdGen) := do let (coeffs, gen) := (koalaBearArray gsMessageDegree false).run gen @@ -226,8 +545,8 @@ private def runGsRootKoala (preset : BenchPreset) (gen : StdGen) : pure ({ groupKey := "guruswami-sudan-root-koalabear", title := "Guruswami-Sudan root finding (KoalaBear)", - records := #[row, nttFastRow, alekhnovichRow, alekhnovichNttFastRow, - fastRow, fastNttFastRow, alekhnovichFastRow, alekhnovichFastNttFastRow] + records := #[row, nttFastRow, fastRow, fastNttFastRow, alekhnovichRow, + alekhnovichNttFastRow, alekhnovichFastRow, alekhnovichFastNttFastRow] }, gen) private def runGsPackedFilterKoala (preset : BenchPreset) (gen : StdGen) : @@ -277,13 +596,25 @@ def guruswamiSudanTasks : List BenchTask := [ BenchTask.fromGroupRunner (guruswamiSudanGroupInfos.getD 2 ⟨"guruswami-sudan-interp-small-koalabear", ""⟩) runGsInterpolationSmallKoala, BenchTask.fromGroupRunner (guruswamiSudanGroupInfos.getD 3 - ⟨"guruswami-sudan-root-koalabear", ""⟩) runGsRootKoala, + ⟨"guruswami-sudan-interp-large-koalabear", ""⟩) runGsInterpolationLargeKoala, BenchTask.fromGroupRunner (guruswamiSudanGroupInfos.getD 4 - ⟨"guruswami-sudan-core-small-koalabear", ""⟩) runGsCoreSmallKoala, + ⟨"guruswami-sudan-lee-setup-large-koalabear", ""⟩) runGsLeeSetupLargeKoala, BenchTask.fromGroupRunner (guruswamiSudanGroupInfos.getD 5 - ⟨"guruswami-sudan-packed-filter-koalabear", ""⟩) runGsPackedFilterKoala, + ⟨"guruswami-sudan-hasse-koalabear", ""⟩) runGsHasseKoala, BenchTask.fromGroupRunner (guruswamiSudanGroupInfos.getD 6 - ⟨"guruswami-sudan-filtered-core-small-koalabear", ""⟩) runGsFilteredCoreSmallKoala + ⟨"guruswami-sudan-compose-koalabear", ""⟩) runGsComposeKoala, + BenchTask.fromGroupRunner (guruswamiSudanGroupInfos.getD 7 + ⟨"guruswami-sudan-root-koalabear", ""⟩) runGsRootKoala, + BenchTask.fromGroupRunner (guruswamiSudanGroupInfos.getD 8 + ⟨"guruswami-sudan-core-small-koalabear", ""⟩) runGsCoreSmallKoala, + BenchTask.fromGroupRunner (guruswamiSudanGroupInfos.getD 9 + ⟨"guruswami-sudan-core-large-koalabear", ""⟩) runGsCoreLargeKoala, + BenchTask.fromGroupRunner (guruswamiSudanGroupInfos.getD 10 + ⟨"guruswami-sudan-packed-filter-koalabear", ""⟩) runGsPackedFilterKoala, + BenchTask.fromGroupRunner (guruswamiSudanGroupInfos.getD 11 + ⟨"guruswami-sudan-filtered-core-small-koalabear", ""⟩) runGsFilteredCoreSmallKoala, + BenchTask.fromGroupRunner (guruswamiSudanGroupInfos.getD 12 + ⟨"guruswami-sudan-filtered-core-large-koalabear", ""⟩) runGsFilteredCoreLargeKoala ] ++ guruswamiSudanReceivedWordTasks end CompPolyBench diff --git a/bench/CompPolyBench/Bivariate/GuruswamiSudan/Core.lean b/bench/CompPolyBench/Bivariate/GuruswamiSudan/Core.lean index 528afb25..5ffaed5c 100644 --- a/bench/CompPolyBench/Bivariate/GuruswamiSudan/Core.lean +++ b/bench/CompPolyBench/Bivariate/GuruswamiSudan/Core.lean @@ -24,62 +24,88 @@ def runGsCoreSmallKoala (preset : BenchPreset) (gen : StdGen) : let fastMessage := cpolyOfArray (koalaBearFastArray coeffs) let points := gsSmallBenchmarkPoints message let fastPoints := gsSmallBenchmarkPoints fastMessage - let alekRootContext := - alekhnovichRootContext KoalaBear.Field koalaBearFieldRootContext - let fastAlekRootContext := - alekhnovichRootContext KoalaBear.Fast.Field fastKoalaBearFieldRootContext + let rothRootContext := koalaBearRothRootContext + let fastRothRootContext := fastKoalaBearRothRootContext + let alekRootContext := koalaBearAlekhnovichRootContext + let fastAlekRootContext := fastKoalaBearAlekhnovichRootContext let warmup := gsWarmupIterations preset let denseMeasured := preset.selectNat 1 1 1 - let leeDirectMeasured := preset.selectNat 100 15 3 - let leeSubproductMeasured := preset.selectNat 90 13 3 + let koetterMeasured := preset.selectNat 10 2 1 + let leeDirectMeasured := preset.selectNat 60 8 2 + let leeSubproductMeasured := preset.selectNat 50 7 1 + let approximantDirectMeasured := preset.selectNat 30 5 1 + let approximantSubproductMeasured := preset.selectNat 1 1 1 let fastDenseMeasured := preset.selectNat 2 1 1 - let fastLeeDirectMeasured := preset.selectNat 600 90 20 - let fastLeeSubproductMeasured := preset.selectNat 400 60 10 + let fastKoetterMeasured := preset.selectNat 70 10 2 + let fastLeeDirectMeasured := preset.selectNat 400 60 10 + let fastLeeSubproductMeasured := preset.selectNat 300 40 10 + let fastApproximantDirectMeasured := preset.selectNat 200 30 5 + let fastApproximantSubproductMeasured := preset.selectNat 1 1 1 let alekDenseMeasured := denseMeasured + let alekKoetterMeasured := koetterMeasured let alekLeeDirectMeasured := leeDirectMeasured let alekLeeSubproductMeasured := leeSubproductMeasured + let alekApproximantDirectMeasured := approximantDirectMeasured + let alekApproximantSubproductMeasured := approximantSubproductMeasured let fastAlekDenseMeasured := fastDenseMeasured + let fastAlekKoetterMeasured := fastKoetterMeasured let fastAlekLeeDirectMeasured := fastLeeDirectMeasured let fastAlekLeeSubproductMeasured := fastLeeSubproductMeasured + let fastAlekApproximantDirectMeasured := fastApproximantDirectMeasured + let fastAlekApproximantSubproductMeasured := fastApproximantSubproductMeasured let checksumIterations := groupChecksumIterations denseMeasured [ - leeDirectMeasured, leeSubproductMeasured, fastDenseMeasured, - fastLeeDirectMeasured, fastLeeSubproductMeasured, alekDenseMeasured, - alekLeeDirectMeasured, alekLeeSubproductMeasured, fastAlekDenseMeasured, - fastAlekLeeDirectMeasured, fastAlekLeeSubproductMeasured + koetterMeasured, leeDirectMeasured, leeSubproductMeasured, fastDenseMeasured, + approximantDirectMeasured, approximantSubproductMeasured, fastKoetterMeasured, + fastLeeDirectMeasured, fastLeeSubproductMeasured, fastApproximantDirectMeasured, + fastApproximantSubproductMeasured, alekDenseMeasured, alekKoetterMeasured, + alekLeeDirectMeasured, alekLeeSubproductMeasured, alekApproximantDirectMeasured, + alekApproximantSubproductMeasured, fastAlekDenseMeasured, fastAlekKoetterMeasured, + fastAlekLeeDirectMeasured, fastAlekLeeSubproductMeasured, + fastAlekApproximantDirectMeasured, fastAlekApproximantSubproductMeasured ] let denseRow <- runTimed "guruswami-sudan-core-dense-small" "CBivariate" "Dense linear + RR roots" "KoalaBear.Field" gsSmallInterpInputShape preset warmup denseMeasured - (fun _ ↦ gsCore points koalaBearDenseInterpContext koalaBearRothRootContext + (fun _ ↦ gsCore points (denseInterpContext KoalaBear.Field) rothRootContext gsSmallParams) checksumPolynomialArrayKoala checksumIterations let denseAlekRow <- runTimed "guruswami-sudan-core-dense-small-alekhnovich" "CBivariate" "Dense linear + Alekhnovich roots" "KoalaBear.Field" gsSmallInterpInputShape preset warmup alekDenseMeasured - (fun _ ↦ gsCore points koalaBearDenseInterpContext alekRootContext + (fun _ ↦ gsCore points (denseInterpContext KoalaBear.Field) alekRootContext gsSmallParams) checksumPolynomialArrayKoala checksumIterations + let koetterRow <- runTimed + "guruswami-sudan-core-koetter-small" "CBivariate" + "Koetter + RR roots" + "KoalaBear.Field" gsSmallInterpInputShape preset warmup koetterMeasured + (fun _ ↦ gsCore points koalaBearKoetterInterpContext rothRootContext gsSmallParams) + checksumPolynomialArrayKoala checksumIterations + let koetterAlekRow <- runTimed + "guruswami-sudan-core-koetter-small-alekhnovich" "CBivariate" + "Koetter + Alekhnovich roots" + "KoalaBear.Field" gsSmallInterpInputShape preset warmup alekKoetterMeasured + (fun _ ↦ gsCore points koalaBearKoetterInterpContext alekRootContext gsSmallParams) + checksumPolynomialArrayKoala checksumIterations let leeDirectRow <- runTimed "guruswami-sudan-core-lee-direct-small" "CBivariate" "Lee-O'Sullivan direct + RR roots" "KoalaBear.Field" gsSmallInterpInputShape preset warmup leeDirectMeasured - (fun _ ↦ gsCore points koalaBearLeeDirectInterpContext koalaBearRothRootContext - gsSmallParams) + (fun _ ↦ gsCore points koalaBearLeeDirectInterpContext rothRootContext gsSmallParams) checksumPolynomialArrayKoala checksumIterations let leeDirectAlekRow <- runTimed "guruswami-sudan-core-lee-direct-small-alekhnovich" "CBivariate" "Lee-O'Sullivan direct + Alekhnovich roots" "KoalaBear.Field" gsSmallInterpInputShape preset warmup alekLeeDirectMeasured - (fun _ ↦ gsCore points koalaBearLeeDirectInterpContext alekRootContext - gsSmallParams) + (fun _ ↦ gsCore points koalaBearLeeDirectInterpContext alekRootContext gsSmallParams) checksumPolynomialArrayKoala checksumIterations let leeSubproductRow <- runTimed "guruswami-sudan-core-lee-subproduct-small" "CBivariate" "Lee-O'Sullivan subproduct + RR roots" "KoalaBear.Field" gsSmallInterpInputShape preset warmup leeSubproductMeasured - (fun _ ↦ gsCore points koalaBearLeeSubproductInterpContext koalaBearRothRootContext + (fun _ ↦ gsCore points koalaBearLeeSubproductInterpContext rothRootContext gsSmallParams) checksumPolynomialArrayKoala checksumIterations let leeSubproductAlekRow <- runTimed @@ -89,12 +115,42 @@ def runGsCoreSmallKoala (preset : BenchPreset) (gen : StdGen) : (fun _ ↦ gsCore points koalaBearLeeSubproductInterpContext alekRootContext gsSmallParams) checksumPolynomialArrayKoala checksumIterations + let approximantDirectRow <- runTimed + "guruswami-sudan-core-approximant-direct-small" "CBivariate" + "Approximant basis direct + RR roots" + "KoalaBear.Field" gsSmallInterpInputShape preset warmup approximantDirectMeasured + (fun _ ↦ gsCore points koalaBearApproximantBasisDirectInterpContext + rothRootContext gsSmallParams) + checksumPolynomialArrayKoala checksumIterations + let approximantDirectAlekRow <- runTimed + "guruswami-sudan-core-approximant-direct-small-alekhnovich" "CBivariate" + "Approximant basis direct + Alekhnovich roots" + "KoalaBear.Field" gsSmallInterpInputShape preset warmup alekApproximantDirectMeasured + (fun _ ↦ gsCore points koalaBearApproximantBasisDirectInterpContext + alekRootContext gsSmallParams) + checksumPolynomialArrayKoala checksumIterations + let approximantSubproductRow <- runTimed + "guruswami-sudan-core-approximant-subproduct-small" "CBivariate" + "Approximant basis subproduct + RR roots" + "KoalaBear.Field" gsSmallInterpInputShape preset warmup + approximantSubproductMeasured + (fun _ ↦ gsCore points koalaBearApproximantBasisSubproductInterpContext + rothRootContext gsSmallParams) + checksumPolynomialArrayKoala checksumIterations + let approximantSubproductAlekRow <- runTimed + "guruswami-sudan-core-approximant-subproduct-small-alekhnovich" "CBivariate" + "Approximant basis subproduct + Alekhnovich roots" + "KoalaBear.Field" gsSmallInterpInputShape preset warmup + alekApproximantSubproductMeasured + (fun _ ↦ gsCore points koalaBearApproximantBasisSubproductInterpContext + alekRootContext gsSmallParams) + checksumPolynomialArrayKoala checksumIterations let fastDenseRow <- runTimed "guruswami-sudan-core-dense-small-fast" "CBivariate" "Dense linear + RR roots" "KoalaBear.Fast.Field" gsSmallInterpInputShape preset warmup fastDenseMeasured (fun _ ↦ - gsCore fastPoints fastKoalaBearDenseInterpContext fastKoalaBearRothRootContext + gsCore fastPoints (denseInterpContext KoalaBear.Fast.Field) fastRothRootContext gsSmallParams) checksumPolynomialArrayKoalaFast checksumIterations let fastDenseAlekRow <- runTimed @@ -102,50 +158,297 @@ def runGsCoreSmallKoala (preset : BenchPreset) (gen : StdGen) : "Dense linear + Alekhnovich roots" "KoalaBear.Fast.Field" gsSmallInterpInputShape preset warmup fastAlekDenseMeasured (fun _ ↦ - gsCore fastPoints fastKoalaBearDenseInterpContext fastAlekRootContext + gsCore fastPoints (denseInterpContext KoalaBear.Fast.Field) fastAlekRootContext gsSmallParams) checksumPolynomialArrayKoalaFast checksumIterations + let fastKoetterRow <- runTimed + "guruswami-sudan-core-koetter-small-fast" "CBivariate" + "Koetter + RR roots" + "KoalaBear.Fast.Field" gsSmallInterpInputShape preset warmup fastKoetterMeasured + (fun _ ↦ gsCore fastPoints fastKoalaBearKoetterInterpContext fastRothRootContext + gsSmallParams) + checksumPolynomialArrayKoalaFast checksumIterations + let fastKoetterAlekRow <- runTimed + "guruswami-sudan-core-koetter-small-alekhnovich-fast" "CBivariate" + "Koetter + Alekhnovich roots" + "KoalaBear.Fast.Field" gsSmallInterpInputShape preset warmup fastAlekKoetterMeasured + (fun _ ↦ gsCore fastPoints fastKoalaBearKoetterInterpContext fastAlekRootContext + gsSmallParams) + checksumPolynomialArrayKoalaFast checksumIterations let fastLeeDirectRow <- runTimed "guruswami-sudan-core-lee-direct-small-fast" "CBivariate" "Lee-O'Sullivan direct + RR roots" "KoalaBear.Fast.Field" gsSmallInterpInputShape preset warmup fastLeeDirectMeasured - (fun _ ↦ - gsCore fastPoints fastKoalaBearLeeDirectInterpContext fastKoalaBearRothRootContext - gsSmallParams) + (fun _ ↦ gsCore fastPoints fastKoalaBearLeeDirectInterpContext fastRothRootContext + gsSmallParams) checksumPolynomialArrayKoalaFast checksumIterations let fastLeeDirectAlekRow <- runTimed "guruswami-sudan-core-lee-direct-small-alekhnovich-fast" "CBivariate" "Lee-O'Sullivan direct + Alekhnovich roots" "KoalaBear.Fast.Field" gsSmallInterpInputShape preset warmup fastAlekLeeDirectMeasured - (fun _ ↦ - gsCore fastPoints fastKoalaBearLeeDirectInterpContext fastAlekRootContext - gsSmallParams) + (fun _ ↦ gsCore fastPoints fastKoalaBearLeeDirectInterpContext fastAlekRootContext + gsSmallParams) checksumPolynomialArrayKoalaFast checksumIterations let fastLeeSubproductRow <- runTimed "guruswami-sudan-core-lee-subproduct-small-fast" "CBivariate" "Lee-O'Sullivan subproduct + RR roots" "KoalaBear.Fast.Field" gsSmallInterpInputShape preset warmup fastLeeSubproductMeasured - (fun _ ↦ - gsCore fastPoints fastKoalaBearLeeSubproductInterpContext - fastKoalaBearRothRootContext gsSmallParams) + (fun _ ↦ gsCore fastPoints fastKoalaBearLeeSubproductInterpContext fastRothRootContext + gsSmallParams) checksumPolynomialArrayKoalaFast checksumIterations let fastLeeSubproductAlekRow <- runTimed "guruswami-sudan-core-lee-subproduct-small-alekhnovich-fast" "CBivariate" "Lee-O'Sullivan subproduct + Alekhnovich roots" + "KoalaBear.Fast.Field" gsSmallInterpInputShape preset warmup fastAlekLeeSubproductMeasured + (fun _ ↦ gsCore fastPoints fastKoalaBearLeeSubproductInterpContext fastAlekRootContext + gsSmallParams) + checksumPolynomialArrayKoalaFast checksumIterations + let fastApproximantDirectRow <- runTimed + "guruswami-sudan-core-approximant-direct-small-fast" "CBivariate" + "Approximant basis direct + RR roots" "KoalaBear.Fast.Field" gsSmallInterpInputShape preset warmup - fastAlekLeeSubproductMeasured - (fun _ ↦ - gsCore fastPoints fastKoalaBearLeeSubproductInterpContext - fastAlekRootContext gsSmallParams) + fastApproximantDirectMeasured + (fun _ ↦ gsCore fastPoints fastKoalaBearApproximantBasisDirectInterpContext + fastRothRootContext gsSmallParams) + checksumPolynomialArrayKoalaFast checksumIterations + let fastApproximantDirectAlekRow <- runTimed + "guruswami-sudan-core-approximant-direct-small-alekhnovich-fast" "CBivariate" + "Approximant basis direct + Alekhnovich roots" + "KoalaBear.Fast.Field" gsSmallInterpInputShape preset warmup + fastAlekApproximantDirectMeasured + (fun _ ↦ gsCore fastPoints fastKoalaBearApproximantBasisDirectInterpContext + fastAlekRootContext gsSmallParams) + checksumPolynomialArrayKoalaFast checksumIterations + let fastApproximantSubproductRow <- runTimed + "guruswami-sudan-core-approximant-subproduct-small-fast" "CBivariate" + "Approximant basis subproduct + RR roots" + "KoalaBear.Fast.Field" gsSmallInterpInputShape preset warmup + fastApproximantSubproductMeasured + (fun _ ↦ gsCore fastPoints fastKoalaBearApproximantBasisSubproductInterpContext + fastRothRootContext gsSmallParams) + checksumPolynomialArrayKoalaFast checksumIterations + let fastApproximantSubproductAlekRow <- runTimed + "guruswami-sudan-core-approximant-subproduct-small-alekhnovich-fast" "CBivariate" + "Approximant basis subproduct + Alekhnovich roots" + "KoalaBear.Fast.Field" gsSmallInterpInputShape preset warmup + fastAlekApproximantSubproductMeasured + (fun _ ↦ gsCore fastPoints fastKoalaBearApproximantBasisSubproductInterpContext + fastAlekRootContext gsSmallParams) checksumPolynomialArrayKoalaFast checksumIterations pure ({ groupKey := "guruswami-sudan-core-small-koalabear", title := "Guruswami-Sudan full core, small (KoalaBear)", records := #[ - denseRow, denseAlekRow, leeDirectRow, leeDirectAlekRow, - leeSubproductRow, leeSubproductAlekRow, - fastDenseRow, fastDenseAlekRow, fastLeeDirectRow, - fastLeeDirectAlekRow, fastLeeSubproductRow, fastLeeSubproductAlekRow + denseRow, denseAlekRow, koetterRow, koetterAlekRow, leeDirectRow, + leeDirectAlekRow, leeSubproductRow, leeSubproductAlekRow, approximantDirectRow, + approximantDirectAlekRow, approximantSubproductRow, approximantSubproductAlekRow, + fastDenseRow, fastDenseAlekRow, fastKoetterRow, fastKoetterAlekRow, + fastLeeDirectRow, fastLeeDirectAlekRow, fastLeeSubproductRow, + fastLeeSubproductAlekRow, fastApproximantDirectRow, fastApproximantDirectAlekRow, + fastApproximantSubproductRow, fastApproximantSubproductAlekRow + ] + }, gen) + +def runGsCoreLargeKoala (preset : BenchPreset) (gen : StdGen) : + IO (Prod BenchGroup StdGen) := do + let (coeffs, gen) := (koalaBearArray gsLargeInterpMessageDegree false).run gen + let message := cpolyOfArray coeffs + let fastMessage := cpolyOfArray (koalaBearFastArray coeffs) + let points := gsLargeBenchmarkPoints message + let fastPoints := gsLargeBenchmarkPoints fastMessage + let rothRootContext := koalaBearRothRootContext + let fastRothRootContext := fastKoalaBearRothRootContext + let alekRootContext := koalaBearAlekhnovichRootContext + let fastAlekRootContext := fastKoalaBearAlekhnovichRootContext + let warmup := gsWarmupIterations preset + let koetterMeasured := preset.selectNat 1 1 1 + let leeMeasured := preset.selectNat 1 1 1 + let approximantDirectMeasured := preset.selectNat 1 1 1 + let approximantSubproductMeasured := preset.selectNat 1 1 1 + let fastKoetterMeasured := preset.selectNat 1 1 1 + let fastLeeMeasured := preset.selectNat 1 1 1 + let fastApproximantDirectMeasured := preset.selectNat 1 1 1 + let fastApproximantSubproductMeasured := preset.selectNat 1 1 1 + let alekKoetterMeasured := koetterMeasured + let alekLeeMeasured := leeMeasured + let alekApproximantDirectMeasured := approximantDirectMeasured + let alekApproximantSubproductMeasured := approximantSubproductMeasured + let fastAlekKoetterMeasured := fastKoetterMeasured + let fastAlekLeeMeasured := fastLeeMeasured + let fastAlekApproximantDirectMeasured := fastApproximantDirectMeasured + let fastAlekApproximantSubproductMeasured := fastApproximantSubproductMeasured + let checksumIterations := groupChecksumIterations koetterMeasured [ + leeMeasured, leeMeasured, approximantDirectMeasured, approximantSubproductMeasured, + fastKoetterMeasured, fastLeeMeasured, fastLeeMeasured, + fastApproximantDirectMeasured, fastApproximantSubproductMeasured, + alekKoetterMeasured, alekLeeMeasured, alekLeeMeasured, alekApproximantDirectMeasured, + alekApproximantSubproductMeasured, fastAlekKoetterMeasured, fastAlekLeeMeasured, + fastAlekLeeMeasured, fastAlekApproximantDirectMeasured, + fastAlekApproximantSubproductMeasured + ] + let koetterRow <- runTimed + "guruswami-sudan-core-koetter-roth" "CBivariate" + "Koetter + RR roots" + "KoalaBear.Field" gsLargeInterpInputShape preset warmup koetterMeasured + (fun _ ↦ gsCore points koalaBearKoetterInterpContext rothRootContext + gsLargeInterpParams) + checksumPolynomialArrayKoala checksumIterations + let koetterAlekRow <- runTimed + "guruswami-sudan-core-koetter-alekhnovich" "CBivariate" + "Koetter + Alekhnovich roots" + "KoalaBear.Field" gsLargeInterpInputShape preset warmup alekKoetterMeasured + (fun _ ↦ gsCore points koalaBearKoetterInterpContext alekRootContext + gsLargeInterpParams) + checksumPolynomialArrayKoala checksumIterations + let leeDirectRow <- runTimed + "guruswami-sudan-core-lee-direct-roth" "CBivariate" + "Lee-O'Sullivan direct + RR roots" + "KoalaBear.Field" gsLargeInterpInputShape preset warmup leeMeasured + (fun _ ↦ gsCore points koalaBearLeeDirectInterpContext rothRootContext + gsLargeInterpParams) + checksumPolynomialArrayKoala checksumIterations + let leeDirectAlekRow <- runTimed + "guruswami-sudan-core-lee-direct-alekhnovich" "CBivariate" + "Lee-O'Sullivan direct + Alekhnovich roots" + "KoalaBear.Field" gsLargeInterpInputShape preset warmup alekLeeMeasured + (fun _ ↦ gsCore points koalaBearLeeDirectInterpContext alekRootContext + gsLargeInterpParams) + checksumPolynomialArrayKoala checksumIterations + let leeSubproductRow <- runTimed + "guruswami-sudan-core-lee-subproduct-roth" "CBivariate" + "Lee-O'Sullivan subproduct + RR roots" + "KoalaBear.Field" gsLargeInterpInputShape preset warmup leeMeasured + (fun _ ↦ gsCore points koalaBearLeeSubproductInterpContext rothRootContext + gsLargeInterpParams) + checksumPolynomialArrayKoala checksumIterations + let leeSubproductAlekRow <- runTimed + "guruswami-sudan-core-lee-subproduct-alekhnovich" "CBivariate" + "Lee-O'Sullivan subproduct + Alekhnovich roots" + "KoalaBear.Field" gsLargeInterpInputShape preset warmup alekLeeMeasured + (fun _ ↦ gsCore points koalaBearLeeSubproductInterpContext alekRootContext + gsLargeInterpParams) + checksumPolynomialArrayKoala checksumIterations + let approximantDirectRow <- runTimed + "guruswami-sudan-core-approximant-direct-roth" "CBivariate" + "Approximant basis direct + RR roots" + "KoalaBear.Field" gsLargeInterpInputShape preset warmup approximantDirectMeasured + (fun _ ↦ gsCore points koalaBearApproximantBasisDirectInterpContext + rothRootContext gsLargeInterpParams) + checksumPolynomialArrayKoala checksumIterations + let approximantDirectAlekRow <- runTimed + "guruswami-sudan-core-approximant-direct-alekhnovich" "CBivariate" + "Approximant basis direct + Alekhnovich roots" + "KoalaBear.Field" gsLargeInterpInputShape preset warmup alekApproximantDirectMeasured + (fun _ ↦ gsCore points koalaBearApproximantBasisDirectInterpContext + alekRootContext gsLargeInterpParams) + checksumPolynomialArrayKoala checksumIterations + let approximantSubproductRow <- runTimed + "guruswami-sudan-core-approximant-subproduct-roth" "CBivariate" + "Approximant basis subproduct + RR roots" + "KoalaBear.Field" gsLargeInterpInputShape preset warmup + approximantSubproductMeasured + (fun _ ↦ gsCore points koalaBearApproximantBasisSubproductInterpContext + rothRootContext gsLargeInterpParams) + checksumPolynomialArrayKoala checksumIterations + let approximantSubproductAlekRow <- runTimed + "guruswami-sudan-core-approximant-subproduct-alekhnovich" "CBivariate" + "Approximant basis subproduct + Alekhnovich roots" + "KoalaBear.Field" gsLargeInterpInputShape preset warmup + alekApproximantSubproductMeasured + (fun _ ↦ gsCore points koalaBearApproximantBasisSubproductInterpContext + alekRootContext gsLargeInterpParams) + checksumPolynomialArrayKoala checksumIterations + let fastKoetterRow <- runTimed + "guruswami-sudan-core-koetter-roth-fast" "CBivariate" + "Koetter + RR roots" + "KoalaBear.Fast.Field" gsLargeInterpInputShape preset warmup fastKoetterMeasured + (fun _ ↦ + gsCore fastPoints fastKoalaBearKoetterInterpContext fastRothRootContext + gsLargeInterpParams) + checksumPolynomialArrayKoalaFast checksumIterations + let fastKoetterAlekRow <- runTimed + "guruswami-sudan-core-koetter-alekhnovich-fast" "CBivariate" + "Koetter + Alekhnovich roots" + "KoalaBear.Fast.Field" gsLargeInterpInputShape preset warmup + fastAlekKoetterMeasured + (fun _ ↦ + gsCore fastPoints fastKoalaBearKoetterInterpContext fastAlekRootContext + gsLargeInterpParams) + checksumPolynomialArrayKoalaFast checksumIterations + let fastLeeDirectRow <- runTimed + "guruswami-sudan-core-lee-direct-roth-fast" "CBivariate" + "Lee-O'Sullivan direct + RR roots" + "KoalaBear.Fast.Field" gsLargeInterpInputShape preset warmup fastLeeMeasured + (fun _ ↦ + gsCore fastPoints fastKoalaBearLeeDirectInterpContext fastRothRootContext + gsLargeInterpParams) + checksumPolynomialArrayKoalaFast checksumIterations + let fastLeeDirectAlekRow <- runTimed + "guruswami-sudan-core-lee-direct-alekhnovich-fast" "CBivariate" + "Lee-O'Sullivan direct + Alekhnovich roots" + "KoalaBear.Fast.Field" gsLargeInterpInputShape preset warmup fastAlekLeeMeasured + (fun _ ↦ + gsCore fastPoints fastKoalaBearLeeDirectInterpContext fastAlekRootContext + gsLargeInterpParams) + checksumPolynomialArrayKoalaFast checksumIterations + let fastLeeSubproductRow <- runTimed + "guruswami-sudan-core-lee-subproduct-roth-fast" "CBivariate" + "Lee-O'Sullivan subproduct + RR roots" + "KoalaBear.Fast.Field" gsLargeInterpInputShape preset warmup fastLeeMeasured + (fun _ ↦ + gsCore fastPoints fastKoalaBearLeeSubproductInterpContext fastRothRootContext + gsLargeInterpParams) + checksumPolynomialArrayKoalaFast checksumIterations + let fastLeeSubproductAlekRow <- runTimed + "guruswami-sudan-core-lee-subproduct-alekhnovich-fast" "CBivariate" + "Lee-O'Sullivan subproduct + Alekhnovich roots" + "KoalaBear.Fast.Field" gsLargeInterpInputShape preset warmup fastAlekLeeMeasured + (fun _ ↦ + gsCore fastPoints fastKoalaBearLeeSubproductInterpContext fastAlekRootContext + gsLargeInterpParams) + checksumPolynomialArrayKoalaFast checksumIterations + let fastApproximantDirectRow <- runTimed + "guruswami-sudan-core-approximant-direct-roth-fast" "CBivariate" + "Approximant basis direct + RR roots" + "KoalaBear.Fast.Field" gsLargeInterpInputShape preset warmup + fastApproximantDirectMeasured + (fun _ ↦ gsCore fastPoints fastKoalaBearApproximantBasisDirectInterpContext + fastRothRootContext gsLargeInterpParams) + checksumPolynomialArrayKoalaFast checksumIterations + let fastApproximantDirectAlekRow <- runTimed + "guruswami-sudan-core-approximant-direct-alekhnovich-fast" "CBivariate" + "Approximant basis direct + Alekhnovich roots" + "KoalaBear.Fast.Field" gsLargeInterpInputShape preset warmup + fastAlekApproximantDirectMeasured + (fun _ ↦ gsCore fastPoints fastKoalaBearApproximantBasisDirectInterpContext + fastAlekRootContext gsLargeInterpParams) + checksumPolynomialArrayKoalaFast checksumIterations + let fastApproximantSubproductRow <- runTimed + "guruswami-sudan-core-approximant-subproduct-roth-fast" "CBivariate" + "Approximant basis subproduct + RR roots" + "KoalaBear.Fast.Field" gsLargeInterpInputShape preset warmup + fastApproximantSubproductMeasured + (fun _ ↦ gsCore fastPoints fastKoalaBearApproximantBasisSubproductInterpContext + fastRothRootContext gsLargeInterpParams) + checksumPolynomialArrayKoalaFast checksumIterations + let fastApproximantSubproductAlekRow <- runTimed + "guruswami-sudan-core-approximant-subproduct-alekhnovich-fast" "CBivariate" + "Approximant basis subproduct + Alekhnovich roots" + "KoalaBear.Fast.Field" gsLargeInterpInputShape preset warmup + fastAlekApproximantSubproductMeasured + (fun _ ↦ gsCore fastPoints fastKoalaBearApproximantBasisSubproductInterpContext + fastAlekRootContext gsLargeInterpParams) + checksumPolynomialArrayKoalaFast checksumIterations + pure ({ + groupKey := "guruswami-sudan-core-large-koalabear", + title := "Guruswami-Sudan full core, large (KoalaBear)", + records := #[ + koetterRow, koetterAlekRow, leeDirectRow, leeDirectAlekRow, leeSubproductRow, + leeSubproductAlekRow, approximantDirectRow, approximantDirectAlekRow, + approximantSubproductRow, approximantSubproductAlekRow, fastKoetterRow, + fastKoetterAlekRow, fastLeeDirectRow, fastLeeDirectAlekRow, fastLeeSubproductRow, + fastLeeSubproductAlekRow, fastApproximantDirectRow, fastApproximantDirectAlekRow, + fastApproximantSubproductRow, fastApproximantSubproductAlekRow ] }, gen) @@ -156,35 +459,51 @@ def runGsFilteredCoreSmallKoala (preset : BenchPreset) (gen : StdGen) : let fastMessage := cpolyOfArray (koalaBearFastArray coeffs) let points := gsSmallBenchmarkPoints message let fastPoints := gsSmallBenchmarkPoints fastMessage - let alekRootContext := - alekhnovichRootContext KoalaBear.Field koalaBearFieldRootContext - let fastAlekRootContext := - alekhnovichRootContext KoalaBear.Fast.Field fastKoalaBearFieldRootContext + let rothRootContext := koalaBearRothRootContext + let fastRothRootContext := fastKoalaBearRothRootContext + let alekRootContext := koalaBearAlekhnovichRootContext + let fastAlekRootContext := fastKoalaBearAlekhnovichRootContext let warmup := gsWarmupIterations preset let denseMeasured := preset.selectNat 1 1 1 - let leeDirectMeasured := preset.selectNat 100 15 3 - let leeSubproductMeasured := preset.selectNat 90 13 3 + let koetterMeasured := preset.selectNat 10 2 1 + let leeDirectMeasured := preset.selectNat 60 8 2 + let leeSubproductMeasured := preset.selectNat 50 7 1 + let approximantDirectMeasured := preset.selectNat 30 5 1 + let approximantSubproductMeasured := preset.selectNat 1 1 1 let fastDenseMeasured := preset.selectNat 2 1 1 - let fastLeeDirectMeasured := preset.selectNat 600 90 20 - let fastLeeSubproductMeasured := preset.selectNat 400 60 10 + let fastKoetterMeasured := preset.selectNat 70 10 2 + let fastLeeDirectMeasured := preset.selectNat 400 60 10 + let fastLeeSubproductMeasured := preset.selectNat 300 40 10 + let fastApproximantDirectMeasured := preset.selectNat 200 30 5 + let fastApproximantSubproductMeasured := preset.selectNat 1 1 1 let alekDenseMeasured := denseMeasured + let alekKoetterMeasured := koetterMeasured let alekLeeDirectMeasured := leeDirectMeasured let alekLeeSubproductMeasured := leeSubproductMeasured + let alekApproximantDirectMeasured := approximantDirectMeasured + let alekApproximantSubproductMeasured := approximantSubproductMeasured let fastAlekDenseMeasured := fastDenseMeasured + let fastAlekKoetterMeasured := fastKoetterMeasured let fastAlekLeeDirectMeasured := fastLeeDirectMeasured let fastAlekLeeSubproductMeasured := fastLeeSubproductMeasured + let fastAlekApproximantDirectMeasured := fastApproximantDirectMeasured + let fastAlekApproximantSubproductMeasured := fastApproximantSubproductMeasured let checksumIterations := groupChecksumIterations denseMeasured [ - leeDirectMeasured, leeSubproductMeasured, fastDenseMeasured, - fastLeeDirectMeasured, fastLeeSubproductMeasured, alekDenseMeasured, - alekLeeDirectMeasured, alekLeeSubproductMeasured, fastAlekDenseMeasured, - fastAlekLeeDirectMeasured, fastAlekLeeSubproductMeasured + koetterMeasured, leeDirectMeasured, leeSubproductMeasured, fastDenseMeasured, + approximantDirectMeasured, approximantSubproductMeasured, fastKoetterMeasured, + fastLeeDirectMeasured, fastLeeSubproductMeasured, fastApproximantDirectMeasured, + fastApproximantSubproductMeasured, alekDenseMeasured, alekKoetterMeasured, + alekLeeDirectMeasured, alekLeeSubproductMeasured, alekApproximantDirectMeasured, + alekApproximantSubproductMeasured, fastAlekDenseMeasured, fastAlekKoetterMeasured, + fastAlekLeeDirectMeasured, fastAlekLeeSubproductMeasured, + fastAlekApproximantDirectMeasured, fastAlekApproximantSubproductMeasured ] let denseRow <- runTimed "guruswami-sudan-filtered-core-dense-small" "CBivariate" "Dense linear + RR roots + filter" "KoalaBear.Field" gsSmallFilteredShape preset warmup denseMeasured (fun _ ↦ - gsFilteredCore points koalaBearDenseInterpContext koalaBearRothRootContext + gsFilteredCore points (denseInterpContext KoalaBear.Field) rothRootContext gsSmallParams 0) checksumPolynomialArrayKoala checksumIterations let denseAlekRow <- runTimed @@ -192,98 +511,404 @@ def runGsFilteredCoreSmallKoala (preset : BenchPreset) (gen : StdGen) : "Dense linear + Alekhnovich roots + filter" "KoalaBear.Field" gsSmallFilteredShape preset warmup alekDenseMeasured (fun _ ↦ - gsFilteredCore points koalaBearDenseInterpContext alekRootContext + gsFilteredCore points (denseInterpContext KoalaBear.Field) alekRootContext gsSmallParams 0) checksumPolynomialArrayKoala checksumIterations + let koetterRow <- runTimed + "guruswami-sudan-filtered-core-koetter-small" "CBivariate" + "Koetter + RR roots + filter" + "KoalaBear.Field" gsSmallFilteredShape preset warmup koetterMeasured + (fun _ ↦ gsFilteredCore points koalaBearKoetterInterpContext rothRootContext + gsSmallParams 0) + checksumPolynomialArrayKoala checksumIterations + let koetterAlekRow <- runTimed + "guruswami-sudan-filtered-core-koetter-small-alekhnovich" "CBivariate" + "Koetter + Alekhnovich roots + filter" + "KoalaBear.Field" gsSmallFilteredShape preset warmup alekKoetterMeasured + (fun _ ↦ gsFilteredCore points koalaBearKoetterInterpContext alekRootContext + gsSmallParams 0) + checksumPolynomialArrayKoala checksumIterations let leeDirectRow <- runTimed "guruswami-sudan-filtered-core-lee-direct-small" "CBivariate" "Lee-O'Sullivan direct + RR roots + filter" "KoalaBear.Field" gsSmallFilteredShape preset warmup leeDirectMeasured - (fun _ ↦ - gsFilteredCore points koalaBearLeeDirectInterpContext koalaBearRothRootContext - gsSmallParams 0) + (fun _ ↦ gsFilteredCore points koalaBearLeeDirectInterpContext rothRootContext + gsSmallParams 0) checksumPolynomialArrayKoala checksumIterations let leeDirectAlekRow <- runTimed "guruswami-sudan-filtered-core-lee-direct-small-alekhnovich" "CBivariate" "Lee-O'Sullivan direct + Alekhnovich roots + filter" "KoalaBear.Field" gsSmallFilteredShape preset warmup alekLeeDirectMeasured - (fun _ ↦ - gsFilteredCore points koalaBearLeeDirectInterpContext alekRootContext - gsSmallParams 0) + (fun _ ↦ gsFilteredCore points koalaBearLeeDirectInterpContext alekRootContext + gsSmallParams 0) checksumPolynomialArrayKoala checksumIterations let leeSubproductRow <- runTimed "guruswami-sudan-filtered-core-lee-subproduct-small" "CBivariate" "Lee-O'Sullivan subproduct + RR roots + filter" "KoalaBear.Field" gsSmallFilteredShape preset warmup leeSubproductMeasured - (fun _ ↦ - gsFilteredCore points koalaBearLeeSubproductInterpContext koalaBearRothRootContext - gsSmallParams 0) + (fun _ ↦ gsFilteredCore points koalaBearLeeSubproductInterpContext rothRootContext + gsSmallParams 0) checksumPolynomialArrayKoala checksumIterations let leeSubproductAlekRow <- runTimed "guruswami-sudan-filtered-core-lee-subproduct-small-alekhnovich" "CBivariate" "Lee-O'Sullivan subproduct + Alekhnovich roots + filter" "KoalaBear.Field" gsSmallFilteredShape preset warmup alekLeeSubproductMeasured - (fun _ ↦ - gsFilteredCore points koalaBearLeeSubproductInterpContext alekRootContext - gsSmallParams 0) + (fun _ ↦ gsFilteredCore points koalaBearLeeSubproductInterpContext alekRootContext + gsSmallParams 0) + checksumPolynomialArrayKoala checksumIterations + let approximantDirectRow <- runTimed + "guruswami-sudan-filtered-core-approximant-direct-small" "CBivariate" + "Approximant basis direct + RR roots + filter" + "KoalaBear.Field" gsSmallFilteredShape preset warmup approximantDirectMeasured + (fun _ ↦ gsFilteredCore points koalaBearApproximantBasisDirectInterpContext + rothRootContext gsSmallParams 0) + checksumPolynomialArrayKoala checksumIterations + let approximantDirectAlekRow <- runTimed + "guruswami-sudan-filtered-core-approximant-direct-small-alekhnovich" "CBivariate" + "Approximant basis direct + Alekhnovich roots + filter" + "KoalaBear.Field" gsSmallFilteredShape preset warmup alekApproximantDirectMeasured + (fun _ ↦ gsFilteredCore points koalaBearApproximantBasisDirectInterpContext + alekRootContext gsSmallParams 0) + checksumPolynomialArrayKoala checksumIterations + let approximantSubproductRow <- runTimed + "guruswami-sudan-filtered-core-approximant-subproduct-small" "CBivariate" + "Approximant basis subproduct + RR roots + filter" + "KoalaBear.Field" gsSmallFilteredShape preset warmup approximantSubproductMeasured + (fun _ ↦ gsFilteredCore points koalaBearApproximantBasisSubproductInterpContext + rothRootContext gsSmallParams 0) + checksumPolynomialArrayKoala checksumIterations + let approximantSubproductAlekRow <- runTimed + "guruswami-sudan-filtered-core-approximant-subproduct-small-alekhnovich" + "CBivariate" + "Approximant basis subproduct + Alekhnovich roots + filter" + "KoalaBear.Field" gsSmallFilteredShape preset warmup + alekApproximantSubproductMeasured + (fun _ ↦ gsFilteredCore points koalaBearApproximantBasisSubproductInterpContext + alekRootContext gsSmallParams 0) checksumPolynomialArrayKoala checksumIterations let fastDenseRow <- runTimed "guruswami-sudan-filtered-core-dense-small-fast" "CBivariate" "Dense linear + RR roots + filter" "KoalaBear.Fast.Field" gsSmallFilteredShape preset warmup fastDenseMeasured (fun _ ↦ - gsFilteredCore fastPoints fastKoalaBearDenseInterpContext - fastKoalaBearRothRootContext gsSmallParams 0) + gsFilteredCore fastPoints (denseInterpContext KoalaBear.Fast.Field) fastRothRootContext + gsSmallParams 0) checksumPolynomialArrayKoalaFast checksumIterations let fastDenseAlekRow <- runTimed "guruswami-sudan-filtered-core-dense-small-alekhnovich-fast" "CBivariate" "Dense linear + Alekhnovich roots + filter" "KoalaBear.Fast.Field" gsSmallFilteredShape preset warmup fastAlekDenseMeasured (fun _ ↦ - gsFilteredCore fastPoints fastKoalaBearDenseInterpContext - fastAlekRootContext gsSmallParams 0) + gsFilteredCore fastPoints (denseInterpContext KoalaBear.Fast.Field) fastAlekRootContext + gsSmallParams 0) + checksumPolynomialArrayKoalaFast checksumIterations + let fastKoetterRow <- runTimed + "guruswami-sudan-filtered-core-koetter-small-fast" "CBivariate" + "Koetter + RR roots + filter" + "KoalaBear.Fast.Field" gsSmallFilteredShape preset warmup fastKoetterMeasured + (fun _ ↦ + gsFilteredCore fastPoints fastKoalaBearKoetterInterpContext fastRothRootContext + gsSmallParams 0) + checksumPolynomialArrayKoalaFast checksumIterations + let fastKoetterAlekRow <- runTimed + "guruswami-sudan-filtered-core-koetter-small-alekhnovich-fast" "CBivariate" + "Koetter + Alekhnovich roots + filter" + "KoalaBear.Fast.Field" gsSmallFilteredShape preset warmup fastAlekKoetterMeasured + (fun _ ↦ + gsFilteredCore fastPoints fastKoalaBearKoetterInterpContext fastAlekRootContext + gsSmallParams 0) checksumPolynomialArrayKoalaFast checksumIterations let fastLeeDirectRow <- runTimed "guruswami-sudan-filtered-core-lee-direct-small-fast" "CBivariate" "Lee-O'Sullivan direct + RR roots + filter" "KoalaBear.Fast.Field" gsSmallFilteredShape preset warmup fastLeeDirectMeasured (fun _ ↦ - gsFilteredCore fastPoints fastKoalaBearLeeDirectInterpContext - fastKoalaBearRothRootContext gsSmallParams 0) + gsFilteredCore fastPoints fastKoalaBearLeeDirectInterpContext fastRothRootContext + gsSmallParams 0) checksumPolynomialArrayKoalaFast checksumIterations let fastLeeDirectAlekRow <- runTimed "guruswami-sudan-filtered-core-lee-direct-small-alekhnovich-fast" "CBivariate" "Lee-O'Sullivan direct + Alekhnovich roots + filter" "KoalaBear.Fast.Field" gsSmallFilteredShape preset warmup fastAlekLeeDirectMeasured (fun _ ↦ - gsFilteredCore fastPoints fastKoalaBearLeeDirectInterpContext - fastAlekRootContext gsSmallParams 0) + gsFilteredCore fastPoints fastKoalaBearLeeDirectInterpContext fastAlekRootContext + gsSmallParams 0) checksumPolynomialArrayKoalaFast checksumIterations let fastLeeSubproductRow <- runTimed "guruswami-sudan-filtered-core-lee-subproduct-small-fast" "CBivariate" "Lee-O'Sullivan subproduct + RR roots + filter" "KoalaBear.Fast.Field" gsSmallFilteredShape preset warmup fastLeeSubproductMeasured (fun _ ↦ - gsFilteredCore fastPoints fastKoalaBearLeeSubproductInterpContext - fastKoalaBearRothRootContext gsSmallParams 0) + gsFilteredCore fastPoints fastKoalaBearLeeSubproductInterpContext fastRothRootContext + gsSmallParams 0) checksumPolynomialArrayKoalaFast checksumIterations let fastLeeSubproductAlekRow <- runTimed "guruswami-sudan-filtered-core-lee-subproduct-small-alekhnovich-fast" "CBivariate" "Lee-O'Sullivan subproduct + Alekhnovich roots + filter" - "KoalaBear.Fast.Field" gsSmallFilteredShape preset warmup - fastAlekLeeSubproductMeasured + "KoalaBear.Fast.Field" gsSmallFilteredShape preset warmup fastAlekLeeSubproductMeasured (fun _ ↦ - gsFilteredCore fastPoints fastKoalaBearLeeSubproductInterpContext - fastAlekRootContext gsSmallParams 0) + gsFilteredCore fastPoints fastKoalaBearLeeSubproductInterpContext fastAlekRootContext + gsSmallParams 0) + checksumPolynomialArrayKoalaFast checksumIterations + let fastApproximantDirectRow <- runTimed + "guruswami-sudan-filtered-core-approximant-direct-small-fast" "CBivariate" + "Approximant basis direct + RR roots + filter" + "KoalaBear.Fast.Field" gsSmallFilteredShape preset warmup + fastApproximantDirectMeasured + (fun _ ↦ gsFilteredCore fastPoints + fastKoalaBearApproximantBasisDirectInterpContext fastRothRootContext + gsSmallParams 0) + checksumPolynomialArrayKoalaFast checksumIterations + let fastApproximantDirectAlekRow <- runTimed + "guruswami-sudan-filtered-core-approximant-direct-small-alekhnovich-fast" + "CBivariate" + "Approximant basis direct + Alekhnovich roots + filter" + "KoalaBear.Fast.Field" gsSmallFilteredShape preset warmup + fastAlekApproximantDirectMeasured + (fun _ ↦ gsFilteredCore fastPoints + fastKoalaBearApproximantBasisDirectInterpContext fastAlekRootContext + gsSmallParams 0) + checksumPolynomialArrayKoalaFast checksumIterations + let fastApproximantSubproductRow <- runTimed + "guruswami-sudan-filtered-core-approximant-subproduct-small-fast" "CBivariate" + "Approximant basis subproduct + RR roots + filter" + "KoalaBear.Fast.Field" gsSmallFilteredShape preset warmup + fastApproximantSubproductMeasured + (fun _ ↦ gsFilteredCore fastPoints + fastKoalaBearApproximantBasisSubproductInterpContext fastRothRootContext + gsSmallParams 0) + checksumPolynomialArrayKoalaFast checksumIterations + let fastApproximantSubproductAlekRow <- runTimed + "guruswami-sudan-filtered-core-approximant-subproduct-small-alekhnovich-fast" + "CBivariate" + "Approximant basis subproduct + Alekhnovich roots + filter" + "KoalaBear.Fast.Field" gsSmallFilteredShape preset warmup + fastAlekApproximantSubproductMeasured + (fun _ ↦ gsFilteredCore fastPoints + fastKoalaBearApproximantBasisSubproductInterpContext fastAlekRootContext + gsSmallParams 0) checksumPolynomialArrayKoalaFast checksumIterations pure ({ groupKey := "guruswami-sudan-filtered-core-small-koalabear", title := "Guruswami-Sudan filtered core, small (KoalaBear)", records := #[ - denseRow, denseAlekRow, leeDirectRow, leeDirectAlekRow, - leeSubproductRow, leeSubproductAlekRow, - fastDenseRow, fastDenseAlekRow, fastLeeDirectRow, - fastLeeDirectAlekRow, fastLeeSubproductRow, fastLeeSubproductAlekRow + denseRow, denseAlekRow, koetterRow, koetterAlekRow, leeDirectRow, + leeDirectAlekRow, leeSubproductRow, leeSubproductAlekRow, approximantDirectRow, + approximantDirectAlekRow, approximantSubproductRow, approximantSubproductAlekRow, + fastDenseRow, fastDenseAlekRow, fastKoetterRow, fastKoetterAlekRow, + fastLeeDirectRow, fastLeeDirectAlekRow, fastLeeSubproductRow, + fastLeeSubproductAlekRow, fastApproximantDirectRow, fastApproximantDirectAlekRow, + fastApproximantSubproductRow, fastApproximantSubproductAlekRow + ] + }, gen) + +def runGsFilteredCoreLargeKoala (preset : BenchPreset) (gen : StdGen) : + IO (Prod BenchGroup StdGen) := do + let (coeffs, gen) := (koalaBearArray gsLargeInterpMessageDegree false).run gen + let message := cpolyOfArray coeffs + let fastMessage := cpolyOfArray (koalaBearFastArray coeffs) + let points := gsLargeBenchmarkPoints message + let fastPoints := gsLargeBenchmarkPoints fastMessage + let rothRootContext := koalaBearRothRootContext + let fastRothRootContext := fastKoalaBearRothRootContext + let alekRootContext := koalaBearAlekhnovichRootContext + let fastAlekRootContext := fastKoalaBearAlekhnovichRootContext + let warmup := gsWarmupIterations preset + let koetterMeasured := preset.selectNat 1 1 1 + let leeMeasured := preset.selectNat 1 1 1 + let approximantDirectMeasured := preset.selectNat 1 1 1 + let approximantSubproductMeasured := preset.selectNat 1 1 1 + let fastKoetterMeasured := preset.selectNat 1 1 1 + let fastLeeMeasured := preset.selectNat 1 1 1 + let fastApproximantDirectMeasured := preset.selectNat 1 1 1 + let fastApproximantSubproductMeasured := preset.selectNat 1 1 1 + let alekKoetterMeasured := koetterMeasured + let alekLeeMeasured := leeMeasured + let alekApproximantDirectMeasured := approximantDirectMeasured + let alekApproximantSubproductMeasured := approximantSubproductMeasured + let fastAlekKoetterMeasured := fastKoetterMeasured + let fastAlekLeeMeasured := fastLeeMeasured + let fastAlekApproximantDirectMeasured := fastApproximantDirectMeasured + let fastAlekApproximantSubproductMeasured := fastApproximantSubproductMeasured + let checksumIterations := groupChecksumIterations koetterMeasured [ + leeMeasured, leeMeasured, approximantDirectMeasured, approximantSubproductMeasured, + fastKoetterMeasured, fastLeeMeasured, fastLeeMeasured, + fastApproximantDirectMeasured, fastApproximantSubproductMeasured, + alekKoetterMeasured, alekLeeMeasured, alekLeeMeasured, alekApproximantDirectMeasured, + alekApproximantSubproductMeasured, fastAlekKoetterMeasured, fastAlekLeeMeasured, + fastAlekLeeMeasured, fastAlekApproximantDirectMeasured, + fastAlekApproximantSubproductMeasured + ] + let koetterRow <- runTimed + "guruswami-sudan-filtered-core-koetter" "CBivariate" + "Koetter + RR roots + filter" + "KoalaBear.Field" gsFilteredShape preset warmup koetterMeasured + (fun _ ↦ + gsFilteredCore points koalaBearKoetterInterpContext rothRootContext + gsLargeInterpParams 0) + checksumPolynomialArrayKoala checksumIterations + let koetterAlekRow <- runTimed + "guruswami-sudan-filtered-core-koetter-alekhnovich" "CBivariate" + "Koetter + Alekhnovich roots + filter" + "KoalaBear.Field" gsFilteredShape preset warmup alekKoetterMeasured + (fun _ ↦ + gsFilteredCore points koalaBearKoetterInterpContext alekRootContext + gsLargeInterpParams 0) + checksumPolynomialArrayKoala checksumIterations + let leeDirectRow <- runTimed + "guruswami-sudan-filtered-core-lee-direct" "CBivariate" + "Lee-O'Sullivan direct + RR roots + filter" + "KoalaBear.Field" gsFilteredShape preset warmup leeMeasured + (fun _ ↦ + gsFilteredCore points koalaBearLeeDirectInterpContext rothRootContext + gsLargeInterpParams 0) + checksumPolynomialArrayKoala checksumIterations + let leeDirectAlekRow <- runTimed + "guruswami-sudan-filtered-core-lee-direct-alekhnovich" "CBivariate" + "Lee-O'Sullivan direct + Alekhnovich roots + filter" + "KoalaBear.Field" gsFilteredShape preset warmup alekLeeMeasured + (fun _ ↦ + gsFilteredCore points koalaBearLeeDirectInterpContext alekRootContext + gsLargeInterpParams 0) + checksumPolynomialArrayKoala checksumIterations + let leeSubproductRow <- runTimed + "guruswami-sudan-filtered-core-lee-subproduct" "CBivariate" + "Lee-O'Sullivan subproduct + RR roots + filter" + "KoalaBear.Field" gsFilteredShape preset warmup leeMeasured + (fun _ ↦ + gsFilteredCore points koalaBearLeeSubproductInterpContext rothRootContext + gsLargeInterpParams 0) + checksumPolynomialArrayKoala checksumIterations + let leeSubproductAlekRow <- runTimed + "guruswami-sudan-filtered-core-lee-subproduct-alekhnovich" "CBivariate" + "Lee-O'Sullivan subproduct + Alekhnovich roots + filter" + "KoalaBear.Field" gsFilteredShape preset warmup alekLeeMeasured + (fun _ ↦ + gsFilteredCore points koalaBearLeeSubproductInterpContext alekRootContext + gsLargeInterpParams 0) + checksumPolynomialArrayKoala checksumIterations + let approximantDirectRow <- runTimed + "guruswami-sudan-filtered-core-approximant-direct-roth" "CBivariate" + "Approximant basis direct + RR roots + filter" + "KoalaBear.Field" gsFilteredShape preset warmup approximantDirectMeasured + (fun _ ↦ gsFilteredCore points koalaBearApproximantBasisDirectInterpContext + rothRootContext gsLargeInterpParams 0) + checksumPolynomialArrayKoala checksumIterations + let approximantDirectAlekRow <- runTimed + "guruswami-sudan-filtered-core-approximant-direct-alekhnovich" "CBivariate" + "Approximant basis direct + Alekhnovich roots + filter" + "KoalaBear.Field" gsFilteredShape preset warmup alekApproximantDirectMeasured + (fun _ ↦ gsFilteredCore points koalaBearApproximantBasisDirectInterpContext + alekRootContext gsLargeInterpParams 0) + checksumPolynomialArrayKoala checksumIterations + let approximantSubproductRow <- runTimed + "guruswami-sudan-filtered-core-approximant-subproduct-roth" "CBivariate" + "Approximant basis subproduct + RR roots + filter" + "KoalaBear.Field" gsFilteredShape preset warmup approximantSubproductMeasured + (fun _ ↦ gsFilteredCore points koalaBearApproximantBasisSubproductInterpContext + rothRootContext gsLargeInterpParams 0) + checksumPolynomialArrayKoala checksumIterations + let approximantSubproductAlekRow <- runTimed + "guruswami-sudan-filtered-core-approximant-subproduct-alekhnovich" "CBivariate" + "Approximant basis subproduct + Alekhnovich roots + filter" + "KoalaBear.Field" gsFilteredShape preset warmup alekApproximantSubproductMeasured + (fun _ ↦ gsFilteredCore points koalaBearApproximantBasisSubproductInterpContext + alekRootContext gsLargeInterpParams 0) + checksumPolynomialArrayKoala checksumIterations + let fastKoetterRow <- runTimed + "guruswami-sudan-filtered-core-koetter-fast" "CBivariate" + "Koetter + RR roots + filter" + "KoalaBear.Fast.Field" gsFilteredShape preset warmup fastKoetterMeasured + (fun _ ↦ + gsFilteredCore fastPoints fastKoalaBearKoetterInterpContext fastRothRootContext + gsLargeInterpParams 0) + checksumPolynomialArrayKoalaFast checksumIterations + let fastKoetterAlekRow <- runTimed + "guruswami-sudan-filtered-core-koetter-alekhnovich-fast" "CBivariate" + "Koetter + Alekhnovich roots + filter" + "KoalaBear.Fast.Field" gsFilteredShape preset warmup fastAlekKoetterMeasured + (fun _ ↦ + gsFilteredCore fastPoints fastKoalaBearKoetterInterpContext fastAlekRootContext + gsLargeInterpParams 0) + checksumPolynomialArrayKoalaFast checksumIterations + let fastLeeDirectRow <- runTimed + "guruswami-sudan-filtered-core-lee-direct-fast" "CBivariate" + "Lee-O'Sullivan direct + RR roots + filter" + "KoalaBear.Fast.Field" gsFilteredShape preset warmup fastLeeMeasured + (fun _ ↦ + gsFilteredCore fastPoints fastKoalaBearLeeDirectInterpContext fastRothRootContext + gsLargeInterpParams 0) + checksumPolynomialArrayKoalaFast checksumIterations + let fastLeeDirectAlekRow <- runTimed + "guruswami-sudan-filtered-core-lee-direct-alekhnovich-fast" "CBivariate" + "Lee-O'Sullivan direct + Alekhnovich roots + filter" + "KoalaBear.Fast.Field" gsFilteredShape preset warmup fastAlekLeeMeasured + (fun _ ↦ + gsFilteredCore fastPoints fastKoalaBearLeeDirectInterpContext fastAlekRootContext + gsLargeInterpParams 0) + checksumPolynomialArrayKoalaFast checksumIterations + let fastLeeSubproductRow <- runTimed + "guruswami-sudan-filtered-core-lee-subproduct-fast" "CBivariate" + "Lee-O'Sullivan subproduct + RR roots + filter" + "KoalaBear.Fast.Field" gsFilteredShape preset warmup fastLeeMeasured + (fun _ ↦ + gsFilteredCore fastPoints fastKoalaBearLeeSubproductInterpContext fastRothRootContext + gsLargeInterpParams 0) + checksumPolynomialArrayKoalaFast checksumIterations + let fastLeeSubproductAlekRow <- runTimed + "guruswami-sudan-filtered-core-lee-subproduct-alekhnovich-fast" "CBivariate" + "Lee-O'Sullivan subproduct + Alekhnovich roots + filter" + "KoalaBear.Fast.Field" gsFilteredShape preset warmup fastAlekLeeMeasured + (fun _ ↦ + gsFilteredCore fastPoints fastKoalaBearLeeSubproductInterpContext fastAlekRootContext + gsLargeInterpParams 0) + checksumPolynomialArrayKoalaFast checksumIterations + let fastApproximantDirectRow <- runTimed + "guruswami-sudan-filtered-core-approximant-direct-roth-fast" "CBivariate" + "Approximant basis direct + RR roots + filter" + "KoalaBear.Fast.Field" gsFilteredShape preset warmup fastApproximantDirectMeasured + (fun _ ↦ gsFilteredCore fastPoints + fastKoalaBearApproximantBasisDirectInterpContext fastRothRootContext + gsLargeInterpParams 0) + checksumPolynomialArrayKoalaFast checksumIterations + let fastApproximantDirectAlekRow <- runTimed + "guruswami-sudan-filtered-core-approximant-direct-alekhnovich-fast" "CBivariate" + "Approximant basis direct + Alekhnovich roots + filter" + "KoalaBear.Fast.Field" gsFilteredShape preset warmup + fastAlekApproximantDirectMeasured + (fun _ ↦ gsFilteredCore fastPoints + fastKoalaBearApproximantBasisDirectInterpContext fastAlekRootContext + gsLargeInterpParams 0) + checksumPolynomialArrayKoalaFast checksumIterations + let fastApproximantSubproductRow <- runTimed + "guruswami-sudan-filtered-core-approximant-subproduct-roth-fast" "CBivariate" + "Approximant basis subproduct + RR roots + filter" + "KoalaBear.Fast.Field" gsFilteredShape preset warmup + fastApproximantSubproductMeasured + (fun _ ↦ gsFilteredCore fastPoints + fastKoalaBearApproximantBasisSubproductInterpContext fastRothRootContext + gsLargeInterpParams 0) + checksumPolynomialArrayKoalaFast checksumIterations + let fastApproximantSubproductAlekRow <- runTimed + "guruswami-sudan-filtered-core-approximant-subproduct-alekhnovich-fast" + "CBivariate" + "Approximant basis subproduct + Alekhnovich roots + filter" + "KoalaBear.Fast.Field" gsFilteredShape preset warmup + fastAlekApproximantSubproductMeasured + (fun _ ↦ gsFilteredCore fastPoints + fastKoalaBearApproximantBasisSubproductInterpContext fastAlekRootContext + gsLargeInterpParams 0) + checksumPolynomialArrayKoalaFast checksumIterations + pure ({ + groupKey := "guruswami-sudan-filtered-core-large-koalabear", + title := "Guruswami-Sudan filtered core, large (KoalaBear)", + records := #[ + koetterRow, koetterAlekRow, leeDirectRow, leeDirectAlekRow, leeSubproductRow, + leeSubproductAlekRow, approximantDirectRow, approximantDirectAlekRow, + approximantSubproductRow, approximantSubproductAlekRow, fastKoetterRow, + fastKoetterAlekRow, fastLeeDirectRow, fastLeeDirectAlekRow, fastLeeSubproductRow, + fastLeeSubproductAlekRow, fastApproximantDirectRow, fastApproximantDirectAlekRow, + fastApproximantSubproductRow, fastApproximantSubproductAlekRow ] }, gen) diff --git a/bench/CompPolyBench/Bivariate/GuruswamiSudan/ReceivedWord.lean b/bench/CompPolyBench/Bivariate/GuruswamiSudan/ReceivedWord.lean index 91dc5cd7..40a88c5a 100644 --- a/bench/CompPolyBench/Bivariate/GuruswamiSudan/ReceivedWord.lean +++ b/bench/CompPolyBench/Bivariate/GuruswamiSudan/ReceivedWord.lean @@ -4,13 +4,16 @@ Released under Apache 2.0 license as described in the file LICENSE. Authors: Valerii Huhnin -/ -import CompPolyBench.Bivariate.GuruswamiSudan.Shared +import CompPolyBench.Common +import CompPoly.Bivariate.GuruswamiSudan +import CompPoly.Bivariate.GuruswamiSudan.Implementations /-! -# Guruswami-Sudan Perturbed Received-Word Benchmarks +# Guruswami-Sudan Received-Word Benchmarks -Perturbed (non-codeword) counterparts of the small interpolation, -root-backend core, and filtered-core benchmark groups. +KoalaBear benchmarks for Guruswami-Sudan interpolation on perturbed received +words, where different interpolation backends may return different valid +witnesses. -/ open CompPoly @@ -18,18 +21,48 @@ open CompPoly.GuruswamiSudan namespace CompPolyBench -/-! ### Perturbation shape -/ +private def gsNonCodewordPointCount : Nat := 4 -private def gsNonCodewordSmallPeriod : Nat := 3 +private def gsNonCodewordMessageDegree : Nat := 2 -private def gsNonCodewordSmallErrors : Nat := - (gsSmallPointCount + gsNonCodewordSmallPeriod - 1) / gsNonCodewordSmallPeriod +private def gsNonCodewordWeightedDegreeBound : Nat := + 3 -private def gsNonCodewordSmallInputShape : String := - gsSmallInterpInputShape ++ s!",errors=every{gsNonCodewordSmallPeriod}" +private def gsNonCodewordMultiplicity : Nat := 1 -private def gsNonCodewordSmallFilteredShape : String := - gsNonCodewordSmallInputShape ++ s!",r={gsNonCodewordSmallErrors}" +private def gsNonCodewordParams : GSInterpParams := + { messageDegree := gsNonCodewordMessageDegree + multiplicity := gsNonCodewordMultiplicity + weightedDegreeBound := gsNonCodewordWeightedDegreeBound } + +private def gsStressPointCount : Nat := 128 + +private def gsStressMessageDegree : Nat := 33 + +private def gsStressWeightedDegreeBound : Nat := 160 + +private def gsStressMultiplicity : Nat := 2 + +private def gsStressParams : GSInterpParams := + { messageDegree := gsStressMessageDegree + multiplicity := gsStressMultiplicity + weightedDegreeBound := gsStressWeightedDegreeBound } + +private def gsNonCodewordInputShape : String := + s!"n={gsNonCodewordPointCount},k={gsNonCodewordMessageDegree}," ++ + s!"m={gsNonCodewordMultiplicity},D={gsNonCodewordWeightedDegreeBound}," ++ + "errors=every2" + +private def gsStressInputShape : String := + s!"n={gsStressPointCount},k={gsStressMessageDegree}," ++ + s!"m={gsStressMultiplicity},D={gsStressWeightedDegreeBound},errors=every7" + +private def codewordPointsWithCount {F : Type*} [Semiring F] [BEq F] [LawfulBEq F] + (pointCount : Nat) (p : CPolynomial F) : Array (Prod F F) := + (List.range pointCount).map + (fun i ↦ + let x : F := (i + 1 : Nat) + (x, CPolynomial.eval x p)) |>.toArray private def perturbEveryNthY {F : Type*} [Add F] [OfNat F 1] (period : Nat) (points : Array (Prod F F)) : Array (Prod F F) := @@ -44,390 +77,207 @@ private def perturbEveryNthY {F : Type*} [Add F] [OfNat F 1] else point -/-! ### Shared inputs -/ - -private structure PerturbedSmallInputs where - points : Array (Prod KoalaBear.Field KoalaBear.Field) - fastPoints : Array (Prod KoalaBear.Fast.Field KoalaBear.Fast.Field) - -private def perturbedSmallInputs (gen : StdGen) : PerturbedSmallInputs × StdGen := - let (coeffs, gen) := (koalaBearArray gsSmallMessageDegree false).run gen - let message := cpolyOfArray coeffs - let fastMessage := cpolyOfArray (koalaBearFastArray coeffs) - let points := perturbEveryNthY gsNonCodewordSmallPeriod - (codewordPointsWithCount gsSmallPointCount message) - let fastPoints := perturbEveryNthY gsNonCodewordSmallPeriod - (codewordPointsWithCount gsSmallPointCount fastMessage) - ({ points := points, fastPoints := fastPoints }, gen) - -/-! ### Group metadata -/ +private def checksumInterpolationValidityOption {F : Type*} + [CommSemiring F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (points : Array (F × F)) (params : GSInterpParams) + (Q? : Option (CBivariate F)) : Nat := + match Q? with + | none => 0 + | some Q => if interpolationWitnessIsValidBool points params Q then 2 else 1 -/-- Benchmark group metadata for perturbed received-word rows. -/ +/-- Benchmark group metadata for received-word interpolation rows. -/ def guruswamiSudanReceivedWordGroupInfos : List BenchGroupInfo := [ ⟨"guruswami-sudan-interp-noncodeword-small-koalabear", - "Guruswami-Sudan interpolation on perturbed received word, small (KoalaBear)"⟩, - ⟨"guruswami-sudan-core-noncodeword-small-koalabear", - "Guruswami-Sudan full core on perturbed received word, small (KoalaBear)"⟩, - ⟨"guruswami-sudan-filtered-core-noncodeword-small-koalabear", - "Guruswami-Sudan filtered core on perturbed received word, small (KoalaBear)"⟩ + "Guruswami-Sudan interpolation on perturbed received word, smoke (KoalaBear)"⟩, + ⟨"guruswami-sudan-interp-stress-koalabear", + "Guruswami-Sudan interpolation on perturbed received word, stress (KoalaBear)"⟩ ] -/-! ### Group runners -/ - -private def runGsInterpolationNonCodewordSmallKoala (preset : BenchPreset) - (gen : StdGen) : IO (Prod BenchGroup StdGen) := do - let (inputs, gen) := perturbedSmallInputs gen - let warmup := gsWarmupIterations preset - let denseMeasured := preset.selectNat 1 1 1 - let leeDirectMeasured := preset.selectNat 15 2 1 - let leeSubproductMeasured := preset.selectNat 15 2 1 - let fastDenseMeasured := preset.selectNat 2 1 1 - let fastLeeDirectMeasured := preset.selectNat 80 11 2 - let fastLeeSubproductMeasured := preset.selectNat 70 10 2 - let checksumIterations := groupChecksumIterations denseMeasured [ - leeDirectMeasured, leeSubproductMeasured, fastDenseMeasured, - fastLeeDirectMeasured, fastLeeSubproductMeasured +private def runGsInterpolationNonCodewordSmallKoala (preset : BenchPreset) (gen : StdGen) : + IO (Prod BenchGroup StdGen) := do + let (coeffs, gen) := (koalaBearArray gsNonCodewordMessageDegree false).run gen + let message := cpolyOfArray coeffs + let fastMessage := cpolyOfArray (koalaBearFastArray coeffs) + let points := perturbEveryNthY 2 (codewordPointsWithCount gsNonCodewordPointCount message) + let fastPoints := perturbEveryNthY 2 + (codewordPointsWithCount gsNonCodewordPointCount fastMessage) + let warmup := 0 + let leeDirectMeasured := preset.selectNat 20 3 1 + let leeSubproductMeasured := preset.selectNat 20 3 1 + let approximantDirectMeasured := preset.selectNat 1 1 1 + let approximantSubproductMeasured := preset.selectNat 1 1 1 + let fastLeeDirectMeasured := preset.selectNat 100 15 3 + let fastLeeSubproductMeasured := preset.selectNat 80 12 2 + let fastApproximantDirectMeasured := preset.selectNat 1 1 1 + let fastApproximantSubproductMeasured := preset.selectNat 1 1 1 + let checksumIterations := groupChecksumIterations leeDirectMeasured [ + leeSubproductMeasured, approximantDirectMeasured, approximantSubproductMeasured, + fastLeeDirectMeasured, fastLeeSubproductMeasured, fastApproximantDirectMeasured, + fastApproximantSubproductMeasured ] - let denseRow <- runTimed - "guruswami-sudan-interp-dense-noncodeword-small" "CBivariate" - "Dense linear" - "KoalaBear.Field" gsNonCodewordSmallInputShape preset warmup denseMeasured - (fun _ ↦ koalaBearDenseInterpContext.interpolate inputs.points gsSmallParams) - (checksumInterpolationValidityOption inputs.points gsSmallParams) - checksumIterations let leeDirectRow <- runTimed "guruswami-sudan-interp-lee-direct-noncodeword-small" "CBivariate" "Lee-O'Sullivan direct" - "KoalaBear.Field" gsNonCodewordSmallInputShape preset warmup leeDirectMeasured - (fun _ ↦ koalaBearLeeDirectInterpContext.interpolate inputs.points gsSmallParams) - (checksumInterpolationValidityOption inputs.points gsSmallParams) + "KoalaBear.Field" gsNonCodewordInputShape preset warmup leeDirectMeasured + (fun _ ↦ koalaBearLeeDirectInterpContext.interpolate points gsNonCodewordParams) + (checksumInterpolationValidityOption points gsNonCodewordParams) checksumIterations let leeSubproductRow <- runTimed "guruswami-sudan-interp-lee-subproduct-noncodeword-small" "CBivariate" "Lee-O'Sullivan subproduct" - "KoalaBear.Field" gsNonCodewordSmallInputShape preset warmup leeSubproductMeasured - (fun _ ↦ - koalaBearLeeSubproductInterpContext.interpolate inputs.points gsSmallParams) - (checksumInterpolationValidityOption inputs.points gsSmallParams) + "KoalaBear.Field" gsNonCodewordInputShape preset warmup leeSubproductMeasured + (fun _ ↦ koalaBearLeeSubproductInterpContext.interpolate points gsNonCodewordParams) + (checksumInterpolationValidityOption points gsNonCodewordParams) + checksumIterations + let approximantDirectRow <- runTimed + "guruswami-sudan-interp-approximant-direct-noncodeword-small" "CBivariate" + "Approximant basis direct" + "KoalaBear.Field" gsNonCodewordInputShape preset warmup approximantDirectMeasured + (fun _ ↦ koalaBearApproximantBasisDirectInterpContext.interpolate points + gsNonCodewordParams) + (checksumInterpolationValidityOption points gsNonCodewordParams) checksumIterations - let fastDenseRow <- runTimed - "guruswami-sudan-interp-dense-noncodeword-small-fast" "CBivariate" - "Dense linear" - "KoalaBear.Fast.Field" gsNonCodewordSmallInputShape preset warmup - fastDenseMeasured - (fun _ ↦ fastKoalaBearDenseInterpContext.interpolate inputs.fastPoints gsSmallParams) - (checksumInterpolationValidityOption inputs.fastPoints gsSmallParams) + let approximantSubproductRow <- runTimed + "guruswami-sudan-interp-approximant-subproduct-noncodeword-small" "CBivariate" + "Approximant basis subproduct" + "KoalaBear.Field" gsNonCodewordInputShape preset warmup + approximantSubproductMeasured + (fun _ ↦ koalaBearApproximantBasisSubproductInterpContext.interpolate points + gsNonCodewordParams) + (checksumInterpolationValidityOption points gsNonCodewordParams) checksumIterations let fastLeeDirectRow <- runTimed "guruswami-sudan-interp-lee-direct-noncodeword-small-fast" "CBivariate" "Lee-O'Sullivan direct" - "KoalaBear.Fast.Field" gsNonCodewordSmallInputShape preset warmup + "KoalaBear.Fast.Field" gsNonCodewordInputShape preset warmup fastLeeDirectMeasured - (fun _ ↦ - fastKoalaBearLeeDirectInterpContext.interpolate inputs.fastPoints - gsSmallParams) - (checksumInterpolationValidityOption inputs.fastPoints gsSmallParams) + (fun _ ↦ fastKoalaBearLeeDirectInterpContext.interpolate fastPoints + gsNonCodewordParams) + (checksumInterpolationValidityOption fastPoints gsNonCodewordParams) checksumIterations let fastLeeSubproductRow <- runTimed "guruswami-sudan-interp-lee-subproduct-noncodeword-small-fast" "CBivariate" "Lee-O'Sullivan subproduct" - "KoalaBear.Fast.Field" gsNonCodewordSmallInputShape preset warmup + "KoalaBear.Fast.Field" gsNonCodewordInputShape preset warmup fastLeeSubproductMeasured - (fun _ ↦ - fastKoalaBearLeeSubproductInterpContext.interpolate inputs.fastPoints - gsSmallParams) - (checksumInterpolationValidityOption inputs.fastPoints gsSmallParams) + (fun _ ↦ fastKoalaBearLeeSubproductInterpContext.interpolate fastPoints + gsNonCodewordParams) + (checksumInterpolationValidityOption fastPoints gsNonCodewordParams) + checksumIterations + let fastApproximantDirectRow <- runTimed + "guruswami-sudan-interp-approximant-direct-noncodeword-small-fast" "CBivariate" + "Approximant basis direct" + "KoalaBear.Fast.Field" gsNonCodewordInputShape preset warmup + fastApproximantDirectMeasured + (fun _ ↦ fastKoalaBearApproximantBasisDirectInterpContext.interpolate fastPoints + gsNonCodewordParams) + (checksumInterpolationValidityOption fastPoints gsNonCodewordParams) + checksumIterations + let fastApproximantSubproductRow <- runTimed + "guruswami-sudan-interp-approximant-subproduct-noncodeword-small-fast" "CBivariate" + "Approximant basis subproduct" + "KoalaBear.Fast.Field" gsNonCodewordInputShape preset warmup + fastApproximantSubproductMeasured + (fun _ ↦ fastKoalaBearApproximantBasisSubproductInterpContext.interpolate + fastPoints gsNonCodewordParams) + (checksumInterpolationValidityOption fastPoints gsNonCodewordParams) checksumIterations pure ({ groupKey := "guruswami-sudan-interp-noncodeword-small-koalabear", - title := "Guruswami-Sudan interpolation on perturbed received word, small (KoalaBear)", - records := #[ - denseRow, leeDirectRow, leeSubproductRow, - fastDenseRow, fastLeeDirectRow, fastLeeSubproductRow - ] - }, gen) - -private def runGsCoreNonCodewordSmallKoala (preset : BenchPreset) - (gen : StdGen) : IO (Prod BenchGroup StdGen) := do - let (inputs, gen) := perturbedSmallInputs gen - let alekRootContext := - alekhnovichRootContext KoalaBear.Field koalaBearFieldRootContext - let fastAlekRootContext := - alekhnovichRootContext KoalaBear.Fast.Field fastKoalaBearFieldRootContext - let warmup := gsWarmupIterations preset - let denseMeasured := preset.selectNat 1 1 1 - let leeDirectMeasured := preset.selectNat 15 2 1 - let leeSubproductMeasured := preset.selectNat 15 2 1 - let fastDenseMeasured := preset.selectNat 2 1 1 - let fastLeeDirectMeasured := preset.selectNat 80 11 2 - let fastLeeSubproductMeasured := preset.selectNat 70 10 2 - let alekDenseMeasured := denseMeasured - let alekLeeDirectMeasured := leeDirectMeasured - let alekLeeSubproductMeasured := leeSubproductMeasured - let fastAlekDenseMeasured := fastDenseMeasured - let fastAlekLeeDirectMeasured := fastLeeDirectMeasured - let fastAlekLeeSubproductMeasured := fastLeeSubproductMeasured - let checksumIterations := groupChecksumIterations denseMeasured [ - leeDirectMeasured, leeSubproductMeasured, fastDenseMeasured, - fastLeeDirectMeasured, fastLeeSubproductMeasured, alekDenseMeasured, - alekLeeDirectMeasured, alekLeeSubproductMeasured, fastAlekDenseMeasured, - fastAlekLeeDirectMeasured, fastAlekLeeSubproductMeasured - ] - let denseRow <- runTimed - "guruswami-sudan-core-dense-noncodeword-small" "CBivariate" - "Dense linear + RR roots" - "KoalaBear.Field" gsNonCodewordSmallInputShape preset warmup denseMeasured - (fun _ ↦ - (gsCore inputs.points koalaBearDenseInterpContext koalaBearRothRootContext - gsSmallParams).filter (passesCandidateDistance inputs.points gsNonCodewordSmallErrors)) - checksumPolynomialArrayKoala checksumIterations - let denseAlekRow <- runTimed - "guruswami-sudan-core-dense-noncodeword-small-alekhnovich" "CBivariate" - "Dense linear + Alekhnovich roots" - "KoalaBear.Field" gsNonCodewordSmallInputShape preset warmup alekDenseMeasured - (fun _ ↦ - (gsCore inputs.points koalaBearDenseInterpContext alekRootContext - gsSmallParams).filter (passesCandidateDistance inputs.points gsNonCodewordSmallErrors)) - checksumPolynomialArrayKoala checksumIterations - let leeDirectRow <- runTimed - "guruswami-sudan-core-lee-direct-noncodeword-small" "CBivariate" - "Lee-O'Sullivan direct + RR roots" - "KoalaBear.Field" gsNonCodewordSmallInputShape preset warmup leeDirectMeasured - (fun _ ↦ - (gsCore inputs.points koalaBearLeeDirectInterpContext koalaBearRothRootContext - gsSmallParams).filter (passesCandidateDistance inputs.points gsNonCodewordSmallErrors)) - checksumPolynomialArrayKoala checksumIterations - let leeDirectAlekRow <- runTimed - "guruswami-sudan-core-lee-direct-noncodeword-small-alekhnovich" "CBivariate" - "Lee-O'Sullivan direct + Alekhnovich roots" - "KoalaBear.Field" gsNonCodewordSmallInputShape preset warmup alekLeeDirectMeasured - (fun _ ↦ - (gsCore inputs.points koalaBearLeeDirectInterpContext alekRootContext - gsSmallParams).filter (passesCandidateDistance inputs.points gsNonCodewordSmallErrors)) - checksumPolynomialArrayKoala checksumIterations - let leeSubproductRow <- runTimed - "guruswami-sudan-core-lee-subproduct-noncodeword-small" "CBivariate" - "Lee-O'Sullivan subproduct + RR roots" - "KoalaBear.Field" gsNonCodewordSmallInputShape preset warmup leeSubproductMeasured - (fun _ ↦ - (gsCore inputs.points koalaBearLeeSubproductInterpContext koalaBearRothRootContext - gsSmallParams).filter (passesCandidateDistance inputs.points gsNonCodewordSmallErrors)) - checksumPolynomialArrayKoala checksumIterations - let leeSubproductAlekRow <- runTimed - "guruswami-sudan-core-lee-subproduct-noncodeword-small-alekhnovich" "CBivariate" - "Lee-O'Sullivan subproduct + Alekhnovich roots" - "KoalaBear.Field" gsNonCodewordSmallInputShape preset warmup alekLeeSubproductMeasured - (fun _ ↦ - (gsCore inputs.points koalaBearLeeSubproductInterpContext alekRootContext - gsSmallParams).filter (passesCandidateDistance inputs.points gsNonCodewordSmallErrors)) - checksumPolynomialArrayKoala checksumIterations - let fastDenseRow <- runTimed - "guruswami-sudan-core-dense-noncodeword-small-fast" "CBivariate" - "Dense linear + RR roots" - "KoalaBear.Fast.Field" gsNonCodewordSmallInputShape preset warmup fastDenseMeasured - (fun _ ↦ - (gsCore inputs.fastPoints fastKoalaBearDenseInterpContext - fastKoalaBearRothRootContext gsSmallParams).filter - (passesCandidateDistance inputs.fastPoints gsNonCodewordSmallErrors)) - checksumPolynomialArrayKoalaFast checksumIterations - let fastDenseAlekRow <- runTimed - "guruswami-sudan-core-dense-noncodeword-small-alekhnovich-fast" "CBivariate" - "Dense linear + Alekhnovich roots" - "KoalaBear.Fast.Field" gsNonCodewordSmallInputShape preset warmup - fastAlekDenseMeasured - (fun _ ↦ - (gsCore inputs.fastPoints fastKoalaBearDenseInterpContext - fastAlekRootContext gsSmallParams).filter - (passesCandidateDistance inputs.fastPoints gsNonCodewordSmallErrors)) - checksumPolynomialArrayKoalaFast checksumIterations - let fastLeeDirectRow <- runTimed - "guruswami-sudan-core-lee-direct-noncodeword-small-fast" "CBivariate" - "Lee-O'Sullivan direct + RR roots" - "KoalaBear.Fast.Field" gsNonCodewordSmallInputShape preset warmup - fastLeeDirectMeasured - (fun _ ↦ - (gsCore inputs.fastPoints fastKoalaBearLeeDirectInterpContext - fastKoalaBearRothRootContext gsSmallParams).filter - (passesCandidateDistance inputs.fastPoints gsNonCodewordSmallErrors)) - checksumPolynomialArrayKoalaFast checksumIterations - let fastLeeDirectAlekRow <- runTimed - "guruswami-sudan-core-lee-direct-noncodeword-small-alekhnovich-fast" "CBivariate" - "Lee-O'Sullivan direct + Alekhnovich roots" - "KoalaBear.Fast.Field" gsNonCodewordSmallInputShape preset warmup - fastAlekLeeDirectMeasured - (fun _ ↦ - (gsCore inputs.fastPoints fastKoalaBearLeeDirectInterpContext - fastAlekRootContext gsSmallParams).filter - (passesCandidateDistance inputs.fastPoints gsNonCodewordSmallErrors)) - checksumPolynomialArrayKoalaFast checksumIterations - let fastLeeSubproductRow <- runTimed - "guruswami-sudan-core-lee-subproduct-noncodeword-small-fast" "CBivariate" - "Lee-O'Sullivan subproduct + RR roots" - "KoalaBear.Fast.Field" gsNonCodewordSmallInputShape preset warmup - fastLeeSubproductMeasured - (fun _ ↦ - (gsCore inputs.fastPoints fastKoalaBearLeeSubproductInterpContext - fastKoalaBearRothRootContext gsSmallParams).filter - (passesCandidateDistance inputs.fastPoints gsNonCodewordSmallErrors)) - checksumPolynomialArrayKoalaFast checksumIterations - let fastLeeSubproductAlekRow <- runTimed - "guruswami-sudan-core-lee-subproduct-noncodeword-small-alekhnovich-fast" "CBivariate" - "Lee-O'Sullivan subproduct + Alekhnovich roots" - "KoalaBear.Fast.Field" gsNonCodewordSmallInputShape preset warmup - fastAlekLeeSubproductMeasured - (fun _ ↦ - (gsCore inputs.fastPoints fastKoalaBearLeeSubproductInterpContext - fastAlekRootContext gsSmallParams).filter - (passesCandidateDistance inputs.fastPoints gsNonCodewordSmallErrors)) - checksumPolynomialArrayKoalaFast checksumIterations - pure ({ - groupKey := "guruswami-sudan-core-noncodeword-small-koalabear", - title := "Guruswami-Sudan full core on perturbed received word, small (KoalaBear)", + title := "Guruswami-Sudan interpolation on perturbed received word, smoke (KoalaBear)", records := #[ - denseRow, denseAlekRow, leeDirectRow, leeDirectAlekRow, - leeSubproductRow, leeSubproductAlekRow, - fastDenseRow, fastDenseAlekRow, fastLeeDirectRow, - fastLeeDirectAlekRow, fastLeeSubproductRow, fastLeeSubproductAlekRow + leeDirectRow, leeSubproductRow, approximantDirectRow, approximantSubproductRow, + fastLeeDirectRow, fastLeeSubproductRow, fastApproximantDirectRow, + fastApproximantSubproductRow ] }, gen) -private def runGsFilteredCoreNonCodewordSmallKoala (preset : BenchPreset) - (gen : StdGen) : IO (Prod BenchGroup StdGen) := do - let (inputs, gen) := perturbedSmallInputs gen - let alekRootContext := - alekhnovichRootContext KoalaBear.Field koalaBearFieldRootContext - let fastAlekRootContext := - alekhnovichRootContext KoalaBear.Fast.Field fastKoalaBearFieldRootContext - let warmup := gsWarmupIterations preset - let denseMeasured := preset.selectNat 1 1 1 - let leeDirectMeasured := preset.selectNat 15 2 1 - let leeSubproductMeasured := preset.selectNat 15 2 1 - let fastDenseMeasured := preset.selectNat 2 1 1 - let fastLeeDirectMeasured := preset.selectNat 80 11 2 - let fastLeeSubproductMeasured := preset.selectNat 70 10 2 - let alekDenseMeasured := denseMeasured - let alekLeeDirectMeasured := leeDirectMeasured - let alekLeeSubproductMeasured := leeSubproductMeasured - let fastAlekDenseMeasured := fastDenseMeasured - let fastAlekLeeDirectMeasured := fastLeeDirectMeasured - let fastAlekLeeSubproductMeasured := fastLeeSubproductMeasured - let checksumIterations := groupChecksumIterations denseMeasured [ - leeDirectMeasured, leeSubproductMeasured, fastDenseMeasured, - fastLeeDirectMeasured, fastLeeSubproductMeasured, alekDenseMeasured, - alekLeeDirectMeasured, alekLeeSubproductMeasured, fastAlekDenseMeasured, - fastAlekLeeDirectMeasured, fastAlekLeeSubproductMeasured +private def runGsInterpolationStressKoala (preset : BenchPreset) (gen : StdGen) : + IO (Prod BenchGroup StdGen) := do + let (coeffs, gen) := (koalaBearArray gsStressMessageDegree false).run gen + let message := cpolyOfArray coeffs + let fastMessage := cpolyOfArray (koalaBearFastArray coeffs) + let points := perturbEveryNthY 7 (codewordPointsWithCount gsStressPointCount message) + let fastPoints := perturbEveryNthY 7 + (codewordPointsWithCount gsStressPointCount fastMessage) + let warmup := 0 + let leeMeasured := preset.selectNat 3 1 1 + let approximantMeasured := preset.selectNat 1 1 1 + let fastLeeMeasured := preset.selectNat 10 2 1 + let fastApproximantMeasured := preset.selectNat 1 1 1 + let checksumIterations := groupChecksumIterations leeMeasured [ + leeMeasured, approximantMeasured, approximantMeasured, fastLeeMeasured, + fastLeeMeasured, fastApproximantMeasured, fastApproximantMeasured ] - let denseRow <- runTimed - "guruswami-sudan-filtered-core-dense-noncodeword-small" "CBivariate" - "Dense linear + RR roots + filter" - "KoalaBear.Field" gsNonCodewordSmallFilteredShape preset warmup denseMeasured - (fun _ ↦ - gsFilteredCore inputs.points koalaBearDenseInterpContext koalaBearRothRootContext - gsSmallParams gsNonCodewordSmallErrors) - checksumPolynomialArrayKoala checksumIterations - let denseAlekRow <- runTimed - "guruswami-sudan-filtered-core-dense-noncodeword-small-alekhnovich" "CBivariate" - "Dense linear + Alekhnovich roots + filter" - "KoalaBear.Field" gsNonCodewordSmallFilteredShape preset warmup alekDenseMeasured - (fun _ ↦ - gsFilteredCore inputs.points koalaBearDenseInterpContext alekRootContext - gsSmallParams gsNonCodewordSmallErrors) - checksumPolynomialArrayKoala checksumIterations let leeDirectRow <- runTimed - "guruswami-sudan-filtered-core-lee-direct-noncodeword-small" "CBivariate" - "Lee-O'Sullivan direct + RR roots + filter" - "KoalaBear.Field" gsNonCodewordSmallFilteredShape preset warmup leeDirectMeasured - (fun _ ↦ - gsFilteredCore inputs.points koalaBearLeeDirectInterpContext - koalaBearRothRootContext gsSmallParams gsNonCodewordSmallErrors) - checksumPolynomialArrayKoala checksumIterations - let leeDirectAlekRow <- runTimed - "guruswami-sudan-filtered-core-lee-direct-noncodeword-small-alekhnovich" "CBivariate" - "Lee-O'Sullivan direct + Alekhnovich roots + filter" - "KoalaBear.Field" gsNonCodewordSmallFilteredShape preset warmup alekLeeDirectMeasured - (fun _ ↦ - gsFilteredCore inputs.points koalaBearLeeDirectInterpContext alekRootContext - gsSmallParams gsNonCodewordSmallErrors) - checksumPolynomialArrayKoala checksumIterations + "guruswami-sudan-interp-lee-direct-stress" "CBivariate" + "Lee-O'Sullivan direct" + "KoalaBear.Field" gsStressInputShape preset warmup leeMeasured + (fun _ ↦ koalaBearLeeDirectInterpContext.interpolate points gsStressParams) + (checksumInterpolationValidityOption points gsStressParams) + checksumIterations let leeSubproductRow <- runTimed - "guruswami-sudan-filtered-core-lee-subproduct-noncodeword-small" "CBivariate" - "Lee-O'Sullivan subproduct + RR roots + filter" - "KoalaBear.Field" gsNonCodewordSmallFilteredShape preset warmup leeSubproductMeasured - (fun _ ↦ - gsFilteredCore inputs.points koalaBearLeeSubproductInterpContext - koalaBearRothRootContext gsSmallParams gsNonCodewordSmallErrors) - checksumPolynomialArrayKoala checksumIterations - let leeSubproductAlekRow <- runTimed - "guruswami-sudan-filtered-core-lee-subproduct-noncodeword-small-alekhnovich" "CBivariate" - "Lee-O'Sullivan subproduct + Alekhnovich roots + filter" - "KoalaBear.Field" gsNonCodewordSmallFilteredShape preset warmup - alekLeeSubproductMeasured - (fun _ ↦ - gsFilteredCore inputs.points koalaBearLeeSubproductInterpContext alekRootContext - gsSmallParams gsNonCodewordSmallErrors) - checksumPolynomialArrayKoala checksumIterations - let fastDenseRow <- runTimed - "guruswami-sudan-filtered-core-dense-noncodeword-small-fast" "CBivariate" - "Dense linear + RR roots + filter" - "KoalaBear.Fast.Field" gsNonCodewordSmallFilteredShape preset warmup - fastDenseMeasured - (fun _ ↦ - gsFilteredCore inputs.fastPoints fastKoalaBearDenseInterpContext - fastKoalaBearRothRootContext gsSmallParams gsNonCodewordSmallErrors) - checksumPolynomialArrayKoalaFast checksumIterations - let fastDenseAlekRow <- runTimed - "guruswami-sudan-filtered-core-dense-noncodeword-small-alekhnovich-fast" "CBivariate" - "Dense linear + Alekhnovich roots + filter" - "KoalaBear.Fast.Field" gsNonCodewordSmallFilteredShape preset warmup - fastAlekDenseMeasured - (fun _ ↦ - gsFilteredCore inputs.fastPoints fastKoalaBearDenseInterpContext - fastAlekRootContext gsSmallParams gsNonCodewordSmallErrors) - checksumPolynomialArrayKoalaFast checksumIterations + "guruswami-sudan-interp-lee-subproduct-stress" "CBivariate" + "Lee-O'Sullivan subproduct" + "KoalaBear.Field" gsStressInputShape preset warmup leeMeasured + (fun _ ↦ koalaBearLeeSubproductInterpContext.interpolate points gsStressParams) + (checksumInterpolationValidityOption points gsStressParams) + checksumIterations + let approximantDirectRow <- runTimed + "guruswami-sudan-interp-approximant-direct-stress" "CBivariate" + "Approximant basis direct" + "KoalaBear.Field" gsStressInputShape preset warmup approximantMeasured + (fun _ ↦ koalaBearApproximantBasisDirectInterpContext.interpolate points + gsStressParams) + (checksumInterpolationValidityOption points gsStressParams) + checksumIterations + let approximantSubproductRow <- runTimed + "guruswami-sudan-interp-approximant-subproduct-stress" "CBivariate" + "Approximant basis subproduct" + "KoalaBear.Field" gsStressInputShape preset warmup approximantMeasured + (fun _ ↦ koalaBearApproximantBasisSubproductInterpContext.interpolate points + gsStressParams) + (checksumInterpolationValidityOption points gsStressParams) + checksumIterations let fastLeeDirectRow <- runTimed - "guruswami-sudan-filtered-core-lee-direct-noncodeword-small-fast" "CBivariate" - "Lee-O'Sullivan direct + RR roots + filter" - "KoalaBear.Fast.Field" gsNonCodewordSmallFilteredShape preset warmup - fastLeeDirectMeasured - (fun _ ↦ - gsFilteredCore inputs.fastPoints fastKoalaBearLeeDirectInterpContext - fastKoalaBearRothRootContext gsSmallParams gsNonCodewordSmallErrors) - checksumPolynomialArrayKoalaFast checksumIterations - let fastLeeDirectAlekRow <- runTimed - "guruswami-sudan-filtered-core-lee-direct-noncodeword-small-alekhnovich-fast" "CBivariate" - "Lee-O'Sullivan direct + Alekhnovich roots + filter" - "KoalaBear.Fast.Field" gsNonCodewordSmallFilteredShape preset warmup - fastAlekLeeDirectMeasured - (fun _ ↦ - gsFilteredCore inputs.fastPoints fastKoalaBearLeeDirectInterpContext - fastAlekRootContext gsSmallParams gsNonCodewordSmallErrors) - checksumPolynomialArrayKoalaFast checksumIterations + "guruswami-sudan-interp-lee-direct-stress-fast" "CBivariate" + "Lee-O'Sullivan direct" + "KoalaBear.Fast.Field" gsStressInputShape preset warmup fastLeeMeasured + (fun _ ↦ fastKoalaBearLeeDirectInterpContext.interpolate fastPoints + gsStressParams) + (checksumInterpolationValidityOption fastPoints gsStressParams) + checksumIterations let fastLeeSubproductRow <- runTimed - "guruswami-sudan-filtered-core-lee-subproduct-noncodeword-small-fast" "CBivariate" - "Lee-O'Sullivan subproduct + RR roots + filter" - "KoalaBear.Fast.Field" gsNonCodewordSmallFilteredShape preset warmup - fastLeeSubproductMeasured - (fun _ ↦ - gsFilteredCore inputs.fastPoints fastKoalaBearLeeSubproductInterpContext - fastKoalaBearRothRootContext gsSmallParams gsNonCodewordSmallErrors) - checksumPolynomialArrayKoalaFast checksumIterations - let fastLeeSubproductAlekRow <- runTimed - "guruswami-sudan-filtered-core-lee-subproduct-noncodeword-small-alekhnovich-fast" "CBivariate" - "Lee-O'Sullivan subproduct + Alekhnovich roots + filter" - "KoalaBear.Fast.Field" gsNonCodewordSmallFilteredShape preset warmup - fastAlekLeeSubproductMeasured - (fun _ ↦ - gsFilteredCore inputs.fastPoints fastKoalaBearLeeSubproductInterpContext - fastAlekRootContext gsSmallParams gsNonCodewordSmallErrors) - checksumPolynomialArrayKoalaFast checksumIterations + "guruswami-sudan-interp-lee-subproduct-stress-fast" "CBivariate" + "Lee-O'Sullivan subproduct" + "KoalaBear.Fast.Field" gsStressInputShape preset warmup fastLeeMeasured + (fun _ ↦ fastKoalaBearLeeSubproductInterpContext.interpolate fastPoints + gsStressParams) + (checksumInterpolationValidityOption fastPoints gsStressParams) + checksumIterations + let fastApproximantDirectRow <- runTimed + "guruswami-sudan-interp-approximant-direct-stress-fast" "CBivariate" + "Approximant basis direct" + "KoalaBear.Fast.Field" gsStressInputShape preset warmup fastApproximantMeasured + (fun _ ↦ fastKoalaBearApproximantBasisDirectInterpContext.interpolate fastPoints + gsStressParams) + (checksumInterpolationValidityOption fastPoints gsStressParams) + checksumIterations + let fastApproximantSubproductRow <- runTimed + "guruswami-sudan-interp-approximant-subproduct-stress-fast" "CBivariate" + "Approximant basis subproduct" + "KoalaBear.Fast.Field" gsStressInputShape preset warmup fastApproximantMeasured + (fun _ ↦ fastKoalaBearApproximantBasisSubproductInterpContext.interpolate + fastPoints gsStressParams) + (checksumInterpolationValidityOption fastPoints gsStressParams) + checksumIterations pure ({ - groupKey := "guruswami-sudan-filtered-core-noncodeword-small-koalabear", - title := "Guruswami-Sudan filtered core on perturbed received word, small (KoalaBear)", + groupKey := "guruswami-sudan-interp-stress-koalabear", + title := "Guruswami-Sudan interpolation on perturbed received word, stress (KoalaBear)", records := #[ - denseRow, denseAlekRow, leeDirectRow, leeDirectAlekRow, - leeSubproductRow, leeSubproductAlekRow, - fastDenseRow, fastDenseAlekRow, fastLeeDirectRow, - fastLeeDirectAlekRow, fastLeeSubproductRow, fastLeeSubproductAlekRow + leeDirectRow, leeSubproductRow, approximantDirectRow, approximantSubproductRow, + fastLeeDirectRow, fastLeeSubproductRow, fastApproximantDirectRow, + fastApproximantSubproductRow ] }, gen) @@ -437,11 +287,8 @@ def guruswamiSudanReceivedWordTasks : List BenchTask := [ ⟨"guruswami-sudan-interp-noncodeword-small-koalabear", ""⟩) runGsInterpolationNonCodewordSmallKoala, BenchTask.fromGroupRunner (guruswamiSudanReceivedWordGroupInfos.getD 1 - ⟨"guruswami-sudan-core-noncodeword-small-koalabear", ""⟩) - runGsCoreNonCodewordSmallKoala, - BenchTask.fromGroupRunner (guruswamiSudanReceivedWordGroupInfos.getD 2 - ⟨"guruswami-sudan-filtered-core-noncodeword-small-koalabear", ""⟩) - runGsFilteredCoreNonCodewordSmallKoala + ⟨"guruswami-sudan-interp-stress-koalabear", ""⟩) + runGsInterpolationStressKoala ] end CompPolyBench diff --git a/bench/CompPolyBench/Bivariate/GuruswamiSudan/Shared.lean b/bench/CompPolyBench/Bivariate/GuruswamiSudan/Shared.lean index b9c64092..8909f4b9 100644 --- a/bench/CompPolyBench/Bivariate/GuruswamiSudan/Shared.lean +++ b/bench/CompPolyBench/Bivariate/GuruswamiSudan/Shared.lean @@ -5,16 +5,18 @@ Authors: Valerii Huhnin -/ import CompPolyBench.Common +import CompPoly.Bivariate.Deriv import CompPoly.Bivariate.GuruswamiSudan import CompPoly.Bivariate.GuruswamiSudan.Implementations +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Koetter.Algorithm import CompPoly.Bivariate.GuruswamiSudan.Root.FieldRoots.KoalaBear /-! -# Guruswami-Sudan Benchmark Helpers +# Guruswami-Sudan Benchmarks -Shared KoalaBear input shapes, checksum helpers, and group metadata for the -dense and Lee-O'Sullivan interpolation/root-search Guruswami-Sudan benchmark -subset. +KoalaBear cost-center benchmarks for the dense interpolation path, +Koetter interpolation path, Roth-Ruckenstein and Alekhnovich root finding, and +full backend-parametric `gsCore` and `gsFilteredCore`. -/ open CompPoly @@ -26,6 +28,22 @@ def gsPointCount : Nat := 128 def gsMessageDegree : Nat := 32 def gsWeightedDegreeBound : Nat := 4 * (gsMessageDegree - 1) def gsMultiplicity : Nat := 4 +def gsCheckMultiplicity : Nat := 2 + +def gsKoalaParams : GSInterpParams := + { messageDegree := gsMessageDegree + multiplicity := gsMultiplicity + weightedDegreeBound := gsWeightedDegreeBound } + +def gsLargeInterpPointCount : Nat := 192 +def gsLargeInterpMessageDegree : Nat := 49 +def gsLargeInterpWeightedDegreeBound : Nat := + 5 * (gsLargeInterpMessageDegree - 1) +def gsLargeInterpMultiplicity : Nat := 2 +def gsLargeInterpParams : GSInterpParams := + { messageDegree := gsLargeInterpMessageDegree + multiplicity := gsLargeInterpMultiplicity + weightedDegreeBound := gsLargeInterpWeightedDegreeBound } def gsSmallPointCount : Nat := 64 def gsSmallMessageDegree : Nat := 16 @@ -47,9 +65,19 @@ def gsSmallInputShape : String := def gsSmallInterpInputShape : String := gsSmallInputShape +def gsLargeInterpInputShape : String := + s!"n={gsLargeInterpPointCount},k={gsLargeInterpMessageDegree}," ++ + s!"m={gsLargeInterpMultiplicity},D={gsLargeInterpWeightedDegreeBound}" + +def gsMultiplicityShape : String := + s!"n={gsPointCount},k={gsMessageDegree},m={gsCheckMultiplicity},Q=(Y-p)^2" + def gsRootShape : String := s!"k={gsMessageDegree},Q=(Y-p)(Y-(p+7))" +def gsFilteredShape : String := + gsLargeInterpInputShape ++ ",r=0" + def gsSmallFilteredShape : String := gsSmallInterpInputShape ++ ",r=0" @@ -68,10 +96,20 @@ def gsSmallBenchmarkPoints {F : Type*} [Semiring F] [BEq F] [LawfulBEq F] (p : CPolynomial F) : Array (Prod F F) := codewordPointsWithCount gsSmallPointCount p -def nonlinearRootBenchmarkQ {F : Type*} +def gsLargeBenchmarkPoints {F : Type*} [Semiring F] [BEq F] [LawfulBEq F] + (p : CPolynomial F) : Array (Prod F F) := + codewordPointsWithCount gsLargeInterpPointCount p + +def rootBenchmarkQ {F : Type*} [CommRing F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] (p : CPolynomial F) : CBivariate F := - CBivariate.linearYDivisor p * CBivariate.linearYDivisor (p + CPolynomial.C 7) + CBivariate.linearYDivisor p + +def multiplicityBenchmarkQ {F : Type*} + [CommRing F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (p : CPolynomial F) : CBivariate F := + let Q := rootBenchmarkQ p + Q * Q def koalaFieldRoots : FieldRootContext KoalaBear.Field := koalaBearFieldRootContext def koalaFieldRootsFast : FieldRootContext KoalaBear.Field := @@ -81,11 +119,16 @@ def koalaFastFieldRoots : FieldRootContext KoalaBear.Fast.Field := def koalaFastFieldRootsFast : FieldRootContext KoalaBear.Fast.Field := fastKoalaBearNttFastFieldRootContext +def nonlinearRootBenchmarkQ {F : Type*} + [CommRing F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (p : CPolynomial F) : CBivariate F := + CBivariate.linearYDivisor p * CBivariate.linearYDivisor (p + CPolynomial.C 7) + def checksumDenseMatrix {F : Type*} [Zero F] - (checksum : F → Nat) (M : DenseMatrix F) : Nat := + (checksum : F -> Nat) (M : DenseMatrix F) : Nat := mixChecksum (mixChecksum M.rows M.cols) (checksumArray checksum M.data) -def checksumOptionArray {F : Type*} (checksum : F → Nat) +def checksumOptionArray {F : Type*} (checksum : F -> Nat) (v? : Option (Array F)) : Nat := match v? with | none => 0 @@ -99,6 +142,10 @@ def checksumInterpolationValidityOption {F : Type*} | none => 0 | some Q => if interpolationWitnessIsValidBool points params Q then 2 else 1 +def checksumPolynomialMatrix {F : Type*} [Zero F] + (checksum : F -> Nat) (M : PolynomialMatrix F) : Nat := + checksumArray (fun row ↦ checksumArray (checksumCPolynomial checksum) row) M + def checksumPolynomialArrayKoala (ps : Array (CPolynomial KoalaBear.Field)) : Nat := checksumArray (checksumCPolynomial checksumKoalaBear) ps @@ -106,6 +153,17 @@ def checksumPolynomialArrayKoalaFast (ps : Array (CPolynomial KoalaBear.Fast.Field)) : Nat := checksumArray (checksumCPolynomial checksumKoalaBearFast) ps +def checksumBool (b : Bool) : Nat := + if b then 1 else 0 + +def checkMultiplicityShiftOnce {F : Type*} + [CommSemiring F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (Q : CBivariate F) (r : Nat) (x y : F) : Bool := + let shifted := CBivariate.shiftC x y Q + List.all (List.range r) fun k ↦ + List.all (List.range (k + 1)) fun i ↦ + CBivariate.coeff shifted i (k - i) == 0 + def gsWarmupIterations (preset : BenchPreset) : Nat := preset.selectNat 1 0 0 @@ -121,14 +179,25 @@ def guruswamiSudanGroupInfos : List BenchGroupInfo := [ "Guruswami-Sudan dense interpolation solving, small (KoalaBear)"⟩, ⟨"guruswami-sudan-interp-small-koalabear", "Guruswami-Sudan interpolation, small (KoalaBear)"⟩, + ⟨"guruswami-sudan-interp-large-koalabear", + "Guruswami-Sudan interpolation, large (KoalaBear)"⟩, + ⟨"guruswami-sudan-lee-setup-large-koalabear", + "Guruswami-Sudan Lee-O'Sullivan setup, large (KoalaBear)"⟩, + ⟨"guruswami-sudan-hasse-koalabear", + "Guruswami-Sudan Hasse multiplicity checking (KoalaBear)"⟩, + ⟨"guruswami-sudan-compose-koalabear", + "Guruswami-Sudan composition in Y (KoalaBear)"⟩, ⟨"guruswami-sudan-root-koalabear", "Guruswami-Sudan root finding (KoalaBear)"⟩, ⟨"guruswami-sudan-core-small-koalabear", "Guruswami-Sudan full core, small (KoalaBear)"⟩, + ⟨"guruswami-sudan-core-large-koalabear", + "Guruswami-Sudan full core, large (KoalaBear)"⟩, ⟨"guruswami-sudan-packed-filter-koalabear", "Guruswami-Sudan packed distance filtering (KoalaBear)"⟩, ⟨"guruswami-sudan-filtered-core-small-koalabear", - "Guruswami-Sudan filtered core, small (KoalaBear)"⟩ + "Guruswami-Sudan filtered core, small (KoalaBear)"⟩, + ⟨"guruswami-sudan-filtered-core-large-koalabear", + "Guruswami-Sudan filtered core, large (KoalaBear)"⟩ ] -end CompPolyBench diff --git a/tests/CompPolyTests.lean b/tests/CompPolyTests.lean index 0321cad5..96935ce8 100644 --- a/tests/CompPolyTests.lean +++ b/tests/CompPolyTests.lean @@ -15,6 +15,7 @@ import CompPolyTests.Bivariate.GuruswamiSudan.Filter import CompPolyTests.Bivariate.GuruswamiSudan.Hasse import CompPolyTests.Bivariate.GuruswamiSudan.Interpolation.Dense import CompPolyTests.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan +import CompPolyTests.Bivariate.GuruswamiSudan.Interpolation.ApproximantBasis import CompPolyTests.Bivariate.GuruswamiSudan.Root.Alekhnovich import CompPolyTests.Bivariate.GuruswamiSudan.Root.FieldRoots.KoalaBear import CompPolyTests.Bivariate.GuruswamiSudan.Root.RothRuckenstein @@ -26,6 +27,7 @@ import CompPolyTests.Fields.Binary.BF128Ghash.Prelude import CompPolyTests.Fields.KoalaBear.Fast import CompPolyTests.Fields.PrattCertificate import CompPolyTests.LinearAlgebra.Dense +import CompPolyTests.LinearAlgebra.PolynomialMatrix.Approximant import CompPolyTests.Multilinear.Equiv import CompPolyTests.Multivariate.CMvMonomial import CompPolyTests.Multivariate.Restrict diff --git a/tests/CompPolyTests/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis.lean b/tests/CompPolyTests/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis.lean new file mode 100644 index 00000000..42d3b88d --- /dev/null +++ b/tests/CompPolyTests/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis.lean @@ -0,0 +1,223 @@ +/- +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.Interpolation.ApproximantBasis +import CompPoly.Bivariate.GuruswamiSudan.Core +import CompPoly.Bivariate.GuruswamiSudan.Root.RothRuckenstein.Correctness +import Mathlib.Algebra.Field.ZMod + +/-! +# Approximant-Basis Guruswami-Sudan Interpolation Tests + +Focused executable coverage for GS modular-data construction and diagonal +relation checks. +-/ + +namespace CompPolyTests + +open CompPoly +open CompPoly.GuruswamiSudan +open CompPoly.GuruswamiSudan.ApproximantBasis +open CompPoly.PolynomialMatrix + +namespace GuruswamiSudan.Interpolation.ApproximantBasis + +abbrev F3 := ZMod 3 +abbrev F5 := ZMod 5 + +instance : Fact (Nat.Prime 3) := + ⟨by decide⟩ + +instance : Fact (Nat.Prime 5) := + ⟨by decide⟩ + +private def X : CPolynomial F3 := + CPolynomial.X + +private def paramsS1 : GSInterpParams := + { messageDegree := 2, multiplicity := 1, weightedDegreeBound := 2 } + +private def paramsS2 : GSInterpParams := + { messageDegree := 2, multiplicity := 2, weightedDegreeBound := 4 } + +private def paramsS3 : GSInterpParams := + { messageDegree := 2, multiplicity := 3, weightedDegreeBound := 6 } + +private def lowParams : GSInterpParams := + { messageDegree := 1, multiplicity := 1, weightedDegreeBound := 0 } + +private def nonCodewordParams : GSInterpParams := + { messageDegree := 2, multiplicity := 1, weightedDegreeBound := 3 } + +private def nonCodewordStressParams : GSInterpParams := + { messageDegree := 2, multiplicity := 2, weightedDegreeBound := 3 } + +private def points : Array (F3 × F3) := + #[(0, 0), (1, 1)] + +private def nonCodewordPoints : Array (F5 × F5) := + #[(0, 0), (1, 1), (2, 0)] + +private def lowPoints : Array (F3 × F3) := + #[(0, 0)] + +private def duplicateXPoints : Array (F3 × F3) := + #[(0, 0), (0, 1)] + +private def directV : CPolynomial.VanishingPolynomialContext F3 := + CPolynomial.VanishingPolynomialContext.direct + +private def hornerE : CPolynomial.BatchEvalContext F3 := + CPolynomial.BatchEvalContext.horner F3 + +private def modCtx : CPolynomial.ModContext F3 := + CPolynomial.ModContext.remainderOnly + +private def directV5 : CPolynomial.VanishingPolynomialContext F5 := + CPolynomial.VanishingPolynomialContext.direct + +private def hornerE5 : CPolynomial.BatchEvalContext F5 := + CPolynomial.BatchEvalContext.horner F5 + +private def modCtx5 : CPolynomial.ModContext F5 := + CPolynomial.ModContext.remainderOnly + +private def naiveMul : CPolynomial.MulContext F3 := + CPolynomial.MulContext.naive + +private def naiveMul5 : CPolynomial.MulContext F5 := + CPolynomial.MulContext.naive + +private def dataS1 : GSModularData F3 := + buildGSModularData directV hornerE naiveMul modCtx points paramsS1 + +private def dataS2 : GSModularData F3 := + buildGSModularData directV hornerE naiveMul modCtx points paramsS2 + +private def dataS3 : GSModularData F3 := + buildGSModularData directV hornerE naiveMul modCtx points paramsS3 + +private def nonCodewordData : GSModularData F5 := + buildGSModularData directV5 hornerE5 naiveMul5 modCtx5 nonCodewordPoints + nonCodewordParams + +private def nonCodewordStressData : GSModularData F5 := + buildGSModularData directV5 hornerE5 naiveMul5 modCtx5 nonCodewordPoints + nonCodewordStressParams + +private def pmCtx : PolynomialMatrix.Approximant.PMBasisContext F3 := + PolynomialMatrix.Approximant.kernelLeafPMBasisContext + CPolynomial.MulContext.naive 32 + +private def solver : PolynomialMatrix.Approximant.ModularSolutionBasisContext F3 := + PolynomialMatrix.Approximant.modularSolutionBasisContextViaPMBasis + CPolynomial.MulContext.naive modCtx pmCtx + +private def pmCtx5 : PolynomialMatrix.Approximant.PMBasisContext F5 := + PolynomialMatrix.Approximant.kernelLeafPMBasisContext + CPolynomial.MulContext.naive 32 + +private def solver5 : PolynomialMatrix.Approximant.ModularSolutionBasisContext F5 := + PolynomialMatrix.Approximant.modularSolutionBasisContextViaPMBasis + CPolynomial.MulContext.naive modCtx5 pmCtx5 + +private def approxContext : GSInterpContext F3 := + approximantBasisInterpContext directV hornerE solver + +private def approxContext5 : GSInterpContext F5 := + approximantBasisInterpContext directV5 hornerE5 solver5 + +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 rootContext : GSRootContext F3 := + rothRuckensteinRootContext F3 fieldRoots + +private def rowYMinusX : PolynomialRow F3 := + #[-X, 1, 0] + +private def rowYMinusXSquared : PolynomialRow F3 := + #[X ^ 2, -(CPolynomial.C (2 : F3) * X), 1, 0, 0] + +#guard interpolationYCap paramsS1 == 2 +#guard interpolationWidth paramsS1 == 3 +#guard interpolationShifts paramsS1 == #[0, 1, 2] +#guard distinctXCoordinatesBool points +#guard !distinctXCoordinatesBool duplicateXPoints + +#guard dataS1.moduli.size == 1 +#guard dataS1.matrix.size == 3 +#guard MatrixWidth dataS1.matrix == 1 +#guard dataS1.shift == #[0, 1, 2] +#guard rowGet (dataS1.matrix.getD 0 #[]) 0 == 1 +#guard rowGet (dataS1.matrix.getD 1 #[]) 0 == X +#guard rowGet (dataS1.matrix.getD 2 #[]) 0 == X +#guard rowSatisfiesModularBool CPolynomial.MulContext.naive modCtx + rowYMinusX dataS1.matrix dataS1.moduli + +#guard dataS2.moduli.size == 2 +#guard dataS2.matrix.size == 5 +#guard MatrixWidth dataS2.matrix == 2 +#guard dataS2.moduli.getD 0 0 == dataS2.G ^ 2 +#guard dataS2.moduli.getD 1 0 == dataS2.G +#guard rowGet (dataS2.matrix.getD 0 #[]) 1 == 0 +#guard rowGet (dataS2.matrix.getD 1 #[]) 1 == 1 +#guard rowSatisfiesModularBool CPolynomial.MulContext.naive modCtx + rowYMinusXSquared dataS2.matrix dataS2.moduli + +#guard dataS3.moduli.size == 3 +#guard dataS3.matrix.size == 7 +#guard MatrixWidth dataS3.matrix == 3 +#guard dataS3.moduli.getD 0 0 == dataS3.G ^ 3 +#guard dataS3.moduli.getD 1 0 == dataS3.G ^ 2 +#guard dataS3.moduli.getD 2 0 == dataS3.G + +#guard approximantBasisPositiveInterpolate directV hornerE solver + duplicateXPoints paramsS1 == none + +#guard (approximantBasisInterpolate directV hornerE solver points paramsS1).isSome +#guard match approximantBasisInterpolate directV hornerE solver points paramsS1 with + | none => false + | some Q => interpolationWitnessIsValidBool points paramsS1 Q + +#guard (approximantBasisInterpolate directV hornerE solver lowPoints lowParams).isSome +#guard match approximantBasisInterpolate directV hornerE solver lowPoints lowParams with + | none => false + | some Q => interpolationWitnessIsValidBool lowPoints lowParams Q + +#guard (approximantBasisInterpolate directV5 hornerE5 solver5 + nonCodewordPoints nonCodewordParams).isSome +#guard match approximantBasisInterpolate directV5 hornerE5 solver5 + nonCodewordPoints nonCodewordParams with + | none => false + | some Q => interpolationWitnessIsValidBool nonCodewordPoints nonCodewordParams Q + +#guard (approximantBasisInterpolate directV5 hornerE5 solver5 + nonCodewordPoints nonCodewordStressParams).isSome +#guard match approximantBasisInterpolate directV5 hornerE5 solver5 + nonCodewordPoints nonCodewordStressParams with + | none => false + | some Q => interpolationWitnessIsValidBool nonCodewordPoints nonCodewordStressParams Q + +#guard (approxContext5.interpolate nonCodewordPoints nonCodewordStressParams).isSome +#guard match approxContext5.interpolate nonCodewordPoints nonCodewordStressParams with + | none => false + | some Q => interpolationWitnessIsValidBool nonCodewordPoints nonCodewordStressParams Q + +#guard (gsCore points approxContext rootContext paramsS1).size <= 3 +#guard (gsCore lowPoints approxContext rootContext lowParams).size <= 3 + +end GuruswamiSudan.Interpolation.ApproximantBasis + +end CompPolyTests diff --git a/tests/CompPolyTests/LinearAlgebra/PolynomialMatrix/Approximant.lean b/tests/CompPolyTests/LinearAlgebra/PolynomialMatrix/Approximant.lean new file mode 100644 index 00000000..4ac40e26 --- /dev/null +++ b/tests/CompPolyTests/LinearAlgebra/PolynomialMatrix/Approximant.lean @@ -0,0 +1,250 @@ +/- +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.LinearAlgebra.PolynomialMatrix.Approximant +import Mathlib.Algebra.Field.ZMod + +/-! +# Polynomial-Matrix Approximant Tests + +Focused executable checks for X-adic approximant problem sizing, recursive +PM-basis plumbing, partial-linearization orders, and exact-nullspace lifts. +-/ + +namespace CompPolyTests + +open CompPoly +open CompPoly.PolynomialMatrix +open CompPoly.PolynomialMatrix.Approximant + +namespace LinearAlgebra.PolynomialMatrix.Approximant + +abbrev F3 := ZMod 3 + +instance : Fact (Nat.Prime 3) := + ⟨by decide⟩ + +private def X : CPolynomial F3 := + CPolynomial.X + +private def problem : XAdicProblem F3 := + { orders := #[2] + matrix := #[ + #[1], + #[X] + ] } + +private def fullRankProblem : XAdicProblem F3 := + { orders := #[1] + matrix := #[ + #[1] + ] } + +private def identityLeaf (problem : XAdicProblem F3) (_shift : Array Nat) : + PolynomialMatrix F3 := + PolynomialMatrix.identity problem.matrix.size + +private def lowCtx : PolynomialMatrix.MulLowContext F3 := + PolynomialMatrix.MulLowContext.fromMulContext CPolynomial.MulContext.naive + +private def runtime : PMBasisRuntime F3 := + { mulContext := CPolynomial.MulContext.naive + lowMulContext := lowCtx + composeBasis := PolynomialMatrix.mulStrassenWith lowCtx 1 + residualProduct := PolynomialMatrix.mulTruncColumnStrassenWith lowCtx 1 + leafCutoff := 1 + leafBasis := identityLeaf } + +private def kernelLeafRows : PolynomialMatrix F3 := + kernelLeafBasis problem #[0, 0] + +private def firstPivotOnlyRows : PolynomialMatrix F3 := + #[ + #[1, 0] + ] + +private def compositionLeft : PolynomialMatrix F3 := + #[ + #[1 + X, X ^ 2], + #[X, 1] + ] + +private def compositionRight : PolynomialMatrix F3 := + #[ + #[1, X], + #[X + 1, X ^ 2] + ] + +private def rectangularLeft : PolynomialMatrix F3 := + #[ + #[1, X], + #[X + 1, X ^ 2], + #[0, 1] + ] + +private def rectangularRight : PolynomialMatrix F3 := + #[ + #[X, 1, X ^ 2], + #[1, X + 1, 0] + ] + +private def moduli : Array (CPolynomial F3) := + #[X ^ 2, X] + +private def equation : ModularEquation F3 := + { moduli := moduli + matrix := #[ + #[1, X], + #[X, 1] + ] } + +private def chunkPlan : PartialLinearizationPlan := + partialLinearizationPlan 2 2 moduli #[0, 5] + +private def profileChunkPlan : PartialLinearizationPlan := + partialLinearizationPlanFromPivotDegrees 2 2 moduli #[0, 5] #[some 7, none] + +private def highDegreeProfileChunkPlan : PartialLinearizationPlan := + partialLinearizationPlanFromPivotDegrees 2 2 moduli #[0, 20] + #[some 25, some 45] + +private def noChunkPlan : PartialLinearizationPlan := + unchunkedPartialLinearizationPlan 2 2 moduli + +private def chunkedRow : PolynomialRow F3 := + #[1, X, 0, 1, 0, 0] + +private def profileRows : PolynomialMatrix F3 := + #[ + #[X ^ 2, 0], + #[0, X] + ] + +private def profileFromRows : PivotDegreeProfile := + pivotDegreeProfileFromRows 2 profileRows #[0, 5] + +private def productionPMCtx : PMBasisContext F3 := + kernelLeafPMBasisContext CPolynomial.MulContext.naive 1 + +private def remainderModCtx : CPolynomial.ModContext F3 := + CPolynomial.ModContext.remainderOnly + +private def discoveredProfile : PivotDegreeProfile := + discoverPivotDegreeProfileViaPMBasis CPolynomial.MulContext.naive + remainderModCtx productionPMCtx equation #[0, 5] + +private def knownDegreePlan : PartialLinearizationPlan := + partialLinearizationPlanFromPivotDegrees equation.solutionWidth equation.modularWidth + equation.moduli #[0, 5] discoveredProfile.degrees + +private def knownDegreeProblem : XAdicProblem F3 := + chunkedExactNullspaceProblemForShift remainderModCtx equation knownDegreePlan #[0, 5] + +private def knownDegreeExpandedRows : PolynomialMatrix F3 := + productionPMCtx.basis knownDegreeProblem + (chunkedExactNullspaceShift knownDegreePlan #[0, 5]) + +private def knownDegreeCompressedRows : PolynomialMatrix F3 := + compactNonzeroRows + (compressChunkedPrincipalRows knownDegreePlan knownDegreeExpandedRows) + +private def knownDegreeRows : PolynomialMatrix F3 := + knownDegreeSolutionBasisViaPMBasis remainderModCtx productionPMCtx equation #[0, 5] + discoveredProfile + +private def knownDegreeFilteredRows : PolynomialMatrix F3 := + filterModularSolutionRows CPolynomial.MulContext.naive + CPolynomial.ModContext.remainderOnly equation knownDegreeRows + +private def productionRows : PolynomialMatrix F3 := + filteredSolutionBasisViaPMBasis CPolynomial.MulContext.naive + CPolynomial.ModContext.remainderOnly productionPMCtx equation #[0, 5] + +private def debugUnchunkedRows : PolynomialMatrix F3 := + debugUnchunkedFilteredSolutionBasisViaPMBasis CPolynomial.MulContext.naive + CPolynomial.ModContext.remainderOnly productionPMCtx equation #[0, 5] + +private def leastChoiceRows : PolynomialMatrix F3 := + #[ + #[X ^ 2, 0], + #[1, 0], + #[0, X] + ] + +#guard maxOrder problem == 2 +#guard totalOrder problem == 2 +#guard lowerOrders problem 1 == #[1] +#guard residualOrders problem 1 == #[1] +#guard leafDegreeCap problem == 2 +#guard (coefficientMatrix problem).rows == 2 +#guard (coefficientMatrix problem).cols == 4 +#guard kernelLeafRows.any fun row ↦ row == #[-X, 1] +#guard (kernelLeafBasis fullRankProblem #[0]).any fun row ↦ row == #[X] +#guard rowsContainLeadingPosition firstPivotOnlyRows #[0, 0] 0 +#guard !rowsContainLeadingPosition firstPivotOnlyRows #[0, 0] 1 +#guard missingCompletionRows problem #[0, 0] firstPivotOnlyRows == #[#[0, X ^ 2]] +#guard rowGet (rowMulMatrixTruncColumnWith lowCtx #[2] #[1, X] problem.matrix) 0 == 1 +#guard mulBoundedWith lowCtx compositionLeft compositionRight == + mulWith CPolynomial.MulContext.naive compositionLeft compositionRight +#guard mulStrassenWith lowCtx 1 compositionLeft compositionRight == + mulWith CPolynomial.MulContext.naive compositionLeft compositionRight +#guard mulStrassenWith lowCtx 1 rectangularLeft rectangularRight == + mulWith CPolynomial.MulContext.naive rectangularLeft rectangularRight +#guard mulTruncColumnStrassenWith lowCtx 1 #[2, 1, 3] + rectangularLeft rectangularRight == + mulTruncColumnWith lowCtx #[2, 1, 3] rectangularLeft rectangularRight + +#guard (pmBasis runtime problem #[0, 0]).size == 2 +#guard MatrixWidth (pmBasis runtime problem #[0, 0]) == 2 + +#guard modulusDegreeMass moduli == 3 +#guard chunkDelta 2 moduli == 2 +#guard linearizedOrders 2 moduli == #[5, 4] +#guard chunkPlan.chunks.size == 4 +#guard chunkedExactNullspaceShift chunkPlan #[0, 5] == #[3, 5, 7, 8, 2, 2] +#guard profileChunkPlan.chunks.size == 5 +#guard highDegreeProfileChunkPlan.chunks.size > profileChunkPlan.chunks.size +#guard noChunkPlan.chunks == #[{ coord := 0, offset := 0 }, { coord := 1, offset := 0 }] +#guard profileFromRows.degrees == #[some 2, some 6] +#guard (chunkedExactNullspaceLift remainderModCtx equation chunkPlan).size == 6 +#guard MatrixWidth (chunkedExactNullspaceLift remainderModCtx equation chunkPlan) == 2 +#guard rowGet ((chunkedExactNullspaceLift remainderModCtx equation chunkPlan).getD 1 #[]) 0 == 0 +#guard rowGet ((chunkedExactNullspaceLift remainderModCtx equation chunkPlan).getD 3 #[]) 0 == X +#guard compressChunkedPrincipalRow chunkPlan chunkedRow == #[1 + X ^ 3, 1] + +#guard equation.solutionWidth == 2 +#guard equation.modularWidth == 2 +#guard (exactNullspaceLift equation).size == 4 +#guard MatrixWidth (exactNullspaceLift equation) == 2 +#guard rowGet ((exactNullspaceLift equation).getD 2 #[]) 0 == -(X ^ 2) +#guard rowGet ((exactNullspaceLift equation).getD 3 #[]) 1 == -X +#guard (filterModularSolutionRows CPolynomial.MulContext.naive + CPolynomial.ModContext.remainderOnly equation #[PolynomialMatrix.zeroRow 2]).isEmpty + +#guard knownDegreeCompressedRows == knownDegreeRows +#guard !knownDegreeRows.isEmpty +#guard knownDegreeRows.all fun row ↦ + rowSatisfiesModularBool CPolynomial.MulContext.naive + CPolynomial.ModContext.remainderOnly row equation.matrix equation.moduli +#guard !knownDegreeFilteredRows.isEmpty +#guard knownDegreeFilteredRows.all fun row ↦ + rowSatisfiesModularBool CPolynomial.MulContext.naive + CPolynomial.ModContext.remainderOnly row equation.matrix equation.moduli +#guard !productionRows.isEmpty +#guard MatrixWidth productionRows == equation.solutionWidth +#guard productionRows.all fun row ↦ + rowSatisfiesModularBool CPolynomial.MulContext.naive + CPolynomial.ModContext.remainderOnly row equation.matrix equation.moduli +#guard debugUnchunkedRows.all fun row ↦ + rowSatisfiesModularBool CPolynomial.MulContext.naive + CPolynomial.ModContext.remainderOnly row equation.matrix equation.moduli +#guard match leastShiftedDegreeChoice? leastChoiceRows #[0, 3] with + | some choice => choice.index == 1 && choice.degree == 0 && choice.row == #[1, 0] + | none => false + +end LinearAlgebra.PolynomialMatrix.Approximant + +end CompPolyTests From fbd8f3ddeedee5de43ba26dedd50e08b15ea626d Mon Sep 17 00:00:00 2001 From: Valerii Huhnin Date: Wed, 10 Jun 2026 15:30:06 +0000 Subject: [PATCH 02/10] Improve Lee-O'Sullivan performance (faster Mulders-Storjohann) --- CompPoly.lean | 83 ++++++++++++++++++- .../GuruswamiSudan/Implementations.lean | 51 ++++++++++-- .../MuldersStorjohannCorrectness/Fast.lean | 11 +-- .../Interpolation/LeeOSullivan.lean | 19 +++++ 4 files changed, 152 insertions(+), 12 deletions(-) diff --git a/CompPoly.lean b/CompPoly.lean index 46108405..e823d381 100644 --- a/CompPoly.lean +++ b/CompPoly.lean @@ -5,23 +5,39 @@ import CompPoly.Bivariate.Deriv import CompPoly.Bivariate.Factor import CompPoly.Bivariate.FactorMonic import CompPoly.Bivariate.GuruswamiSudan +import CompPoly.Bivariate.GuruswamiSudan.Compose import CompPoly.Bivariate.GuruswamiSudan.Context import CompPoly.Bivariate.GuruswamiSudan.Core import CompPoly.Bivariate.GuruswamiSudan.CoreCorrectness import CompPoly.Bivariate.GuruswamiSudan.Executable import CompPoly.Bivariate.GuruswamiSudan.Filter import CompPoly.Bivariate.GuruswamiSudan.FilterCorrectness +import CompPoly.Bivariate.GuruswamiSudan.Hasse import CompPoly.Bivariate.GuruswamiSudan.Implementations +import CompPoly.Bivariate.GuruswamiSudan.Interpolation +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.ApproximantBasis +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.ApproximantBasis.Algorithm +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.ApproximantBasis.Basic +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.ApproximantBasis.Correctness import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Basic import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Correctness import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Dense.Algorithm import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Dense.Correctness +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Koetter.Algorithm +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Koetter.Basic +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Koetter.Correctness +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Koetter.Correctness.Algebra +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Koetter.Correctness.Combinations +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Koetter.Correctness.Completeness +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Koetter.Correctness.Selection +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Koetter.Correctness.Soundness +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Koetter.Correctness.Transport +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Koetter.Correctness.Update import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Algorithm import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Basic import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Correctness import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Correctness.Basis -import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Correctness.Combinations import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Correctness.Common import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Correctness.Completeness import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Correctness.Divisibility @@ -29,14 +45,18 @@ import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Correctness. import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Correctness.Rows import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Correctness.Selection import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Correctness.Soundness +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.Alekhnovich.Algorithm import CompPoly.Bivariate.GuruswamiSudan.Root.Alekhnovich.Correctness import CompPoly.Bivariate.GuruswamiSudan.Root.Alekhnovich.Lemmas +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.Binary import CompPoly.Bivariate.GuruswamiSudan.Root.FieldRoots.FiniteField import CompPoly.Bivariate.GuruswamiSudan.Root.FieldRoots.KoalaBear import CompPoly.Bivariate.GuruswamiSudan.Root.RothRuckenstein.Algorithm @@ -45,7 +65,6 @@ 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.Kronecker import CompPoly.Bivariate.ToPoly import CompPoly.Data.Array.Lemmas import CompPoly.Data.Classes.DCast @@ -80,6 +99,47 @@ import CompPoly.Fields.Binary.BF128Ghash.Prelude import CompPoly.Fields.Binary.BF128Ghash.XPowTwoPowGcdCertificate import CompPoly.Fields.Binary.BF128Ghash.XPowTwoPowModCertificate import CompPoly.Fields.Binary.Common +import CompPoly.Fields.Binary.Extension.Basic +import CompPoly.Fields.Binary.Extension.Enumeration +import CompPoly.Fields.Binary.Extension.Impl +import CompPoly.Fields.Binary.Extension.Prelude +import CompPoly.Fields.Binary.Extension.Primitive +import CompPoly.Fields.Binary.GF2_32 +import CompPoly.Fields.Binary.GF2_32.Basic +import CompPoly.Fields.Binary.GF2_32.Impl +import CompPoly.Fields.Binary.GF2_32.Prelude +import CompPoly.Fields.Binary.GF2_32.Primitive +import CompPoly.Fields.Binary.GF2_32.PrimitivePowerCertificate +import CompPoly.Fields.Binary.GF2_32.RootContexts +import CompPoly.Fields.Binary.GF2_32.XPowTwoPowGcdCertificate +import CompPoly.Fields.Binary.GF2_32.XPowTwoPowModCertificate +import CompPoly.Fields.Binary.GF2_48 +import CompPoly.Fields.Binary.GF2_48.Basic +import CompPoly.Fields.Binary.GF2_48.Impl +import CompPoly.Fields.Binary.GF2_48.Prelude +import CompPoly.Fields.Binary.GF2_48.Primitive +import CompPoly.Fields.Binary.GF2_48.PrimitivePowerCertificate +import CompPoly.Fields.Binary.GF2_48.RootContexts +import CompPoly.Fields.Binary.GF2_48.XPowTwoPowGcdCertificate +import CompPoly.Fields.Binary.GF2_48.XPowTwoPowModCertificate +import CompPoly.Fields.Binary.GF2_64 +import CompPoly.Fields.Binary.GF2_64.Basic +import CompPoly.Fields.Binary.GF2_64.Impl +import CompPoly.Fields.Binary.GF2_64.Prelude +import CompPoly.Fields.Binary.GF2_64.Primitive +import CompPoly.Fields.Binary.GF2_64.PrimitivePowerCertificate +import CompPoly.Fields.Binary.GF2_64.RootContexts +import CompPoly.Fields.Binary.GF2_64.XPowTwoPowGcdCertificate +import CompPoly.Fields.Binary.GF2_64.XPowTwoPowModCertificate +import CompPoly.Fields.Binary.GF2_72 +import CompPoly.Fields.Binary.GF2_72.Basic +import CompPoly.Fields.Binary.GF2_72.Impl +import CompPoly.Fields.Binary.GF2_72.Prelude +import CompPoly.Fields.Binary.GF2_72.Primitive +import CompPoly.Fields.Binary.GF2_72.PrimitivePowerCertificate +import CompPoly.Fields.Binary.GF2_72.RootContexts +import CompPoly.Fields.Binary.GF2_72.XPowTwoPowGcdCertificate +import CompPoly.Fields.Binary.GF2_72.XPowTwoPowModCertificate import CompPoly.Fields.Binary.Tower.Abstract.Algebra import CompPoly.Fields.Binary.Tower.Abstract.Basis import CompPoly.Fields.Binary.Tower.Abstract.Core @@ -114,6 +174,12 @@ import CompPoly.LinearAlgebra.Dense.RowOpsCorrectness import CompPoly.LinearAlgebra.Dense.RrefSemantics import CompPoly.LinearAlgebra.Dense.RrefShape import CompPoly.LinearAlgebra.PolynomialMatrix +import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant +import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant.Basic +import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant.Correctness +import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant.ModularEquation +import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant.PMBasis +import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant.PartialLinearization import CompPoly.LinearAlgebra.PolynomialMatrix.Basic import CompPoly.LinearAlgebra.PolynomialMatrix.Degree import CompPoly.LinearAlgebra.PolynomialMatrix.MuldersStorjohann @@ -127,6 +193,7 @@ import CompPoly.LinearAlgebra.PolynomialMatrix.MuldersStorjohannCorrectness.Meas import CompPoly.LinearAlgebra.PolynomialMatrix.MuldersStorjohannCorrectness.Minimal import CompPoly.LinearAlgebra.PolynomialMatrix.MuldersStorjohannCorrectness.Reduction import CompPoly.LinearAlgebra.PolynomialMatrix.MuldersStorjohannCorrectness.RowOps +import CompPoly.LinearAlgebra.PolynomialMatrix.Operations import CompPoly.LinearAlgebra.PolynomialMatrix.RowSpan import CompPoly.LinearAlgebra.PolynomialMatrix.Shifted import CompPoly.LinearAlgebra.PolynomialMatrix.ShiftedReduction @@ -217,7 +284,19 @@ import CompPoly.Univariate.Roots.Context import CompPoly.Univariate.Roots.Correctness import CompPoly.Univariate.Roots.Enumeration import CompPoly.Univariate.Roots.Extraction +import CompPoly.Univariate.Roots.LasVegas +import CompPoly.Univariate.Roots.LasVegas.Basic +import CompPoly.Univariate.Roots.LasVegas.Correctness +import CompPoly.Univariate.Roots.LasVegas.Correctness.Common +import CompPoly.Univariate.Roots.LasVegas.Correctness.EvenTrace +import CompPoly.Univariate.Roots.LasVegas.Correctness.Loop +import CompPoly.Univariate.Roots.LasVegas.Correctness.Odd +import CompPoly.Univariate.Roots.LasVegas.Probability import CompPoly.Univariate.Roots.RootProduct +import CompPoly.Univariate.Roots.Shoup +import CompPoly.Univariate.Roots.Shoup.Basic +import CompPoly.Univariate.Roots.Shoup.Correctness +import CompPoly.Univariate.Roots.Shoup.FrobeniusLinear import CompPoly.Univariate.Roots.SmoothSubgroup import CompPoly.Univariate.Roots.SmoothSubgroup.Basic import CompPoly.Univariate.Roots.SmoothSubgroup.Correctness diff --git a/CompPoly/Bivariate/GuruswamiSudan/Implementations.lean b/CompPoly/Bivariate/GuruswamiSudan/Implementations.lean index f1eae870..1a2d196b 100644 --- a/CompPoly/Bivariate/GuruswamiSudan/Implementations.lean +++ b/CompPoly/Bivariate/GuruswamiSudan/Implementations.lean @@ -5,10 +5,7 @@ Authors: Valerii Huhnin -/ import CompPoly.Bivariate.GuruswamiSudan.Executable -import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Dense.Correctness -import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Correctness import CompPoly.Bivariate.GuruswamiSudan.Root.FieldRoots.KoalaBear -import CompPoly.Bivariate.GuruswamiSudan.Root.RothRuckenstein.Correctness import CompPoly.LinearAlgebra.PolynomialMatrix.MuldersStorjohannCorrectness.Fast import CompPoly.Univariate.BatchEval.Context import CompPoly.Univariate.NTT.KoalaBear @@ -16,8 +13,9 @@ import CompPoly.Univariate.NTT.KoalaBear /-! # Guruswami-Sudan Concrete Implementations -Named concrete dense and Lee-O'Sullivan interpolation/Roth-Ruckenstein -implementations and correctness theorem specializations for the decoder surface. +Named concrete dense-interpolation/Roth-Ruckenstein implementations and +correctness theorem specializations for the implementations exercised by the +benchmark suite. -/ namespace CompPoly @@ -32,6 +30,14 @@ def koalaBearDenseInterpContext : GSInterpContext KoalaBear.Field := def fastKoalaBearDenseInterpContext : GSInterpContext KoalaBear.Fast.Field := denseInterpContext KoalaBear.Fast.Field +/-- Direct Koetter interpolation backend over canonical KoalaBear. -/ +def koalaBearKoetterInterpContext : GSInterpContext KoalaBear.Field := + koetterInterpContext KoalaBear.Field + +/-- Direct Koetter interpolation backend over native-word fast KoalaBear. -/ +def fastKoalaBearKoetterInterpContext : GSInterpContext KoalaBear.Fast.Field := + koetterInterpContext KoalaBear.Fast.Field + /-- NTTFast-backed univariate multiplication over canonical KoalaBear. -/ def koalaBearNttFastMulContext : CPolynomial.MulContext KoalaBear.Field := CPolynomial.MulContext.nttFast CPolynomial.NTT.KoalaBear.bestDomainForLength? @@ -203,6 +209,22 @@ def fastKoalaBearRothRootContext : GSRootContext KoalaBear.Fast.Field := def fastKoalaBearRothNttFastRootContext : GSRootContext KoalaBear.Fast.Field := rothRuckensteinRootContext KoalaBear.Fast.Field fastKoalaBearNttFastFieldRootContext +/-- Alekhnovich root backend over canonical KoalaBear. -/ +def koalaBearAlekhnovichRootContext : GSRootContext KoalaBear.Field := + alekhnovichRootContext KoalaBear.Field koalaBearFieldRootContext + +/-- Alekhnovich root backend over canonical KoalaBear with NTTFast field roots. -/ +def koalaBearAlekhnovichNttFastRootContext : GSRootContext KoalaBear.Field := + alekhnovichRootContext KoalaBear.Field koalaBearNttFastFieldRootContext + +/-- Alekhnovich root backend over native-word fast KoalaBear. -/ +def fastKoalaBearAlekhnovichRootContext : GSRootContext KoalaBear.Fast.Field := + alekhnovichRootContext KoalaBear.Fast.Field fastKoalaBearFieldRootContext + +/-- Alekhnovich root backend over native-word fast KoalaBear with NTTFast field roots. -/ +def fastKoalaBearAlekhnovichNttFastRootContext : GSRootContext KoalaBear.Fast.Field := + alekhnovichRootContext KoalaBear.Fast.Field fastKoalaBearNttFastFieldRootContext + /-- Filtered dense/Roth context over canonical KoalaBear. -/ def koalaBearDenseRothContext : GSFilteredCoreContext KoalaBear.Field := filteredCoreContextOfInterpRootContexts koalaBearDenseInterpContext koalaBearRothRootContext @@ -222,6 +244,25 @@ def fastKoalaBearDenseRothNttFastContext : GSFilteredCoreContext KoalaBear.Fast. filteredCoreContextOfInterpRootContexts fastKoalaBearDenseInterpContext fastKoalaBearRothNttFastRootContext +/-- Filtered Koetter/Roth context over canonical KoalaBear. -/ +def koalaBearKoetterRothContext : GSFilteredCoreContext KoalaBear.Field := + filteredCoreContextOfInterpRootContexts koalaBearKoetterInterpContext koalaBearRothRootContext + +/-- Filtered Koetter/Roth context over canonical KoalaBear with NTTFast field roots. -/ +def koalaBearKoetterRothNttFastContext : GSFilteredCoreContext KoalaBear.Field := + filteredCoreContextOfInterpRootContexts koalaBearKoetterInterpContext + koalaBearRothNttFastRootContext + +/-- Filtered Koetter/Roth context over native-word fast KoalaBear. -/ +def fastKoalaBearKoetterRothContext : GSFilteredCoreContext KoalaBear.Fast.Field := + filteredCoreContextOfInterpRootContexts fastKoalaBearKoetterInterpContext + fastKoalaBearRothRootContext + +/-- Filtered Koetter/Roth context over native-word fast KoalaBear with NTTFast field roots. -/ +def fastKoalaBearKoetterRothNttFastContext : GSFilteredCoreContext KoalaBear.Fast.Field := + filteredCoreContextOfInterpRootContexts fastKoalaBearKoetterInterpContext + fastKoalaBearRothNttFastRootContext + /-- Concrete soundness for the canonical KoalaBear dense/Roth core. -/ theorem koalaBearDenseRothGsCore_sound {points : Array (KoalaBear.Field × KoalaBear.Field)} {params : GSInterpParams} {p : CPolynomial KoalaBear.Field} diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix/MuldersStorjohannCorrectness/Fast.lean b/CompPoly/LinearAlgebra/PolynomialMatrix/MuldersStorjohannCorrectness/Fast.lean index 08320317..79844b08 100644 --- a/CompPoly/LinearAlgebra/PolynomialMatrix/MuldersStorjohannCorrectness/Fast.lean +++ b/CompPoly/LinearAlgebra/PolynomialMatrix/MuldersStorjohannCorrectness/Fast.lean @@ -9,10 +9,11 @@ import CompPoly.LinearAlgebra.PolynomialMatrix.MuldersStorjohannCorrectness.Mini /-! # Fast Mulders-Storjohann Reduction Agrees With the Direct Definition -The fast reducer caches shifted leading positions once per scan and cancels -leading terms through the fused `rowSubScaledShift` update. This file proves it -extensionally equal to `muldersStorjohannReduce`, so every correctness result -transfers, and packages it as a certified `ShiftedRowReducerContext`. +The fast reducer caches shifted leading positions once per conflict scan and +cancels leading terms through the fused `rowSubScaledShift` update. This file +proves it extensionally equal to `muldersStorjohannReduce`, so every +correctness result transfers, and packages it as a certified +`ShiftedRowReducerContext`. -/ namespace CompPoly @@ -153,7 +154,7 @@ theorem muldersStorjohannReduceWithFuelFast_eq : rw [cachedLeadingConflict?_eq] cases shiftedLeadingConflict? M shift with | none => rfl - | some _ => + | some pair => simp only [muldersStorjohannStepFast_eq, ih] theorem muldersStorjohannReduceFast_eq (M : PolynomialMatrix F) diff --git a/tests/CompPolyTests/Bivariate/GuruswamiSudan/Interpolation/LeeOSullivan.lean b/tests/CompPolyTests/Bivariate/GuruswamiSudan/Interpolation/LeeOSullivan.lean index 004547a4..98433d89 100644 --- a/tests/CompPolyTests/Bivariate/GuruswamiSudan/Interpolation/LeeOSullivan.lean +++ b/tests/CompPolyTests/Bivariate/GuruswamiSudan/Interpolation/LeeOSullivan.lean @@ -5,6 +5,8 @@ Authors: Valerii Huhnin -/ import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Correctness +import CompPoly.Bivariate.GuruswamiSudan.Core +import CompPoly.Bivariate.GuruswamiSudan.Root.RothRuckenstein.Correctness import CompPoly.LinearAlgebra.PolynomialMatrix.MuldersStorjohannCorrectness.Fast import CompPoly.Univariate.LagrangeArray import Mathlib.Algebra.Field.ZMod @@ -72,6 +74,20 @@ private def fastReducer : PolynomialMatrix.ShiftedRowReducerContext F3 := private def leeContext : GSInterpContext F3 := leeOSullivanInterpContext directV hornerE reducer +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 rootContext : GSRootContext F3 := + rothRuckensteinRootContext F3 fieldRoots + private def R : CPolynomial F3 := CPolynomial.CLagrange.interpolateArray points @@ -152,6 +168,9 @@ private def weakPopovMatrix : PolynomialMatrix F3 := #guard leeOSullivanPositiveInterpolate directV hornerE fastReducer duplicateXPoints params == none +#guard (gsCore points leeContext rootContext params).size <= 3 +#guard (gsCore lowPoints leeContext rootContext lowParams).size <= 3 + end GuruswamiSudan.Interpolation.LeeOSullivan end CompPolyTests From a82f65328ddc9c0aacc26243be4e3a5ad1f90bbd Mon Sep 17 00:00:00 2001 From: Valerii Huhnin Date: Wed, 10 Jun 2026 22:50:18 +0000 Subject: [PATCH 03/10] Approximant interpolation stall fix --- .../Approximant/ModularEquation.lean | 108 +++- .../Bivariate/GuruswamiSudan.lean | 30 +- .../Bivariate/GuruswamiSudan/Core.lean | 60 +- .../GuruswamiSudan/ReceivedWord.lean | 581 +++++++++++------- 4 files changed, 489 insertions(+), 290 deletions(-) diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation.lean b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation.lean index d5147ec1..b974dedb 100644 --- a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation.lean +++ b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation.lean @@ -21,6 +21,19 @@ within a constant factor of the true pivot-degree mass, which is at most the modulus degree mass `sigma`. Every round is therefore an X-adic problem with `O(m)` chunk rows and total order `O(sigma)`, and the number of rounds is logarithmic, preserving the `~O(m^(omega-1) * sigma)` solver target. + +The chunked X-adic problems are relaxations: their orders certify exactness +only for rows whose chunk coefficients stay below the chunk size, and the +relaxed kernel module also contains uncertified rows. When such rows have +lower shifted degrees than every exact solution, the minimal basis consists +entirely of uncertified rows, no round discovers a pivot, and escalation can +never make progress. The solver detects this regime (one round with no +filtered row and an empty profile) and switches to the certified full-window +fallback `fullWindowSolutionBasisViaPMBasis`: an unchunked exact-nullspace +problem on `m + s` rows whose orders cover the whole pivot window, so no +uncertified row can sit below an in-window solution. Its order mass is larger +by a factor of the module width, but on the original `m + s` rows this stays +far below an escalated chunked round. -/ namespace CompPoly @@ -150,6 +163,10 @@ def PivotDegreeProfile.insert (profile : PivotDegreeProfile) def PivotDegreeProfile.coversAll (profile : PivotDegreeProfile) : Bool := profile.degrees.all fun degree ↦ degree.isSome +/-- Whether the profile has discovered a pivot degree for any coordinate. -/ +def PivotDegreeProfile.discoveredAny (profile : PivotDegreeProfile) : Bool := + profile.degrees.any fun degree ↦ degree.isSome + /-- Merge the principal pivot degrees observed in `rows` into a profile. -/ def pivotDegreeProfileMergeRows (profile : PivotDegreeProfile) (solutionWidth : Nat) (rows : PolynomialMatrix F) (shift : Array Nat) : @@ -247,6 +264,55 @@ degree is within this window. -/ def pivotWindowCap [Zero F] (equation : ModularEquation F) : Nat := modulusDegreeMass equation.moduli +/-- Coefficient-degree bound for the full-window fallback problem. Any row of +the lifted module whose shifted degree does not exceed the shifted degree of an +in-window solution has plain coefficient degrees at most +`pivotWindowCap + maxShiftDegree shift`: comparing two principal coordinates +costs at most the shift spread, and `pivotWindowCap` bounds every minimal +pivot degree. -/ +def fullWindowDegreeBound [Zero F] (equation : ModularEquation F) + (shift : Array Nat) : Nat := + pivotWindowCap equation + maxShiftDegree shift + +/-- Exact-nullspace lift with the relation entries reduced columnwise by the +moduli, so every principal entry of column `b` has degree below `deg M_b` and +the quotient coefficients of exact solutions stay below the solution degree. -/ +def reducedExactNullspaceLift (modCtx : CPolynomial.ModContext F) + (equation : ModularEquation F) : PolynomialMatrix F := + ofFn equation.solutionWidth equation.modularWidth + (fun i j ↦ + modByMonicWith modCtx (rowGet (equation.matrix.getD i #[]) j) + (equation.moduli.getD j 0)) ++ + negativeDiagonalRows equation.moduli + +/-- Unchunked exact-nullspace problem with orders certifying exactness across +the whole pivot window: a lifted row with principal coefficient degrees at most +`bound` and quotient coefficient degrees at most `bound + 1` produces column-`b` +products of degree below `deg M_b + bound + 2`, so vanishing to that X-adic +order forces the product to vanish exactly. Unlike the chunked relaxation, +this problem admits no uncertified rows below the in-window solution degrees, +at the cost of an order mass larger by a factor of the module width. -/ +def fullWindowExactNullspaceProblem (modCtx : CPolynomial.ModContext F) + (equation : ModularEquation F) (bound : Nat) : XAdicProblem F := + { orders := equation.moduli.map fun modulus ↦ modulus.natDegree + bound + 2 + matrix := reducedExactNullspaceLift modCtx equation } + +/-- Certified full-window solve: compute a minimal basis of the unchunked +exact-nullspace problem whose orders cover the entire pivot window, and keep +the principal columns. Every returned row that pivots at or below an in-window +solution degree is an exact modular solution, so this entry point cannot lose +the solution basis to uncertified low-degree rows. It is used as the fallback +when the chunked adaptive solver discovers nothing. -/ +def fullWindowSolutionBasisViaPMBasis (modCtx : CPolynomial.ModContext F) + (pmCtx : PMBasisContext F) (equation : ModularEquation F) + (shift : Array Nat) : PolynomialMatrix F := + let bound := fullWindowDegreeBound equation shift + let problem := fullWindowExactNullspaceProblem modCtx equation bound + let expandedShift := exactNullspaceShift shift equation.modularWidth bound + compactNonzeroRows + (principalSolutionRows equation.solutionWidth + (pmCtx.basis problem expandedShift)) + /-- Pivot-degree assignment for one adaptive round: discovered coordinates use their observed pivot degrees, undiscovered coordinates use the current escalation window above their shift entry. -/ @@ -339,7 +405,14 @@ def adaptiveSolutionLoop | fuel + 1, state => let next := adaptiveSolutionRound mulCtx modCtx pmCtx equation shift state let bestDegree? := leastSolutionRowDegree? next.filtered shift - if allCoordinatesSettled next.profile next.budgets cap bestDegree? shift + if next.filtered.size == 0 && !next.profile.discoveredAny then + -- Zero discovery means the chunked relaxation is dominated by + -- uncertified rows below the solution degrees; growing the windows + -- multiplies the round cost without producing new pivot information, + -- so stop here and let the caller run the certified full-window + -- fallback instead. + next + else if allCoordinatesSettled next.profile next.budgets cap bestDegree? shift equation.solutionWidth then next else @@ -378,18 +451,25 @@ def discoverPivotDegreeProfileViaPMBasis /-- One residual reconstruction pass. If compressed rows `B` are not themselves exact modular solutions, solve for polynomial combinations `C` such that `C * (B * F mod M) = 0 mod M`, then return `C * B`. The residual equation is -solved with the same adaptive solver, without a further repair recursion. -/ +solved with the same adaptive solver, without a further repair recursion. + +The candidate rows are first reduced to one representative per shifted leading +position. The reduction steps are unimodular, so the generated row module is +unchanged, while the residual equation's solution width stays bounded by the +principal width instead of the raw candidate count; without this bound the +repair solve can be quadratically wider than the original equation. -/ def repairSolutionRowsViaPMBasis (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) (pmCtx : PMBasisContext F) (equation : ModularEquation F) (shift : Array Nat) (rows : PolynomialMatrix F) : PolynomialMatrix F := + let reduced := compactNonzeroRows (reduceKernelLeafRowsByPivots rows shift) let residualEquation : ModularEquation F := { moduli := equation.moduli - matrix := modularResidualRows mulCtx modCtx equation rows } + matrix := modularResidualRows mulCtx modCtx equation reduced } let repairState := adaptiveSolutionBasis mulCtx modCtx pmCtx residualEquation - (candidateRowShift rows shift) + (candidateRowShift reduced shift) PolynomialMatrix.mulStrassenWith pmCtx.runtime.lowMulContext - pmCtx.runtime.leafCutoff repairState.filtered rows + pmCtx.runtime.leafCutoff repairState.filtered reduced /-- Debug helper for tiny problems that intentionally disables principal-coordinate chunking. This keeps the old unchunked behavior available @@ -421,17 +501,25 @@ def knownDegreeFilteredSolutionBasisViaPMBasis /-- Solver exposed through the modular-equation context: run the adaptive degree-first window-escalation loop, whose final round is the known-degree reconstruction for every discovered coordinate, and discard any row that fails -the original diagonal congruences. The filter and repair pass are semantic -guards around the exact-nullspace bridge; they do not call any alternate -interpolation backend. -/ +the original diagonal congruences. When the chunked loop produces no exact +row — the regime where uncertified low-degree rows of the relaxed X-adic +module crowd out every solution — fall back to the certified full-window +unchunked solve, and only then to the residual repair pass. The filter, +fallback, and repair are semantic guards around the exact-nullspace bridge; +they do not call any alternate interpolation backend. -/ def filteredSolutionBasisViaPMBasis (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) (pmCtx : PMBasisContext F) (equation : ModularEquation F) (shift : Array Nat) : PolynomialMatrix F := let final := adaptiveSolutionBasis mulCtx modCtx pmCtx equation shift if final.filtered.size == 0 then - filterModularSolutionRows mulCtx modCtx equation - (repairSolutionRowsViaPMBasis mulCtx modCtx pmCtx equation shift final.raw) + let fallback := filterModularSolutionRows mulCtx modCtx equation + (fullWindowSolutionBasisViaPMBasis modCtx pmCtx equation shift) + if fallback.size == 0 then + filterModularSolutionRows mulCtx modCtx equation + (repairSolutionRowsViaPMBasis mulCtx modCtx pmCtx equation shift final.raw) + else + fallback else final.filtered diff --git a/bench/CompPolyBench/Bivariate/GuruswamiSudan.lean b/bench/CompPolyBench/Bivariate/GuruswamiSudan.lean index 9751f238..7b975cbc 100644 --- a/bench/CompPolyBench/Bivariate/GuruswamiSudan.lean +++ b/bench/CompPolyBench/Bivariate/GuruswamiSudan.lean @@ -91,16 +91,16 @@ private def runGsInterpolationSmallKoala (preset : BenchPreset) (gen : StdGen) : let warmup := gsWarmupIterations preset let denseMeasured := preset.selectNat 1 1 1 let koetterMeasured := preset.selectNat 10 2 1 - let leeDirectMeasured := preset.selectNat 60 8 2 - let leeSubproductMeasured := preset.selectNat 50 7 1 - let approximantDirectMeasured := preset.selectNat 30 5 1 - let approximantSubproductMeasured := preset.selectNat 1 1 1 + let leeDirectMeasured := preset.selectNat 100 15 3 + let leeSubproductMeasured := preset.selectNat 90 13 3 + let approximantDirectMeasured := preset.selectNat 7 1 1 + let approximantSubproductMeasured := preset.selectNat 7 1 1 let fastDenseMeasured := preset.selectNat 2 1 1 let fastKoetterMeasured := preset.selectNat 70 10 2 - let fastLeeDirectMeasured := preset.selectNat 400 60 10 - let fastLeeSubproductMeasured := preset.selectNat 300 40 10 - let fastApproximantDirectMeasured := preset.selectNat 200 30 5 - let fastApproximantSubproductMeasured := preset.selectNat 1 1 1 + let fastLeeDirectMeasured := preset.selectNat 600 90 20 + let fastLeeSubproductMeasured := preset.selectNat 400 60 10 + let fastApproximantDirectMeasured := preset.selectNat 20 3 1 + let fastApproximantSubproductMeasured := preset.selectNat 20 3 1 let checksumIterations := groupChecksumIterations denseMeasured [ koetterMeasured, leeDirectMeasured, leeSubproductMeasured, fastDenseMeasured, approximantDirectMeasured, approximantSubproductMeasured, fastKoetterMeasured, @@ -219,13 +219,13 @@ private def runGsInterpolationLargeKoala (preset : BenchPreset) (gen : StdGen) : let fastPoints := gsLargeBenchmarkPoints fastMessage let warmup := gsWarmupIterations preset let koetterMeasured := preset.selectNat 1 1 1 - let leeMeasured := preset.selectNat 1 1 1 - let approximantDirectMeasured := preset.selectNat 1 1 1 - let approximantSubproductMeasured := preset.selectNat 1 1 1 - let fastKoetterMeasured := preset.selectNat 1 1 1 - let fastLeeMeasured := preset.selectNat 1 1 1 - let fastApproximantDirectMeasured := preset.selectNat 1 1 1 - let fastApproximantSubproductMeasured := preset.selectNat 1 1 1 + let leeMeasured := preset.selectNat 10 1 1 + let approximantDirectMeasured := preset.selectNat 2 1 1 + let approximantSubproductMeasured := preset.selectNat 2 1 1 + let fastKoetterMeasured := preset.selectNat 4 1 1 + let fastLeeMeasured := preset.selectNat 50 7 1 + let fastApproximantDirectMeasured := preset.selectNat 7 1 1 + let fastApproximantSubproductMeasured := preset.selectNat 7 1 1 let checksumIterations := groupChecksumIterations koetterMeasured [ leeMeasured, leeMeasured, approximantDirectMeasured, approximantSubproductMeasured, fastKoetterMeasured, fastLeeMeasured, fastLeeMeasured, diff --git a/bench/CompPolyBench/Bivariate/GuruswamiSudan/Core.lean b/bench/CompPolyBench/Bivariate/GuruswamiSudan/Core.lean index 5ffaed5c..a8192043 100644 --- a/bench/CompPolyBench/Bivariate/GuruswamiSudan/Core.lean +++ b/bench/CompPolyBench/Bivariate/GuruswamiSudan/Core.lean @@ -31,16 +31,16 @@ def runGsCoreSmallKoala (preset : BenchPreset) (gen : StdGen) : let warmup := gsWarmupIterations preset let denseMeasured := preset.selectNat 1 1 1 let koetterMeasured := preset.selectNat 10 2 1 - let leeDirectMeasured := preset.selectNat 60 8 2 - let leeSubproductMeasured := preset.selectNat 50 7 1 - let approximantDirectMeasured := preset.selectNat 30 5 1 - let approximantSubproductMeasured := preset.selectNat 1 1 1 + let leeDirectMeasured := preset.selectNat 100 15 3 + let leeSubproductMeasured := preset.selectNat 90 13 3 + let approximantDirectMeasured := preset.selectNat 7 1 1 + let approximantSubproductMeasured := preset.selectNat 7 1 1 let fastDenseMeasured := preset.selectNat 2 1 1 let fastKoetterMeasured := preset.selectNat 70 10 2 - let fastLeeDirectMeasured := preset.selectNat 400 60 10 - let fastLeeSubproductMeasured := preset.selectNat 300 40 10 - let fastApproximantDirectMeasured := preset.selectNat 200 30 5 - let fastApproximantSubproductMeasured := preset.selectNat 1 1 1 + let fastLeeDirectMeasured := preset.selectNat 600 90 20 + let fastLeeSubproductMeasured := preset.selectNat 400 60 10 + let fastApproximantDirectMeasured := preset.selectNat 20 3 1 + let fastApproximantSubproductMeasured := preset.selectNat 20 3 1 let alekDenseMeasured := denseMeasured let alekKoetterMeasured := koetterMeasured let alekLeeDirectMeasured := leeDirectMeasured @@ -262,13 +262,13 @@ def runGsCoreLargeKoala (preset : BenchPreset) (gen : StdGen) : let fastAlekRootContext := fastKoalaBearAlekhnovichRootContext let warmup := gsWarmupIterations preset let koetterMeasured := preset.selectNat 1 1 1 - let leeMeasured := preset.selectNat 1 1 1 - let approximantDirectMeasured := preset.selectNat 1 1 1 - let approximantSubproductMeasured := preset.selectNat 1 1 1 - let fastKoetterMeasured := preset.selectNat 1 1 1 - let fastLeeMeasured := preset.selectNat 1 1 1 - let fastApproximantDirectMeasured := preset.selectNat 1 1 1 - let fastApproximantSubproductMeasured := preset.selectNat 1 1 1 + let leeMeasured := preset.selectNat 10 1 1 + let approximantDirectMeasured := preset.selectNat 2 1 1 + let approximantSubproductMeasured := preset.selectNat 2 1 1 + let fastKoetterMeasured := preset.selectNat 4 1 1 + let fastLeeMeasured := preset.selectNat 50 7 1 + let fastApproximantDirectMeasured := preset.selectNat 7 1 1 + let fastApproximantSubproductMeasured := preset.selectNat 7 1 1 let alekKoetterMeasured := koetterMeasured let alekLeeMeasured := leeMeasured let alekApproximantDirectMeasured := approximantDirectMeasured @@ -466,16 +466,16 @@ def runGsFilteredCoreSmallKoala (preset : BenchPreset) (gen : StdGen) : let warmup := gsWarmupIterations preset let denseMeasured := preset.selectNat 1 1 1 let koetterMeasured := preset.selectNat 10 2 1 - let leeDirectMeasured := preset.selectNat 60 8 2 - let leeSubproductMeasured := preset.selectNat 50 7 1 - let approximantDirectMeasured := preset.selectNat 30 5 1 - let approximantSubproductMeasured := preset.selectNat 1 1 1 + let leeDirectMeasured := preset.selectNat 100 15 3 + let leeSubproductMeasured := preset.selectNat 90 13 3 + let approximantDirectMeasured := preset.selectNat 7 1 1 + let approximantSubproductMeasured := preset.selectNat 7 1 1 let fastDenseMeasured := preset.selectNat 2 1 1 let fastKoetterMeasured := preset.selectNat 70 10 2 - let fastLeeDirectMeasured := preset.selectNat 400 60 10 - let fastLeeSubproductMeasured := preset.selectNat 300 40 10 - let fastApproximantDirectMeasured := preset.selectNat 200 30 5 - let fastApproximantSubproductMeasured := preset.selectNat 1 1 1 + let fastLeeDirectMeasured := preset.selectNat 600 90 20 + let fastLeeSubproductMeasured := preset.selectNat 400 60 10 + let fastApproximantDirectMeasured := preset.selectNat 20 3 1 + let fastApproximantSubproductMeasured := preset.selectNat 20 3 1 let alekDenseMeasured := denseMeasured let alekKoetterMeasured := koetterMeasured let alekLeeDirectMeasured := leeDirectMeasured @@ -715,13 +715,13 @@ def runGsFilteredCoreLargeKoala (preset : BenchPreset) (gen : StdGen) : let fastAlekRootContext := fastKoalaBearAlekhnovichRootContext let warmup := gsWarmupIterations preset let koetterMeasured := preset.selectNat 1 1 1 - let leeMeasured := preset.selectNat 1 1 1 - let approximantDirectMeasured := preset.selectNat 1 1 1 - let approximantSubproductMeasured := preset.selectNat 1 1 1 - let fastKoetterMeasured := preset.selectNat 1 1 1 - let fastLeeMeasured := preset.selectNat 1 1 1 - let fastApproximantDirectMeasured := preset.selectNat 1 1 1 - let fastApproximantSubproductMeasured := preset.selectNat 1 1 1 + let leeMeasured := preset.selectNat 10 1 1 + let approximantDirectMeasured := preset.selectNat 2 1 1 + let approximantSubproductMeasured := preset.selectNat 2 1 1 + let fastKoetterMeasured := preset.selectNat 4 1 1 + let fastLeeMeasured := preset.selectNat 50 7 1 + let fastApproximantDirectMeasured := preset.selectNat 7 1 1 + let fastApproximantSubproductMeasured := preset.selectNat 7 1 1 let alekKoetterMeasured := koetterMeasured let alekLeeMeasured := leeMeasured let alekApproximantDirectMeasured := approximantDirectMeasured diff --git a/bench/CompPolyBench/Bivariate/GuruswamiSudan/ReceivedWord.lean b/bench/CompPolyBench/Bivariate/GuruswamiSudan/ReceivedWord.lean index 40a88c5a..1076aaf9 100644 --- a/bench/CompPolyBench/Bivariate/GuruswamiSudan/ReceivedWord.lean +++ b/bench/CompPolyBench/Bivariate/GuruswamiSudan/ReceivedWord.lean @@ -4,16 +4,19 @@ Released under Apache 2.0 license as described in the file LICENSE. Authors: Valerii Huhnin -/ -import CompPolyBench.Common -import CompPoly.Bivariate.GuruswamiSudan -import CompPoly.Bivariate.GuruswamiSudan.Implementations +import CompPolyBench.Bivariate.GuruswamiSudan.Shared /-! -# Guruswami-Sudan Received-Word Benchmarks +# Guruswami-Sudan Perturbed Received-Word Benchmarks -KoalaBear benchmarks for Guruswami-Sudan interpolation on perturbed received -words, where different interpolation backends may return different valid -witnesses. +Perturbed (non-codeword) counterparts of the codeword interpolation, core, and +filtered-core benchmark groups, over the same small and large input shapes and +the same backend rows. Perturbations stay within the Guruswami-Sudan decoding +radius, so the full pipeline still recovers the original message. + +Different interpolation backends may return different valid witnesses on the +same perturbed word, so interpolation rows checksum the witness validity class +rather than coefficients; core rows checksum the decoded candidate lists. -/ open CompPoly @@ -21,48 +24,36 @@ open CompPoly.GuruswamiSudan namespace CompPolyBench -private def gsNonCodewordPointCount : Nat := 4 - -private def gsNonCodewordMessageDegree : Nat := 2 - -private def gsNonCodewordWeightedDegreeBound : Nat := - 3 +/-! ### Perturbation shapes -private def gsNonCodewordMultiplicity : Nat := 1 - -private def gsNonCodewordParams : GSInterpParams := - { messageDegree := gsNonCodewordMessageDegree - multiplicity := gsNonCodewordMultiplicity - weightedDegreeBound := gsNonCodewordWeightedDegreeBound } +The small shape perturbs every 3rd symbol (22 errors at `n=64`, inside the +`m=2, D=75` decoding radius of 26), the large shape every 7th symbol +(28 errors at `n=192`, inside the radius of 71). Filtered groups use the +exact error count as the distance radius. +-/ -private def gsStressPointCount : Nat := 128 +private def gsNonCodewordSmallPeriod : Nat := 3 -private def gsStressMessageDegree : Nat := 33 +private def gsNonCodewordLargePeriod : Nat := 7 -private def gsStressWeightedDegreeBound : Nat := 160 +private def gsNonCodewordSmallErrors : Nat := + (gsSmallPointCount + gsNonCodewordSmallPeriod - 1) / gsNonCodewordSmallPeriod -private def gsStressMultiplicity : Nat := 2 +private def gsNonCodewordLargeErrors : Nat := + (gsLargeInterpPointCount + gsNonCodewordLargePeriod - 1) / + gsNonCodewordLargePeriod -private def gsStressParams : GSInterpParams := - { messageDegree := gsStressMessageDegree - multiplicity := gsStressMultiplicity - weightedDegreeBound := gsStressWeightedDegreeBound } +private def gsNonCodewordSmallInputShape : String := + gsSmallInterpInputShape ++ s!",errors=every{gsNonCodewordSmallPeriod}" -private def gsNonCodewordInputShape : String := - s!"n={gsNonCodewordPointCount},k={gsNonCodewordMessageDegree}," ++ - s!"m={gsNonCodewordMultiplicity},D={gsNonCodewordWeightedDegreeBound}," ++ - "errors=every2" +private def gsNonCodewordLargeInputShape : String := + gsLargeInterpInputShape ++ s!",errors=every{gsNonCodewordLargePeriod}" -private def gsStressInputShape : String := - s!"n={gsStressPointCount},k={gsStressMessageDegree}," ++ - s!"m={gsStressMultiplicity},D={gsStressWeightedDegreeBound},errors=every7" +private def gsNonCodewordSmallFilteredShape : String := + gsNonCodewordSmallInputShape ++ s!",r={gsNonCodewordSmallErrors}" -private def codewordPointsWithCount {F : Type*} [Semiring F] [BEq F] [LawfulBEq F] - (pointCount : Nat) (p : CPolynomial F) : Array (Prod F F) := - (List.range pointCount).map - (fun i ↦ - let x : F := (i + 1 : Nat) - (x, CPolynomial.eval x p)) |>.toArray +private def gsNonCodewordLargeFilteredShape : String := + gsNonCodewordLargeInputShape ++ s!",r={gsNonCodewordLargeErrors}" private def perturbEveryNthY {F : Type*} [Add F] [OfNat F 1] (period : Nat) (points : Array (Prod F F)) : Array (Prod F F) := @@ -77,209 +68,317 @@ private def perturbEveryNthY {F : Type*} [Add F] [OfNat F 1] else point -private def checksumInterpolationValidityOption {F : Type*} - [CommSemiring F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] - (points : Array (F × F)) (params : GSInterpParams) - (Q? : Option (CBivariate F)) : Nat := - match Q? with - | none => 0 - | some Q => if interpolationWitnessIsValidBool points params Q then 2 else 1 +/-! ### Group runner machinery + +Iteration counts follow the bench conventions: within a group, each row's +count is sized so the rows spend approximately the same total wall-clock time; +presets only scale counts, with `medium ≈ large / 7` and +`small ≈ max 1 (large / 35)`, rounded to convenient values. +-/ + +/-- One benchmark row with the checksum precomposed onto the closure. -/ +private structure PerturbedRowSpec where + key : String + method : String + field : String + measured : Nat + run : Nat → Nat + +/-- Interpolation backend descriptor: row-key part, method label, context, and +large/medium/small measured-iteration counts. -/ +private structure PerturbedBackend (F : Type*) [Field F] [BEq F] [LawfulBEq F] + [DecidableEq F] where + keyPart : String + method : String + ctx : GSInterpContext F + large : Nat + medium : Nat + small : Nat + +private def PerturbedBackend.measured {F : Type*} [Field F] [BEq F] + [LawfulBEq F] [DecidableEq F] (backend : PerturbedBackend F) + (preset : BenchPreset) : Nat := + preset.selectNat backend.large backend.medium backend.small + +/-- Run all rows of one perturbed-word group with a shared checksum prefix. -/ +private def runPerturbedGroup (info : BenchGroupInfo) (inputShape : String) + (preset : BenchPreset) (rows : Array PerturbedRowSpec) : IO BenchGroup := do + let warmup := gsWarmupIterations preset + let counts := (rows.map fun row ↦ row.measured).toList + let checksumIterations := groupChecksumIterations (counts.headD 1) counts + let mut records : Array BenchRecord := #[] + for row in rows do + let record ← runTimed row.key "CBivariate" row.method row.field inputShape + preset warmup row.measured row.run id checksumIterations + records := records.push record + pure { groupKey := info.groupKey, title := info.title, records := records } + +/-- Interpolation rows for one field implementation. -/ +private def perturbedInterpRows {F : Type*} [Field F] [BEq F] [LawfulBEq F] + [DecidableEq F] + (fieldName fieldSuffix sizeKey : String) (preset : BenchPreset) + (points : Array (Prod F F)) (params : GSInterpParams) + (backends : Array (PerturbedBackend F)) : Array PerturbedRowSpec := + backends.map fun backend ↦ + { key := + s!"guruswami-sudan-interp-{backend.keyPart}-noncodeword-{sizeKey}{fieldSuffix}" + method := backend.method + field := fieldName + measured := backend.measured preset + run := fun _ ↦ + checksumInterpolationValidityOption points params + (backend.ctx.interpolate points params) } + +/-- Core or filtered-core rows for one field implementation: one RR-roots row +and one Alekhnovich-roots row per backend. `filtered` selects `gsFilteredCore`. + +On a perturbed received word, different valid interpolation witnesses can +carry different spurious `Y`-roots beyond the decoding radius, so the raw +`gsCore` candidate list is not canonical across backends. Candidates inside +the radius are roots of every valid witness, so checksums always compare the +distance-filtered sublist (a no-op for the already-filtered rows). -/ +private def perturbedCoreRows {F : Type*} [Field F] [BEq F] [LawfulBEq F] + [DecidableEq F] + (groupKeyPart fieldName fieldSuffix sizeKey methodSuffix : String) + (filtered : Bool) (radius : Nat) (preset : BenchPreset) + (points : Array (Prod F F)) (params : GSInterpParams) + (checksumCandidates : Array (CPolynomial F) → Nat) + (rothContext alekContext : GSRootContext F) + (backends : Array (PerturbedBackend F)) : Array PerturbedRowSpec := + backends.flatMap fun backend ↦ + let runFor : GSRootContext F → Nat → Nat := fun rootContext _ ↦ + let candidates := + if filtered then + gsFilteredCore points backend.ctx rootContext params radius + else + gsCore points backend.ctx rootContext params + checksumCandidates + (candidates.filter (passesCandidateDistance points radius)) + #[ + { key := + s!"guruswami-sudan-{groupKeyPart}-{backend.keyPart}-noncodeword-{sizeKey}{fieldSuffix}" + method := s!"{backend.method} + RR roots{methodSuffix}" + field := fieldName + measured := backend.measured preset + run := runFor rothContext }, + { key := + s!"guruswami-sudan-{groupKeyPart}-{backend.keyPart}" ++ + s!"-noncodeword-{sizeKey}-alekhnovich{fieldSuffix}" + method := s!"{backend.method} + Alekhnovich roots{methodSuffix}" + field := fieldName + measured := backend.measured preset + run := runFor alekContext } + ] + +/-! ### Backend tables -/ + +private def gsNonCodewordSmallSlowBackends : + Array (PerturbedBackend KoalaBear.Field) := #[ + ⟨"dense", "Dense linear", koalaBearDenseInterpContext, 1, 1, 1⟩, + ⟨"koetter", "Koetter", koalaBearKoetterInterpContext, 6, 1, 1⟩, + ⟨"lee-direct", "Lee-O'Sullivan direct", koalaBearLeeDirectInterpContext, + 15, 2, 1⟩, + ⟨"lee-subproduct", "Lee-O'Sullivan subproduct", + koalaBearLeeSubproductInterpContext, 15, 2, 1⟩, + ⟨"approximant-direct", "Approximant basis direct", + koalaBearApproximantBasisDirectInterpContext, 2, 1, 1⟩, + ⟨"approximant-subproduct", "Approximant basis subproduct", + koalaBearApproximantBasisSubproductInterpContext, 2, 1, 1⟩ +] + +private def gsNonCodewordSmallFastBackends : + Array (PerturbedBackend KoalaBear.Fast.Field) := #[ + ⟨"dense", "Dense linear", fastKoalaBearDenseInterpContext, 1, 1, 1⟩, + ⟨"koetter", "Koetter", fastKoalaBearKoetterInterpContext, 30, 4, 1⟩, + ⟨"lee-direct", "Lee-O'Sullivan direct", fastKoalaBearLeeDirectInterpContext, + 80, 11, 2⟩, + ⟨"lee-subproduct", "Lee-O'Sullivan subproduct", + fastKoalaBearLeeSubproductInterpContext, 70, 10, 2⟩, + ⟨"approximant-direct", "Approximant basis direct", + fastKoalaBearApproximantBasisDirectInterpContext, 6, 1, 1⟩, + ⟨"approximant-subproduct", "Approximant basis subproduct", + fastKoalaBearApproximantBasisSubproductInterpContext, 6, 1, 1⟩ +] -/-- Benchmark group metadata for received-word interpolation rows. -/ +private def gsNonCodewordLargeSlowBackends : + Array (PerturbedBackend KoalaBear.Field) := #[ + ⟨"koetter", "Koetter", koalaBearKoetterInterpContext, 1, 1, 1⟩, + ⟨"lee-direct", "Lee-O'Sullivan direct", koalaBearLeeDirectInterpContext, + 2, 1, 1⟩, + ⟨"lee-subproduct", "Lee-O'Sullivan subproduct", + koalaBearLeeSubproductInterpContext, 2, 1, 1⟩, + ⟨"approximant-direct", "Approximant basis direct", + koalaBearApproximantBasisDirectInterpContext, 1, 1, 1⟩, + ⟨"approximant-subproduct", "Approximant basis subproduct", + koalaBearApproximantBasisSubproductInterpContext, 1, 1, 1⟩ +] + +private def gsNonCodewordLargeFastBackends : + Array (PerturbedBackend KoalaBear.Fast.Field) := #[ + ⟨"koetter", "Koetter", fastKoalaBearKoetterInterpContext, 2, 1, 1⟩, + ⟨"lee-direct", "Lee-O'Sullivan direct", fastKoalaBearLeeDirectInterpContext, + 10, 1, 1⟩, + ⟨"lee-subproduct", "Lee-O'Sullivan subproduct", + fastKoalaBearLeeSubproductInterpContext, 10, 1, 1⟩, + ⟨"approximant-direct", "Approximant basis direct", + fastKoalaBearApproximantBasisDirectInterpContext, 1, 1, 1⟩, + ⟨"approximant-subproduct", "Approximant basis subproduct", + fastKoalaBearApproximantBasisSubproductInterpContext, 1, 1, 1⟩ +] + +/-! ### Group metadata -/ + +/-- Benchmark group metadata for perturbed received-word rows. -/ def guruswamiSudanReceivedWordGroupInfos : List BenchGroupInfo := [ ⟨"guruswami-sudan-interp-noncodeword-small-koalabear", - "Guruswami-Sudan interpolation on perturbed received word, smoke (KoalaBear)"⟩, - ⟨"guruswami-sudan-interp-stress-koalabear", - "Guruswami-Sudan interpolation on perturbed received word, stress (KoalaBear)"⟩ + "Guruswami-Sudan interpolation on perturbed received word, small (KoalaBear)"⟩, + ⟨"guruswami-sudan-interp-noncodeword-large-koalabear", + "Guruswami-Sudan interpolation on perturbed received word, large (KoalaBear)"⟩, + ⟨"guruswami-sudan-core-noncodeword-small-koalabear", + "Guruswami-Sudan full core on perturbed received word, small (KoalaBear)"⟩, + ⟨"guruswami-sudan-core-noncodeword-large-koalabear", + "Guruswami-Sudan full core on perturbed received word, large (KoalaBear)"⟩, + ⟨"guruswami-sudan-filtered-core-noncodeword-small-koalabear", + "Guruswami-Sudan filtered core on perturbed received word, small (KoalaBear)"⟩, + ⟨"guruswami-sudan-filtered-core-noncodeword-large-koalabear", + "Guruswami-Sudan filtered core on perturbed received word, large (KoalaBear)"⟩ ] -private def runGsInterpolationNonCodewordSmallKoala (preset : BenchPreset) (gen : StdGen) : - IO (Prod BenchGroup StdGen) := do - let (coeffs, gen) := (koalaBearArray gsNonCodewordMessageDegree false).run gen +/-! ### Shared per-shape inputs -/ + +private structure PerturbedInputs where + points : Array (Prod KoalaBear.Field KoalaBear.Field) + fastPoints : Array (Prod KoalaBear.Fast.Field KoalaBear.Fast.Field) + +private def perturbedSmallInputs (gen : StdGen) : PerturbedInputs × StdGen := + let (coeffs, gen) := (koalaBearArray gsSmallMessageDegree false).run gen let message := cpolyOfArray coeffs let fastMessage := cpolyOfArray (koalaBearFastArray coeffs) - let points := perturbEveryNthY 2 (codewordPointsWithCount gsNonCodewordPointCount message) - let fastPoints := perturbEveryNthY 2 - (codewordPointsWithCount gsNonCodewordPointCount fastMessage) - let warmup := 0 - let leeDirectMeasured := preset.selectNat 20 3 1 - let leeSubproductMeasured := preset.selectNat 20 3 1 - let approximantDirectMeasured := preset.selectNat 1 1 1 - let approximantSubproductMeasured := preset.selectNat 1 1 1 - let fastLeeDirectMeasured := preset.selectNat 100 15 3 - let fastLeeSubproductMeasured := preset.selectNat 80 12 2 - let fastApproximantDirectMeasured := preset.selectNat 1 1 1 - let fastApproximantSubproductMeasured := preset.selectNat 1 1 1 - let checksumIterations := groupChecksumIterations leeDirectMeasured [ - leeSubproductMeasured, approximantDirectMeasured, approximantSubproductMeasured, - fastLeeDirectMeasured, fastLeeSubproductMeasured, fastApproximantDirectMeasured, - fastApproximantSubproductMeasured - ] - let leeDirectRow <- runTimed - "guruswami-sudan-interp-lee-direct-noncodeword-small" "CBivariate" - "Lee-O'Sullivan direct" - "KoalaBear.Field" gsNonCodewordInputShape preset warmup leeDirectMeasured - (fun _ ↦ koalaBearLeeDirectInterpContext.interpolate points gsNonCodewordParams) - (checksumInterpolationValidityOption points gsNonCodewordParams) - checksumIterations - let leeSubproductRow <- runTimed - "guruswami-sudan-interp-lee-subproduct-noncodeword-small" "CBivariate" - "Lee-O'Sullivan subproduct" - "KoalaBear.Field" gsNonCodewordInputShape preset warmup leeSubproductMeasured - (fun _ ↦ koalaBearLeeSubproductInterpContext.interpolate points gsNonCodewordParams) - (checksumInterpolationValidityOption points gsNonCodewordParams) - checksumIterations - let approximantDirectRow <- runTimed - "guruswami-sudan-interp-approximant-direct-noncodeword-small" "CBivariate" - "Approximant basis direct" - "KoalaBear.Field" gsNonCodewordInputShape preset warmup approximantDirectMeasured - (fun _ ↦ koalaBearApproximantBasisDirectInterpContext.interpolate points - gsNonCodewordParams) - (checksumInterpolationValidityOption points gsNonCodewordParams) - checksumIterations - let approximantSubproductRow <- runTimed - "guruswami-sudan-interp-approximant-subproduct-noncodeword-small" "CBivariate" - "Approximant basis subproduct" - "KoalaBear.Field" gsNonCodewordInputShape preset warmup - approximantSubproductMeasured - (fun _ ↦ koalaBearApproximantBasisSubproductInterpContext.interpolate points - gsNonCodewordParams) - (checksumInterpolationValidityOption points gsNonCodewordParams) - checksumIterations - let fastLeeDirectRow <- runTimed - "guruswami-sudan-interp-lee-direct-noncodeword-small-fast" "CBivariate" - "Lee-O'Sullivan direct" - "KoalaBear.Fast.Field" gsNonCodewordInputShape preset warmup - fastLeeDirectMeasured - (fun _ ↦ fastKoalaBearLeeDirectInterpContext.interpolate fastPoints - gsNonCodewordParams) - (checksumInterpolationValidityOption fastPoints gsNonCodewordParams) - checksumIterations - let fastLeeSubproductRow <- runTimed - "guruswami-sudan-interp-lee-subproduct-noncodeword-small-fast" "CBivariate" - "Lee-O'Sullivan subproduct" - "KoalaBear.Fast.Field" gsNonCodewordInputShape preset warmup - fastLeeSubproductMeasured - (fun _ ↦ fastKoalaBearLeeSubproductInterpContext.interpolate fastPoints - gsNonCodewordParams) - (checksumInterpolationValidityOption fastPoints gsNonCodewordParams) - checksumIterations - let fastApproximantDirectRow <- runTimed - "guruswami-sudan-interp-approximant-direct-noncodeword-small-fast" "CBivariate" - "Approximant basis direct" - "KoalaBear.Fast.Field" gsNonCodewordInputShape preset warmup - fastApproximantDirectMeasured - (fun _ ↦ fastKoalaBearApproximantBasisDirectInterpContext.interpolate fastPoints - gsNonCodewordParams) - (checksumInterpolationValidityOption fastPoints gsNonCodewordParams) - checksumIterations - let fastApproximantSubproductRow <- runTimed - "guruswami-sudan-interp-approximant-subproduct-noncodeword-small-fast" "CBivariate" - "Approximant basis subproduct" - "KoalaBear.Fast.Field" gsNonCodewordInputShape preset warmup - fastApproximantSubproductMeasured - (fun _ ↦ fastKoalaBearApproximantBasisSubproductInterpContext.interpolate - fastPoints gsNonCodewordParams) - (checksumInterpolationValidityOption fastPoints gsNonCodewordParams) - checksumIterations - pure ({ - groupKey := "guruswami-sudan-interp-noncodeword-small-koalabear", - title := "Guruswami-Sudan interpolation on perturbed received word, smoke (KoalaBear)", - records := #[ - leeDirectRow, leeSubproductRow, approximantDirectRow, approximantSubproductRow, - fastLeeDirectRow, fastLeeSubproductRow, fastApproximantDirectRow, - fastApproximantSubproductRow - ] - }, gen) + let points := perturbEveryNthY gsNonCodewordSmallPeriod + (codewordPointsWithCount gsSmallPointCount message) + let fastPoints := perturbEveryNthY gsNonCodewordSmallPeriod + (codewordPointsWithCount gsSmallPointCount fastMessage) + ({ points := points, fastPoints := fastPoints }, gen) -private def runGsInterpolationStressKoala (preset : BenchPreset) (gen : StdGen) : - IO (Prod BenchGroup StdGen) := do - let (coeffs, gen) := (koalaBearArray gsStressMessageDegree false).run gen +private def perturbedLargeInputs (gen : StdGen) : PerturbedInputs × StdGen := + let (coeffs, gen) := (koalaBearArray gsLargeInterpMessageDegree false).run gen let message := cpolyOfArray coeffs let fastMessage := cpolyOfArray (koalaBearFastArray coeffs) - let points := perturbEveryNthY 7 (codewordPointsWithCount gsStressPointCount message) - let fastPoints := perturbEveryNthY 7 - (codewordPointsWithCount gsStressPointCount fastMessage) - let warmup := 0 - let leeMeasured := preset.selectNat 3 1 1 - let approximantMeasured := preset.selectNat 1 1 1 - let fastLeeMeasured := preset.selectNat 10 2 1 - let fastApproximantMeasured := preset.selectNat 1 1 1 - let checksumIterations := groupChecksumIterations leeMeasured [ - leeMeasured, approximantMeasured, approximantMeasured, fastLeeMeasured, - fastLeeMeasured, fastApproximantMeasured, fastApproximantMeasured - ] - let leeDirectRow <- runTimed - "guruswami-sudan-interp-lee-direct-stress" "CBivariate" - "Lee-O'Sullivan direct" - "KoalaBear.Field" gsStressInputShape preset warmup leeMeasured - (fun _ ↦ koalaBearLeeDirectInterpContext.interpolate points gsStressParams) - (checksumInterpolationValidityOption points gsStressParams) - checksumIterations - let leeSubproductRow <- runTimed - "guruswami-sudan-interp-lee-subproduct-stress" "CBivariate" - "Lee-O'Sullivan subproduct" - "KoalaBear.Field" gsStressInputShape preset warmup leeMeasured - (fun _ ↦ koalaBearLeeSubproductInterpContext.interpolate points gsStressParams) - (checksumInterpolationValidityOption points gsStressParams) - checksumIterations - let approximantDirectRow <- runTimed - "guruswami-sudan-interp-approximant-direct-stress" "CBivariate" - "Approximant basis direct" - "KoalaBear.Field" gsStressInputShape preset warmup approximantMeasured - (fun _ ↦ koalaBearApproximantBasisDirectInterpContext.interpolate points - gsStressParams) - (checksumInterpolationValidityOption points gsStressParams) - checksumIterations - let approximantSubproductRow <- runTimed - "guruswami-sudan-interp-approximant-subproduct-stress" "CBivariate" - "Approximant basis subproduct" - "KoalaBear.Field" gsStressInputShape preset warmup approximantMeasured - (fun _ ↦ koalaBearApproximantBasisSubproductInterpContext.interpolate points - gsStressParams) - (checksumInterpolationValidityOption points gsStressParams) - checksumIterations - let fastLeeDirectRow <- runTimed - "guruswami-sudan-interp-lee-direct-stress-fast" "CBivariate" - "Lee-O'Sullivan direct" - "KoalaBear.Fast.Field" gsStressInputShape preset warmup fastLeeMeasured - (fun _ ↦ fastKoalaBearLeeDirectInterpContext.interpolate fastPoints - gsStressParams) - (checksumInterpolationValidityOption fastPoints gsStressParams) - checksumIterations - let fastLeeSubproductRow <- runTimed - "guruswami-sudan-interp-lee-subproduct-stress-fast" "CBivariate" - "Lee-O'Sullivan subproduct" - "KoalaBear.Fast.Field" gsStressInputShape preset warmup fastLeeMeasured - (fun _ ↦ fastKoalaBearLeeSubproductInterpContext.interpolate fastPoints - gsStressParams) - (checksumInterpolationValidityOption fastPoints gsStressParams) - checksumIterations - let fastApproximantDirectRow <- runTimed - "guruswami-sudan-interp-approximant-direct-stress-fast" "CBivariate" - "Approximant basis direct" - "KoalaBear.Fast.Field" gsStressInputShape preset warmup fastApproximantMeasured - (fun _ ↦ fastKoalaBearApproximantBasisDirectInterpContext.interpolate fastPoints - gsStressParams) - (checksumInterpolationValidityOption fastPoints gsStressParams) - checksumIterations - let fastApproximantSubproductRow <- runTimed - "guruswami-sudan-interp-approximant-subproduct-stress-fast" "CBivariate" - "Approximant basis subproduct" - "KoalaBear.Fast.Field" gsStressInputShape preset warmup fastApproximantMeasured - (fun _ ↦ fastKoalaBearApproximantBasisSubproductInterpContext.interpolate - fastPoints gsStressParams) - (checksumInterpolationValidityOption fastPoints gsStressParams) - checksumIterations - pure ({ - groupKey := "guruswami-sudan-interp-stress-koalabear", - title := "Guruswami-Sudan interpolation on perturbed received word, stress (KoalaBear)", - records := #[ - leeDirectRow, leeSubproductRow, approximantDirectRow, approximantSubproductRow, - fastLeeDirectRow, fastLeeSubproductRow, fastApproximantDirectRow, - fastApproximantSubproductRow - ] - }, gen) + let points := perturbEveryNthY gsNonCodewordLargePeriod + (codewordPointsWithCount gsLargeInterpPointCount message) + let fastPoints := perturbEveryNthY gsNonCodewordLargePeriod + (codewordPointsWithCount gsLargeInterpPointCount fastMessage) + ({ points := points, fastPoints := fastPoints }, gen) + +/-! ### Group runners -/ + +private def runGsInterpolationNonCodewordSmallKoala (preset : BenchPreset) + (gen : StdGen) : IO (Prod BenchGroup StdGen) := do + let (inputs, gen) := perturbedSmallInputs gen + let rows := + perturbedInterpRows "KoalaBear.Field" "" "small" preset + inputs.points gsSmallParams gsNonCodewordSmallSlowBackends ++ + perturbedInterpRows "KoalaBear.Fast.Field" "-fast" "small" preset + inputs.fastPoints gsSmallParams gsNonCodewordSmallFastBackends + let group ← runPerturbedGroup + (guruswamiSudanReceivedWordGroupInfos.getD 0 + ⟨"guruswami-sudan-interp-noncodeword-small-koalabear", ""⟩) + gsNonCodewordSmallInputShape preset rows + pure (group, gen) + +private def runGsInterpolationNonCodewordLargeKoala (preset : BenchPreset) + (gen : StdGen) : IO (Prod BenchGroup StdGen) := do + let (inputs, gen) := perturbedLargeInputs gen + let rows := + perturbedInterpRows "KoalaBear.Field" "" "large" preset + inputs.points gsLargeInterpParams gsNonCodewordLargeSlowBackends ++ + perturbedInterpRows "KoalaBear.Fast.Field" "-fast" "large" preset + inputs.fastPoints gsLargeInterpParams gsNonCodewordLargeFastBackends + let group ← runPerturbedGroup + (guruswamiSudanReceivedWordGroupInfos.getD 1 + ⟨"guruswami-sudan-interp-noncodeword-large-koalabear", ""⟩) + gsNonCodewordLargeInputShape preset rows + pure (group, gen) + +private def runGsCoreNonCodewordSmallKoala (preset : BenchPreset) + (gen : StdGen) : IO (Prod BenchGroup StdGen) := do + let (inputs, gen) := perturbedSmallInputs gen + let rows := + perturbedCoreRows "core" "KoalaBear.Field" "" "small" "" false + gsNonCodewordSmallErrors preset + inputs.points gsSmallParams checksumPolynomialArrayKoala + koalaBearRothRootContext koalaBearAlekhnovichRootContext + gsNonCodewordSmallSlowBackends ++ + perturbedCoreRows "core" "KoalaBear.Fast.Field" "-fast" "small" "" false + gsNonCodewordSmallErrors preset + inputs.fastPoints gsSmallParams checksumPolynomialArrayKoalaFast + fastKoalaBearRothRootContext fastKoalaBearAlekhnovichRootContext + gsNonCodewordSmallFastBackends + let group ← runPerturbedGroup + (guruswamiSudanReceivedWordGroupInfos.getD 2 + ⟨"guruswami-sudan-core-noncodeword-small-koalabear", ""⟩) + gsNonCodewordSmallInputShape preset rows + pure (group, gen) + +private def runGsCoreNonCodewordLargeKoala (preset : BenchPreset) + (gen : StdGen) : IO (Prod BenchGroup StdGen) := do + let (inputs, gen) := perturbedLargeInputs gen + let rows := + perturbedCoreRows "core" "KoalaBear.Field" "" "large" "" false + gsNonCodewordLargeErrors preset + inputs.points gsLargeInterpParams checksumPolynomialArrayKoala + koalaBearRothRootContext koalaBearAlekhnovichRootContext + gsNonCodewordLargeSlowBackends ++ + perturbedCoreRows "core" "KoalaBear.Fast.Field" "-fast" "large" "" false + gsNonCodewordLargeErrors preset + inputs.fastPoints gsLargeInterpParams checksumPolynomialArrayKoalaFast + fastKoalaBearRothRootContext fastKoalaBearAlekhnovichRootContext + gsNonCodewordLargeFastBackends + let group ← runPerturbedGroup + (guruswamiSudanReceivedWordGroupInfos.getD 3 + ⟨"guruswami-sudan-core-noncodeword-large-koalabear", ""⟩) + gsNonCodewordLargeInputShape preset rows + pure (group, gen) + +private def runGsFilteredCoreNonCodewordSmallKoala (preset : BenchPreset) + (gen : StdGen) : IO (Prod BenchGroup StdGen) := do + let (inputs, gen) := perturbedSmallInputs gen + let rows := + perturbedCoreRows "filtered-core" "KoalaBear.Field" "" "small" " + filter" + true gsNonCodewordSmallErrors preset + inputs.points gsSmallParams checksumPolynomialArrayKoala + koalaBearRothRootContext koalaBearAlekhnovichRootContext + gsNonCodewordSmallSlowBackends ++ + perturbedCoreRows "filtered-core" "KoalaBear.Fast.Field" "-fast" "small" + " + filter" true gsNonCodewordSmallErrors preset + inputs.fastPoints gsSmallParams checksumPolynomialArrayKoalaFast + fastKoalaBearRothRootContext fastKoalaBearAlekhnovichRootContext + gsNonCodewordSmallFastBackends + let group ← runPerturbedGroup + (guruswamiSudanReceivedWordGroupInfos.getD 4 + ⟨"guruswami-sudan-filtered-core-noncodeword-small-koalabear", ""⟩) + gsNonCodewordSmallFilteredShape preset rows + pure (group, gen) + +private def runGsFilteredCoreNonCodewordLargeKoala (preset : BenchPreset) + (gen : StdGen) : IO (Prod BenchGroup StdGen) := do + let (inputs, gen) := perturbedLargeInputs gen + let rows := + perturbedCoreRows "filtered-core" "KoalaBear.Field" "" "large" " + filter" + true gsNonCodewordLargeErrors preset + inputs.points gsLargeInterpParams checksumPolynomialArrayKoala + koalaBearRothRootContext koalaBearAlekhnovichRootContext + gsNonCodewordLargeSlowBackends ++ + perturbedCoreRows "filtered-core" "KoalaBear.Fast.Field" "-fast" "large" + " + filter" true gsNonCodewordLargeErrors preset + inputs.fastPoints gsLargeInterpParams checksumPolynomialArrayKoalaFast + fastKoalaBearRothRootContext fastKoalaBearAlekhnovichRootContext + gsNonCodewordLargeFastBackends + let group ← runPerturbedGroup + (guruswamiSudanReceivedWordGroupInfos.getD 5 + ⟨"guruswami-sudan-filtered-core-noncodeword-large-koalabear", ""⟩) + gsNonCodewordLargeFilteredShape preset rows + pure (group, gen) /-- Runnable received-word GS benchmark tasks. -/ def guruswamiSudanReceivedWordTasks : List BenchTask := [ @@ -287,8 +386,20 @@ def guruswamiSudanReceivedWordTasks : List BenchTask := [ ⟨"guruswami-sudan-interp-noncodeword-small-koalabear", ""⟩) runGsInterpolationNonCodewordSmallKoala, BenchTask.fromGroupRunner (guruswamiSudanReceivedWordGroupInfos.getD 1 - ⟨"guruswami-sudan-interp-stress-koalabear", ""⟩) - runGsInterpolationStressKoala + ⟨"guruswami-sudan-interp-noncodeword-large-koalabear", ""⟩) + runGsInterpolationNonCodewordLargeKoala, + BenchTask.fromGroupRunner (guruswamiSudanReceivedWordGroupInfos.getD 2 + ⟨"guruswami-sudan-core-noncodeword-small-koalabear", ""⟩) + runGsCoreNonCodewordSmallKoala, + BenchTask.fromGroupRunner (guruswamiSudanReceivedWordGroupInfos.getD 3 + ⟨"guruswami-sudan-core-noncodeword-large-koalabear", ""⟩) + runGsCoreNonCodewordLargeKoala, + BenchTask.fromGroupRunner (guruswamiSudanReceivedWordGroupInfos.getD 4 + ⟨"guruswami-sudan-filtered-core-noncodeword-small-koalabear", ""⟩) + runGsFilteredCoreNonCodewordSmallKoala, + BenchTask.fromGroupRunner (guruswamiSudanReceivedWordGroupInfos.getD 5 + ⟨"guruswami-sudan-filtered-core-noncodeword-large-koalabear", ""⟩) + runGsFilteredCoreNonCodewordLargeKoala ] end CompPolyBench From 620d778729f7fa18039b48ed4bafc53dfa3d0969 Mon Sep 17 00:00:00 2001 From: Valerii Huhnin Date: Thu, 11 Jun 2026 13:00:35 +0000 Subject: [PATCH 04/10] Finish proofs of approximant interpolation correctness --- CompPoly.lean | 15 + .../ApproximantBasis/Correctness.lean | 562 ++++++- .../Correctness/ModularData.lean | 691 +++++++++ .../Correctness/Multiplicity.lean | 258 ++++ .../GuruswamiSudan/PolynomialCorrectness.lean | 2 +- .../Approximant/Correctness.lean | 19 +- .../Approximant/ModularEquation.lean | 666 ++------ .../Approximant/ModularEquation/Basic.lean | 687 +++++++++ .../ModularEquation/Completeness.lean | 1135 ++++++++++++++ .../PolynomialMatrix/Approximant/PMBasis.lean | 513 +------ .../Approximant/PMBasis/Correctness.lean | 1225 +++++++++++++++ .../Approximant/PMBasis/KernelLeaf.lean | 410 +++++ .../PMBasis/KernelLeafCompleteness.lean | 597 ++++++++ .../Approximant/PMBasis/KernelLeafScalar.lean | 1340 +++++++++++++++++ .../PMBasis/KernelLeafSoundness.lean | 613 ++++++++ .../Approximant/PMBasis/KernelLeafSpan.lean | 1212 +++++++++++++++ .../Approximant/PMBasis/Recursion.lean | 174 +++ .../Approximant/PMBasis/XAdicSoundness.lean | 787 ++++++++++ .../WeakPopovMinimal.lean | 285 ++++ .../PolynomialMatrix/Operations.lean | 52 +- .../PolynomialMatrix/RowSelection.lean | 396 +++++ .../PolynomialMatrix/StrassenCorrectness.lean | 810 ++++++++++ 22 files changed, 11410 insertions(+), 1039 deletions(-) create mode 100644 CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Correctness/ModularData.lean create mode 100644 CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Correctness/Multiplicity.lean create mode 100644 CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation/Basic.lean create mode 100644 CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation/Completeness.lean create mode 100644 CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/Correctness.lean create mode 100644 CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/KernelLeaf.lean create mode 100644 CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/KernelLeafCompleteness.lean create mode 100644 CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/KernelLeafScalar.lean create mode 100644 CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/KernelLeafSoundness.lean create mode 100644 CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/KernelLeafSpan.lean create mode 100644 CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/Recursion.lean create mode 100644 CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/XAdicSoundness.lean create mode 100644 CompPoly/LinearAlgebra/PolynomialMatrix/MuldersStorjohannCorrectness/WeakPopovMinimal.lean create mode 100644 CompPoly/LinearAlgebra/PolynomialMatrix/RowSelection.lean create mode 100644 CompPoly/LinearAlgebra/PolynomialMatrix/StrassenCorrectness.lean diff --git a/CompPoly.lean b/CompPoly.lean index e823d381..cdc76bb8 100644 --- a/CompPoly.lean +++ b/CompPoly.lean @@ -19,6 +19,8 @@ import CompPoly.Bivariate.GuruswamiSudan.Interpolation.ApproximantBasis import CompPoly.Bivariate.GuruswamiSudan.Interpolation.ApproximantBasis.Algorithm import CompPoly.Bivariate.GuruswamiSudan.Interpolation.ApproximantBasis.Basic import CompPoly.Bivariate.GuruswamiSudan.Interpolation.ApproximantBasis.Correctness +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.ApproximantBasis.Correctness.ModularData +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.ApproximantBasis.Correctness.Multiplicity import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Basic import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Correctness import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Dense.Algorithm @@ -178,7 +180,17 @@ import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant.Basic import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant.Correctness import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant.ModularEquation +import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant.ModularEquation.Basic +import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant.ModularEquation.Completeness import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant.PMBasis +import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant.PMBasis.Correctness +import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant.PMBasis.KernelLeaf +import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant.PMBasis.KernelLeafCompleteness +import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant.PMBasis.KernelLeafScalar +import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant.PMBasis.KernelLeafSoundness +import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant.PMBasis.KernelLeafSpan +import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant.PMBasis.Recursion +import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant.PMBasis.XAdicSoundness import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant.PartialLinearization import CompPoly.LinearAlgebra.PolynomialMatrix.Basic import CompPoly.LinearAlgebra.PolynomialMatrix.Degree @@ -193,10 +205,13 @@ import CompPoly.LinearAlgebra.PolynomialMatrix.MuldersStorjohannCorrectness.Meas import CompPoly.LinearAlgebra.PolynomialMatrix.MuldersStorjohannCorrectness.Minimal import CompPoly.LinearAlgebra.PolynomialMatrix.MuldersStorjohannCorrectness.Reduction import CompPoly.LinearAlgebra.PolynomialMatrix.MuldersStorjohannCorrectness.RowOps +import CompPoly.LinearAlgebra.PolynomialMatrix.MuldersStorjohannCorrectness.WeakPopovMinimal import CompPoly.LinearAlgebra.PolynomialMatrix.Operations +import CompPoly.LinearAlgebra.PolynomialMatrix.RowSelection import CompPoly.LinearAlgebra.PolynomialMatrix.RowSpan import CompPoly.LinearAlgebra.PolynomialMatrix.Shifted import CompPoly.LinearAlgebra.PolynomialMatrix.ShiftedReduction +import CompPoly.LinearAlgebra.PolynomialMatrix.StrassenCorrectness import CompPoly.Multilinear.Basic import CompPoly.Multilinear.Equiv import CompPoly.Multilinear.ManyEval diff --git a/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Correctness.lean b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Correctness.lean index 0e5dd18c..c87cac7a 100644 --- a/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Correctness.lean +++ b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Correctness.lean @@ -5,13 +5,17 @@ Authors: Valerii Huhnin -/ import CompPoly.Bivariate.GuruswamiSudan.Interpolation.ApproximantBasis.Algorithm +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.ApproximantBasis.Correctness.ModularData +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Correctness +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Correctness.Normalization +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Correctness.Rows +import CompPoly.LinearAlgebra.PolynomialMatrix.RowSelection /-! # Approximant-Basis Interpolation Correctness Surface Theorem statements for the modular-equation reduction and the public -`GSInterpContext` boundary. Proof bodies are intentionally explicit theorem -debt while the executable backend and benchmark evidence are developed. +`GSInterpContext` boundary. -/ namespace CompPoly @@ -25,20 +29,194 @@ open PolynomialMatrix.Approximant variable {F : Type*} [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] +/-! ## Width truncation helpers -/ + +omit [Nontrivial F] [DecidableEq F] in +/-- Entries of the width truncation of a coefficient row. -/ +private theorem rowGet_toCoeffRow_ofCoeffRow (row : PolynomialRow F) + (width : Nat) {j : Nat} (hj : j < width) : + rowGet (CBivariate.toCoeffRow width (CBivariate.ofCoeffRow row)) j = + rowGet row j := by + rw [CBivariate.toCoeffRow, rowGet, Array.getD_eq_getD_getElem?, + List.getElem?_toArray, List.getElem?_map, List.getElem?_range hj, + Option.map_some, Option.getD_some, CBivariate.ofCoeffRow, + CPolynomial.coeff_ofArray] + rfl + +omit [Nontrivial F] [DecidableEq F] in +/-- Width truncation preserves all coefficients below the width. -/ +private theorem coeff_toCoeffRow_ofCoeffRow (row : PolynomialRow F) + {width i j : Nat} (hj : j < width) : + CBivariate.coeff + (CBivariate.ofCoeffRow (CBivariate.toCoeffRow width + (CBivariate.ofCoeffRow row))) i j = + CBivariate.coeff (CBivariate.ofCoeffRow row) i j := by + have hsize : (CBivariate.toCoeffRow width (CBivariate.ofCoeffRow row)).size = + width := CBivariate.toCoeffRow_size _ _ + rw [CBivariate.coeff_ofCoeffRow_of_lt _ (by omega)] + rw [show (CBivariate.toCoeffRow width (CBivariate.ofCoeffRow row)).getD j 0 = + rowGet (CBivariate.toCoeffRow width (CBivariate.ofCoeffRow row)) j from rfl] + rw [rowGet_toCoeffRow_ofCoeffRow row width hj] + rcases Nat.lt_or_ge j row.size with hjrow | hjrow + · rw [CBivariate.coeff_ofCoeffRow_of_lt _ hjrow] + rfl + · rw [CBivariate.coeff_ofCoeffRow_of_size_le _ hjrow] + rw [show rowGet row j = row.getD j 0 from rfl] + rw [Array.getD_eq_getD_getElem?, Array.getElem?_eq_none hjrow] + exact CPolynomial.coeff_zero i + +omit [Nontrivial F] [DecidableEq F] in +/-- Every interpolation monomial has `Y`-degree below the interpolation width +when the `Y`-weight is positive. -/ +private theorem interpolationMonomials_yDegree_lt (params : GSInterpParams) + (hw : 0 < yWeight params) {m : CBivariate.Monomial} + (hm : m ∈ (interpolationMonomials params).toList) : + m.yDegree < interpolationWidth params := by + have hbound := CBivariate.monomialsWeightedDegreeLE_sound + (xWeight := 1) (yWeight := yWeight params) + (bound := params.weightedDegreeBound) (by simpa [interpolationMonomials] using hm) + have hy : yWeight params * m.yDegree ≤ params.weightedDegreeBound := by omega + have hycap : m.yDegree ≤ interpolationYCap params := by + rw [interpolationYCap, Nat.le_div_iff_mul_le hw] + calc m.yDegree * yWeight params = yWeight params * m.yDegree := + Nat.mul_comm _ _ + _ ≤ params.weightedDegreeBound := by omega + rw [interpolationWidth] + omega + +omit [Nontrivial F] [DecidableEq F] in +/-- The interpolation coefficient vector only sees the first +`interpolationWidth params` coefficient rows. -/ +private theorem interpolationCoefficientVector_toCoeffRow (params : GSInterpParams) + (hw : 0 < yWeight params) (row : PolynomialRow F) : + interpolationCoefficientVector params + (CBivariate.ofCoeffRow (CBivariate.toCoeffRow (interpolationWidth params) + (CBivariate.ofCoeffRow row))) = + interpolationCoefficientVector params (CBivariate.ofCoeffRow row) := by + rw [interpolationCoefficientVector, interpolationCoefficientVector, + interpolationCoefficientVectorOnBasis, interpolationCoefficientVectorOnBasis] + apply Array.ext + · simp + · intro i hi _hi' + have hi' : i < (interpolationMonomials params).size := by + simpa using hi + simp only [Array.getElem_map] + exact coeff_toCoeffRow_ofCoeffRow row + (interpolationMonomials_yDegree_lt params hw + (Array.getElem_mem_toList hi')) + +omit [Nontrivial F] [DecidableEq F] in +/-- Width truncation does not increase the shifted row degree. -/ +private theorem rowShiftedDegree?_toCoeffRow_le {row : PolynomialRow F} + {shift : Array Nat} {width d d' : Nat} + (hd : rowShiftedDegree? row shift = some d) + (hd' : rowShiftedDegree? + (CBivariate.toCoeffRow width (CBivariate.ofCoeffRow row)) shift = some d') : + d' ≤ d := by + rcases exists_shiftedEntryDegree?_eq_of_rowShiftedDegree?_eq_some hd' with + ⟨j, hj, hentry⟩ + have hjw : j < width := by + simpa [CBivariate.toCoeffRow_size] using hj + have hgets := rowGet_toCoeffRow_ofCoeffRow row width hjw + simp only [shiftedEntryDegree?, hgets] at hentry + have hne : ¬ rowGet row j == 0 := by + by_contra h0 + rw [if_pos h0] at hentry + cases hentry + have hjrow : j < row.size := by + by_contra hge + apply hne + rw [rowGet, Array.getD_eq_getD_getElem?, Array.getElem?_eq_none (by omega)] + simp + refine shiftedEntryDegree?_le_of_rowShiftedDegree?_eq_some hd hjrow ?_ + simp only [shiftedEntryDegree?] + exact hentry + +omit [Nontrivial F] [DecidableEq F] in +/-- A vector with all-zero entries does not normalize. -/ +private theorem normalizeVector?_eq_none_of_all_zero {v : Array F} + (h : ∀ i, i < v.size → v.getD i 0 = 0) : + normalizeVector? v = none := by + have hfirst : firstNonzeroIndex? v = none := by + rw [firstNonzeroIndex?, List.find?_eq_none] + intro i hi + have := h i (List.mem_range.mp hi) + simp [this] + rw [normalizeVector?, hfirst] + +/-- The coefficient row of a zero row never normalizes. -/ +private theorem normalizeInterpolationPolynomial?_eq_none_of_rowIsZero + (params : GSInterpParams) {row : PolynomialRow F} (h : RowIsZero row) : + normalizeInterpolationPolynomial? params + (interpolationCoefficientVector params (CBivariate.ofCoeffRow row)) = none := by + have hz : ∀ i, + i < (interpolationCoefficientVector params + (CBivariate.ofCoeffRow row)).size → + (interpolationCoefficientVector params + (CBivariate.ofCoeffRow row)).getD i 0 = 0 := by + intro i hi + rw [interpolationCoefficientVector, interpolationCoefficientVectorOnBasis] at hi ⊢ + have hi' : i < (interpolationMonomials params).size := by simpa using hi + rw [Array.getD_eq_getD_getElem?, Array.getElem?_eq_getElem hi, + Option.getD_some, Array.getElem_map] + rcases Nat.lt_or_ge ((interpolationMonomials params)[i]).yDegree row.size with + hj | hj + · rw [CBivariate.coeff_ofCoeffRow_of_lt _ hj] + have hrow0 : row.getD ((interpolationMonomials params)[i]).yDegree 0 = 0 := by + refine h _ ?_ + rw [Array.getD_eq_getD_getElem?, Array.getElem?_eq_getElem hj, + Option.getD_some] + exact Array.getElem_mem_toList hj + rw [hrow0] + exact CPolynomial.coeff_zero _ + · exact CBivariate.coeff_ofCoeffRow_of_size_le _ hj + rw [normalizeInterpolationPolynomial?, normalizeInterpolationPolynomialOnBasis?, + normalizeVector?_eq_none_of_all_zero hz] + +/-! ## The modular-equation layer -/ + /-- The executable GS modular row predicate is equivalent to packed -multiplicity constraints for the bivariate coefficient-row view. -/ +multiplicity constraints for the bivariate coefficient-row view, for distinct +interpolation nodes and rows inside the interpolation width. -/ theorem gsModularEquation_row_iff_multiplicity (V : CPolynomial.VanishingPolynomialContext F) (E : CPolynomial.BatchEvalContext F) (modCtx : CPolynomial.ModContext F) (mulCtx : CPolynomial.MulContext F) (points : Array (F × F)) (params : GSInterpParams) - (row : PolynomialRow F) : + (row : PolynomialRow F) + (hdistinct : DistinctXCoordinates points) + (hwidth : row.size ≤ interpolationWidth params) : let data := buildGSModularData V E mulCtx modCtx points params rowSatisfiesModularBool mulCtx modCtx row data.matrix data.moduli = true ↔ CBivariate.satisfiesMultiplicityConstraintsBool (CBivariate.ofCoeffRow row) points params.multiplicity = true := by - sorry + intro data + set G := V.vanishingPolynomial (points.map fun point ↦ point.1) with hGdef + set R := CPolynomial.interpolateCoefficientFormWithVanishing E G points with hRdef + have hGcorrect : G = + CPolynomial.vanishingPolynomialArray (points.map fun point ↦ point.1) := + V.correct _ + have hG : Polynomial.Monic G.toPoly := by + rw [hGcorrect] + exact vanishingPolynomialArray_toPoly_monic _ + have hdata_matrix : data.matrix = gsRelationMatrixWithRG mulCtx modCtx R G params := + rfl + have hdata_moduli : data.moduli = gsModuli mulCtx G params.multiplicity := rfl + rw [hdata_matrix, hdata_moduli, + rowSatisfiesModularBool_gsRelationMatrix_iff mulCtx modCtx hG R params hwidth] + have hR : ∀ point, point ∈ points.toList → CPolynomial.eval point.1 R = point.2 := by + intro point hpoint + have heval := CPolynomial.interpolateCoefficientForm_eval_point V E + (by simpa [DistinctXCoordinates] using hdistinct) hpoint + simpa [hRdef, hGdef, CPolynomial.interpolateCoefficientForm] using heval + rw [CBivariate.satisfiesMultiplicityConstraintsBool_iff_hasMultiplicity, + ← CBivariate.satisfiesMultiplicityConstraints_iff_hasMultiplicity, + ← vanishing_pow_dvd_hasseDeriv_eval_iff_satisfiesMultiplicityConstraints + hdistinct hR, + hGcorrect] + +/-! ## Soundness -/ /-- Soundness for executable approximant-basis interpolation. -/ theorem approximantBasisInterpolate_sound @@ -49,7 +227,217 @@ theorem approximantBasisInterpolate_sound (h : approximantBasisInterpolate V E solver points params = some Q) : ValidInterpolationWitness points params Q := by - sorry + unfold approximantBasisInterpolate at h + by_cases hLow : params.messageDegree ≤ 1 + · simp only [hLow, if_true, Option.some_inj] at h + rw [← h] + exact lowMessageDegreeInterpolation_sound (points := points) (params := params) hLow + · simp only [hLow, if_false] at h + rw [approximantBasisPositiveInterpolate] at h + by_cases hdistinctBool : distinctXCoordinatesBool points = true + · rw [if_pos hdistinctBool] at h + have hdistinct : DistinctXCoordinates points := + LeeOSullivan.distinctXCoordinatesBool_iff.mp hdistinctBool + set G := V.vanishingPolynomial (points.map fun point ↦ point.1) with hGdef + set R := CPolynomial.interpolateCoefficientFormWithVanishing E G points + with hRdef + set data := buildGSModularDataWithRG solver.mulContext solver.modContext + R G params with hdata + set basis := solver.solutionBasis (modularEquation data) data.shift + with hbasis + change (match leastShiftedDegreeChoice? basis data.shift with + | none => none + | some choice => + if choice.degree ≤ params.weightedDegreeBound then + normalizeApproximantCandidate? params (CBivariate.ofCoeffRow choice.row) + else none) = some Q at h + cases hchoice : leastShiftedDegreeChoice? basis data.shift with + | none => + rw [hchoice] at h + change (none : Option (CBivariate F)) = some Q at h + simp at h + | some choice => + rw [hchoice] at h + change (if choice.degree ≤ params.weightedDegreeBound then + normalizeApproximantCandidate? params (CBivariate.ofCoeffRow choice.row) + else none) = some Q at h + by_cases hdeg : choice.degree ≤ params.weightedDegreeBound + · rw [if_pos hdeg] at h + -- The chosen row is a basis member satisfying the modular predicate. + rcases leastShiftedDegreeChoice?_some_valid hchoice with + ⟨hindex, hrowEq, hrowDeg⟩ + have hmem : choice.row ∈ MatrixRows basis := by + rw [MatrixRows, hrowEq, Array.getD_eq_getD_getElem?, + Array.getElem?_eq_getElem hindex, Option.getD_some] + exact Array.getElem_mem_toList hindex + have hsat := solver.sound (modularEquation data) data.shift + choice.row hmem + -- Truncate the chosen row to the interpolation width. + set width := interpolationWidth params with hwidthdef + set row' := CBivariate.toCoeffRow width + (CBivariate.ofCoeffRow choice.row) with hrow' + have hrow'size : row'.size = width := CBivariate.toCoeffRow_size _ _ + have hmatrixsize : (modularEquation data).matrix.size = width := + gsRelationMatrixWithModuli_size _ _ _ _ _ + have hagree : ∀ k, k < (modularEquation data).matrix.size → + rowGet choice.row k = rowGet row' k := by + intro k hk + rw [hrow', rowGet_toCoeffRow_ofCoeffRow choice.row width + (by omega)] + have hsat' : rowSatisfiesModularBool solver.mulContext solver.modContext + row' (modularEquation data).matrix (modularEquation data).moduli = + true := by + rw [← rowSatisfiesModularBool_congr_of_agree solver.mulContext + solver.modContext (modularEquation data).matrix + (modularEquation data).moduli hagree] + exact hsat + -- Transfer to multiplicity constraints for the truncated polynomial. + have hiff := gsModularEquation_row_iff_multiplicity V E + solver.modContext solver.mulContext points params row' hdistinct + (by omega) + have hdataEq : buildGSModularData V E solver.mulContext + solver.modContext points params = data := rfl + rw [hdataEq] at hiff + have hmultBool := hiff.mp hsat' + have hmult : CBivariate.SatisfiesMultiplicityConstraints + (CBivariate.ofCoeffRow row') points params.multiplicity := by + rw [CBivariate.satisfiesMultiplicityConstraints_iff_hasMultiplicity] + exact (CBivariate.satisfiesMultiplicityConstraintsBool_iff_hasMultiplicity + _ _ _).mp hmultBool + -- The normalization only sees in-width coefficients. + have hw : 0 < yWeight params := by + rw [yWeight] + omega + have hvec := interpolationCoefficientVector_toCoeffRow params hw + choice.row + have hnorm' : LeeOSullivan.normalizeLeeCandidate? params + (CBivariate.ofCoeffRow row') = some Q := by + rw [LeeOSullivan.normalizeLeeCandidate?, + normalizeInterpolationPolynomial?, hrow', hvec] + rw [normalizeApproximantCandidate?, + normalizeInterpolationPolynomial?] at h + exact h + -- The truncated row is nonzero with bounded shifted degree. + cases hd' : rowShiftedDegree? row' data.shift with + | none => + exfalso + have hzero : RowIsZero row' := + rowShiftedDegree?_eq_none_iff.mp hd' + rw [LeeOSullivan.normalizeLeeCandidate?, + normalizeInterpolationPolynomial?_eq_none_of_rowIsZero params + hzero] at hnorm' + simp at hnorm' + | some d' => + have hd'le : d' ≤ choice.degree := + rowShiftedDegree?_toCoeffRow_le hrowDeg hd' + have hshiftEq : data.shift = + CBivariate.weightedDegreeShift (yWeight params) row'.size := by + rw [hrow'size, hwidthdef] + rfl + have hdegRaw : CBivariate.natWeightedDegree + (CBivariate.ofCoeffRow row') 1 (yWeight params) ≤ + params.weightedDegreeBound := by + refine CBivariate.natWeightedDegree_ofCoeffRow_le_of_rowShiftedDegree?_le + row' (yWeight params) params.weightedDegreeBound d' ?_ (by omega) + rw [← hshiftEq] + exact hd' + exact LeeOSullivan.normalizeLeeCandidate?_sound_of_raw + (points := points) (params := params) hLow hdegRaw hmult hnorm' + · rw [if_neg hdeg] at h + simp at h + · rw [if_neg hdistinctBool] at h + simp at h + +/-! ## Completeness -/ + +omit [LawfulBEq F] [Nontrivial F] [DecidableEq F] in +/-- The shifted row degree only sees shift entries below the row size. -/ +private theorem rowShiftedDegree?_congr_shift {row : PolynomialRow F} + {shift shift' : Array Nat} + (h : ∀ j, j < row.size → shift.getD j 0 = shift'.getD j 0) : + rowShiftedDegree? row shift = rowShiftedDegree? row shift' := by + rw [rowShiftedDegree?, rowShiftedDegree?] + refine List.foldl_ext _ _ _ fun acc j hj ↦ ?_ + have hj' : j < row.size := List.mem_range.mp hj + simp only [shiftedEntryDegree?, h j hj'] + +omit [Nontrivial F] [DecidableEq F] in +/-- Entry access for the weighted-degree shift array. -/ +private theorem weightedDegreeShift_getD {w width j : Nat} (hj : j < width) : + (CBivariate.weightedDegreeShift w width).getD j 0 = j * w := by + rw [CBivariate.weightedDegreeShift, Array.getD_eq_getD_getElem?, + List.getElem?_toArray, List.getElem?_map, List.getElem?_range hj, + Option.map_some, Option.getD_some] + +omit [DecidableEq F] in +/-- Zero rows have zero coefficient rows. -/ +private theorem ofCoeffRow_eq_zero_of_rowIsZero {row : PolynomialRow F} + (h : RowIsZero row) : CBivariate.ofCoeffRow row = 0 := by + rw [CBivariate.ofCoeffRow] + show (CPolynomial.ofArray row : CPolynomial (CPolynomial F)) = 0 + rw [CPolynomial.eq_zero_iff_coeff_zero] + intro n + rw [CPolynomial.coeff_ofArray] + rcases Nat.lt_or_ge n row.size with hn | hn + · refine h _ ?_ + rw [Array.getD_eq_getD_getElem?, Array.getElem?_eq_getElem hn, Option.getD_some] + exact Array.getElem_mem_toList hn + · rw [Array.getD_eq_getD_getElem?, Array.getElem?_eq_none hn] + rfl + +omit [Nontrivial F] [DecidableEq F] in +/-- Bounded-weighted-degree polynomials have no `Y`-coefficients at or above +the interpolation width. -/ +private theorem coeff_eq_zero_of_interpolationWidth_le + {params : GSInterpParams} (hw : 0 < yWeight params) {Q : CBivariate F} + (hdeg : CBivariate.natWeightedDegree Q 1 (yWeight params) ≤ + params.weightedDegreeBound) + {j : Nat} (hj : interpolationWidth params ≤ j) : + CPolynomial.coeff Q j = 0 := by + have hcoeffs : ∀ i, CBivariate.coeff Q i j = 0 := by + intro i + by_contra hne0 + have hle := (CBivariate.natWeightedDegree_le_iff_coeff Q 1 (yWeight params) + params.weightedDegreeBound).mp hdeg i j hne0 + have hdm := Nat.div_add_mod params.weightedDegreeBound (yWeight params) + have hmod := Nat.mod_lt params.weightedDegreeBound hw + have hsum : (params.weightedDegreeBound / yWeight params + 1) * yWeight params + = yWeight params * (params.weightedDegreeBound / yWeight params) + + yWeight params := by + ring + have hwidthmul : (params.weightedDegreeBound / yWeight params + 1) * + yWeight params ≤ j * yWeight params := by + refine Nat.mul_le_mul_right _ ?_ + rw [interpolationWidth, interpolationYCap] at hj + omega + have hcomm : j * yWeight params = yWeight params * j := Nat.mul_comm _ _ + omega + show CPolynomial.coeff Q j = 0 + rw [CPolynomial.eq_zero_iff_coeff_zero] + intro i + exact hcoeffs i + +omit [Nontrivial F] [DecidableEq F] in +/-- Width truncation is the identity on bounded-weighted-degree polynomials. -/ +private theorem ofCoeffRow_toCoeffRow_eq + {params : GSInterpParams} (hw : 0 < yWeight params) {Q : CBivariate F} + (hdeg : CBivariate.natWeightedDegree Q 1 (yWeight params) ≤ + params.weightedDegreeBound) : + CBivariate.ofCoeffRow (CBivariate.toCoeffRow (interpolationWidth params) Q) = + Q := by + rw [CBivariate.ofCoeffRow] + show (CPolynomial.ofArray (CBivariate.toCoeffRow (interpolationWidth params) Q) : + CPolynomial (CPolynomial F)) = Q + apply CPolynomial.eq_iff_coeff.2 + intro n + rw [CPolynomial.coeff_ofArray] + rcases Nat.lt_or_ge n (interpolationWidth params) with hn | hn + · rw [CBivariate.toCoeffRow, Array.getD_eq_getD_getElem?, List.getElem?_toArray, + List.getElem?_map, List.getElem?_range hn, Option.map_some, Option.getD_some] + · rw [CBivariate.toCoeffRow, Array.getD_eq_getD_getElem?, List.getElem?_toArray, + List.getElem?_map, List.getElem?_eq_none (by simpa using hn), + Option.map_none, Option.getD_none] + exact (coeff_eq_zero_of_interpolationWidth_le hw hdeg hn).symm /-- Completeness for executable approximant-basis interpolation on distinct input `x`-coordinates. -/ @@ -61,7 +449,167 @@ theorem approximantBasisInterpolate_complete (hdistinct : DistinctXCoordinates points) (hexists : ∃ Q, ValidInterpolationWitness points params Q) : ∃ Q, approximantBasisInterpolate V E solver points params = some Q := by - sorry + by_cases hLow : params.messageDegree ≤ 1 + · refine ⟨lowMessageDegreeInterpolation points params.multiplicity, ?_⟩ + unfold approximantBasisInterpolate + rw [if_pos hLow] + · rcases hexists with ⟨Q₀, hQ₀ne, hQ₀deg, hQ₀mult⟩ + have hw : 0 < yWeight params := by + rw [yWeight] + omega + have hdistinctBool : distinctXCoordinatesBool points = true := + LeeOSullivan.distinctXCoordinatesBool_iff.mpr hdistinct + set G := V.vanishingPolynomial (points.map fun point ↦ point.1) with hGdef + set R := CPolynomial.interpolateCoefficientFormWithVanishing E G points + with hRdef + set data := buildGSModularDataWithRG solver.mulContext solver.modContext + R G params with hdata + set basis := solver.solutionBasis (modularEquation data) data.shift + with hbasis + set width := interpolationWidth params with hwidthdef + -- The witness coefficient row. + set row₀ := CBivariate.toCoeffRow width Q₀ with hrow₀ + have hround : CBivariate.ofCoeffRow row₀ = Q₀ := + ofCoeffRow_toCoeffRow_eq hw hQ₀deg + have hrow₀size : row₀.size = width := CBivariate.toCoeffRow_size _ _ + have hnz₀ : rowIsZero row₀ = false := by + rw [Bool.eq_false_iff] + intro htrue + exact hQ₀ne (by rw [← hround, + ofCoeffRow_eq_zero_of_rowIsZero (rowIsZero_iff.mp htrue)]) + have hmatrixsize : (modularEquation data).matrix.size = width := + gsRelationMatrixWithModuli_size _ _ _ _ _ + have hsolwidth : (modularEquation data).solutionWidth = width := by + rw [ModularEquation.solutionWidth, hmatrixsize] + -- The witness row satisfies the modular predicate. + have hsat₀ : rowSatisfiesModularBool solver.mulContext solver.modContext row₀ + (modularEquation data).matrix (modularEquation data).moduli = true := by + have hiff := gsModularEquation_row_iff_multiplicity V E + solver.modContext solver.mulContext points params row₀ hdistinct + (by omega) + have hdataEq : buildGSModularData V E solver.mulContext + solver.modContext points params = data := rfl + rw [hdataEq] at hiff + refine hiff.mpr ?_ + rw [hround] + exact (CBivariate.satisfiesMultiplicityConstraintsBool_iff_hasMultiplicity + _ _ _).mpr hQ₀mult + -- The witness row has shifted degree at most the weighted-degree bound. + have hshift₀ : data.shift = CBivariate.weightedDegreeShift (yWeight params) + row₀.size := by + rw [hrow₀size] + rfl + obtain ⟨d₀, hd₀⟩ : ∃ d₀, rowShiftedDegree? row₀ data.shift = some d₀ := by + cases hcase : rowShiftedDegree? row₀ data.shift with + | none => + have hzero := rowShiftedDegree?_eq_none_iff.mp hcase + rw [Bool.eq_false_iff] at hnz₀ + exact absurd (rowIsZero_iff.mpr hzero) hnz₀ + | some d => exact ⟨d, rfl⟩ + have hd₀le : d₀ ≤ params.weightedDegreeBound := by + have hnat := CBivariate.rowShiftedDegree?_eq_natWeightedDegree_ofCoeffRow + row₀ (yWeight params) d₀ (by rw [← hshift₀]; exact hd₀) + rw [hround] at hnat + omega + -- The GS moduli are monic powers of the vanishing polynomial. + have hGmonic : Polynomial.Monic G.toPoly := by + rw [show G = CPolynomial.vanishingPolynomialArray + (points.map fun point ↦ point.1) from V.correct _] + exact vanishingPolynomialArray_toPoly_monic _ + have hmonic : ∀ b, b < (modularEquation data).moduli.size → + ((modularEquation data).moduli.getD b 0).monic := by + intro b hb + have hsize : (modularEquation data).moduli.size = params.multiplicity := + gsModuli_size _ _ _ + have hgetD : (modularEquation data).moduli.getD b 0 = + G ^ (params.multiplicity - b) := + gsModuli_getD solver.mulContext G (by omega) + rw [hgetD] + refine (CPolynomial.monic_toPoly_iff _).mpr ?_ + rw [CPolynomial.toPoly_pow] + exact hGmonic.pow _ + -- The relation matrix exposes every modular column to the row predicate. + have hcols : (modularEquation data).moduli.size ≤ + MatrixWidth (modularEquation data).matrix := by + have hmodsize : (modularEquation data).moduli.size = params.multiplicity := + gsModuli_size _ _ _ + have hmatwidth : MatrixWidth (modularEquation data).matrix = + params.multiplicity := + gsRelationMatrixWithModuli_matrixWidth _ _ _ _ _ + omega + -- The GS shift is aligned with the principal solution width. + have hshiftsize : data.shift.size = (modularEquation data).solutionWidth := by + have hdatashift : data.shift = + CBivariate.weightedDegreeShift (yWeight params) width := rfl + rw [hdatashift, hsolwidth, CBivariate.weightedDegreeShift] + simp + -- Apply the solver completeness/minimality contract. + rcases solver.complete_minimal (modularEquation data) data.shift row₀ hmonic hcols + hshiftsize hsat₀ hnz₀ (by omega) with + ⟨hbasisWidth, basisRow, bDeg, hbMem, hbDeg, hbMin⟩ + have hbDegLe : bDeg ≤ params.weightedDegreeBound := + le_trans (hbMin d₀ hd₀) hd₀le + -- Select the least shifted-degree basis row. + rcases List.getElem_of_mem hbMem with ⟨bIdx, hbIdxList, hbGet⟩ + have hbIdx : bIdx < basis.size := by + simpa [MatrixRows] using hbIdxList + have hbGetD : basis.getD bIdx #[] = basisRow := by + rw [Array.getD_eq_getD_getElem?, Array.getElem?_eq_getElem hbIdx, + Option.getD_some] + simpa [MatrixRows, Array.getElem_toList] using hbGet + rcases leastShiftedDegreeChoice?_some_of_degree (M := basis) + (shift := data.shift) (i := bIdx) (d := bDeg) hbIdx + (by rw [hbGetD]; exact hbDeg) with + ⟨choice, hchoice, hchoiceLe⟩ + have hchoiceBound : choice.degree ≤ params.weightedDegreeBound := by + omega + rcases leastShiftedDegreeChoice?_some_valid hchoice with + ⟨hcIdx, hcRow, hcDeg⟩ + have hcMem : choice.row ∈ MatrixRows basis := by + rw [MatrixRows, hcRow, Array.getD_eq_getD_getElem?, + Array.getElem?_eq_getElem hcIdx, Option.getD_some] + exact Array.getElem_mem_toList hcIdx + have hcWidth : choice.row.size ≤ width := by + have := hbasisWidth choice.row hcMem + omega + -- The chosen row reconstructs and normalizes. + have hrawNe : CBivariate.ofCoeffRow choice.row ≠ 0 := + LeeOSullivan.ofCoeffRow_ne_zero_of_rowShiftedDegree?_some hcDeg + have hcShift : rowShiftedDegree? choice.row + (CBivariate.weightedDegreeShift (yWeight params) choice.row.size) = + some choice.degree := by + rw [← hcDeg] + refine (rowShiftedDegree?_congr_shift fun j hj ↦ ?_).symm + rw [weightedDegreeShift_getD hj] + have hjwidth : j < width := by omega + have hdatashift : data.shift = + CBivariate.weightedDegreeShift (yWeight params) width := rfl + rw [hdatashift, weightedDegreeShift_getD hjwidth] + have hdegRaw : CBivariate.natWeightedDegree (CBivariate.ofCoeffRow choice.row) + 1 (yWeight params) ≤ params.weightedDegreeBound := + CBivariate.natWeightedDegree_ofCoeffRow_le_of_rowShiftedDegree?_le + choice.row (yWeight params) params.weightedDegreeBound choice.degree + hcShift hchoiceBound + rcases LeeOSullivan.normalizeLeeCandidate?_some_of_raw hLow hrawNe hdegRaw with + ⟨Q, hnorm⟩ + -- Assemble the executable run. + refine ⟨Q, ?_⟩ + unfold approximantBasisInterpolate + rw [if_neg hLow, approximantBasisPositiveInterpolate, if_pos hdistinctBool] + change (match leastShiftedDegreeChoice? basis data.shift with + | none => none + | some choice => + if choice.degree ≤ params.weightedDegreeBound then + normalizeApproximantCandidate? params (CBivariate.ofCoeffRow choice.row) + else none) = some Q + rw [hchoice] + change (if choice.degree ≤ params.weightedDegreeBound then + normalizeApproximantCandidate? params (CBivariate.ofCoeffRow choice.row) + else none) = some Q + rw [if_pos hchoiceBound] + rw [normalizeApproximantCandidate?] + rw [LeeOSullivan.normalizeLeeCandidate?] at hnorm + exact hnorm /-- Public approximant-basis interpolation backend context. -/ def approximantBasisInterpContext diff --git a/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Correctness/ModularData.lean b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Correctness/ModularData.lean new file mode 100644 index 00000000..f6f16751 --- /dev/null +++ b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Correctness/ModularData.lean @@ -0,0 +1,691 @@ +/- +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.Interpolation.ApproximantBasis.Basic +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.ApproximantBasis.Correctness.Multiplicity +import CompPoly.Univariate.DivisionCorrectness + +/-! +# Executable GS Modular-Data Bridges + +Semantic characterizations of the executable approximant-basis modular data: +the modulus array `gsModuli`, the incrementally built binomial relation matrix +`gsRelationColumn` / `gsRelationMatrixWithModuli`, and the executable row +predicate `rowSatisfiesModularBool`. The main result identifies the modular +row predicate over the GS data with divisibility of every sheared coefficient +`(hasseDeriv b Q.toPoly).eval R` by `G^(s-b)`. +-/ + +namespace CompPoly + +namespace GuruswamiSudan + +namespace ApproximantBasis + +open Polynomial +open PolynomialMatrix + +variable {F : Type*} [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + +/-! ## Generic helpers -/ + +omit [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] in +private theorem foldl_add_eq_sum {M : Type*} [AddCommMonoid M] (f : Nat → M) : + ∀ n : Nat, + (List.range n).foldl (fun acc k ↦ acc + f k) 0 = ∑ k ∈ Finset.range n, f k := by + intro n + induction n with + | zero => simp + | succ n ih => + rw [List.range_succ, List.foldl_append, ih, List.foldl_cons, List.foldl_nil, + Finset.sum_range_succ] + +omit [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] in +/-- First component of an `MProd`-state fold whose second component evolves +independently of the first, as a `Prod`-state fold. -/ +private theorem foldl_mprod_fst {α β : Type u} {γ : Type*} + (f : α → β → γ → α) (g : β → γ → β) : + ∀ (l : List γ) (a : α) (b : β), + (l.foldl (fun s c ↦ (⟨f s.fst s.snd c, g s.snd c⟩ : MProd α β)) ⟨a, b⟩).fst = + (l.foldl (fun s c ↦ (f s.1 s.2 c, g s.2 c)) (a, b)).1 := by + intro l + induction l with + | nil => + intro a b + rfl + | cons c l ih => + intro a b + rw [List.foldl_cons, List.foldl_cons] + exact ih _ _ + +omit [Nontrivial F] [DecidableEq F] in +private theorem toPoly_finset_sum (f : Nat → CPolynomial F) (n : Nat) : + (∑ k ∈ Finset.range n, f k).toPoly = ∑ k ∈ Finset.range n, (f k).toPoly := by + induction n with + | zero => + simp [CPolynomial.toPoly_zero] + | succ n ih => + rw [Finset.sum_range_succ, Finset.sum_range_succ, CPolynomial.toPoly_add, ih] + +omit [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] in +/-- A common modulus of differences identifies the two divisibility facts. -/ +private theorem dvd_iff_dvd_of_dvd_sub {M a b : Polynomial F} (h : M ∣ a - b) : + M ∣ a ↔ M ∣ b := by + constructor + · intro ha + have hb : M ∣ a - (a - b) := dvd_sub ha h + simpa using hb + · intro hb + have ha : M ∣ (a - b) + b := dvd_add h hb + simpa using ha + +omit [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] in +/-- Entry access for `PolynomialMatrix.ofFn`. -/ +theorem ofFn_rowGet (rows width : Nat) (entry : Nat → Nat → CPolynomial F) + {i j : Nat} (hi : i < rows) (hj : j < width) : + rowGet ((PolynomialMatrix.ofFn rows width entry).getD i #[]) j = entry i j := by + have hrow : (PolynomialMatrix.ofFn rows width entry).getD i #[] = + ((List.range width).map (entry i)).toArray := by + rw [PolynomialMatrix.ofFn, Array.getD_eq_getD_getElem?, List.getElem?_toArray, + List.getElem?_map, List.getElem?_range hi, Option.map_some, Option.getD_some] + rw [hrow, rowGet, Array.getD_eq_getD_getElem?, List.getElem?_toArray, + List.getElem?_map, List.getElem?_range hj, Option.map_some, Option.getD_some] + +omit [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] in +/-- Row count of `PolynomialMatrix.ofFn`. -/ +theorem ofFn_size (rows width : Nat) (entry : Nat → Nat → CPolynomial F) : + (PolynomialMatrix.ofFn rows width entry).size = rows := by + simp [PolynomialMatrix.ofFn] + +omit [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] in +/-- Width of `PolynomialMatrix.ofFn` with at least one row. -/ +theorem ofFn_matrixWidth (rows width : Nat) (entry : Nat → Nat → CPolynomial F) + (hrows : 0 < rows) : + MatrixWidth (PolynomialMatrix.ofFn rows width entry) = width := by + rw [PolynomialMatrix.ofFn, MatrixWidth] + rw [show ((List.range rows).map + (fun i ↦ ((List.range width).map (entry i)).toArray)).toArray[0]? = + some ((List.range width).map (entry 0)).toArray from by + rw [List.getElem?_toArray, List.getElem?_map, List.getElem?_range hrows] + simp] + simp + +/-! ## Modular reduction semantics -/ + +omit [Nontrivial F] [DecidableEq F] in +/-- `modByMonicWith` computes the Mathlib monic remainder under `toPoly`. -/ +theorem modByMonicWith_toPoly (modCtx : CPolynomial.ModContext F) + {p M : CPolynomial F} (hM : Polynomial.Monic M.toPoly) : + (PolynomialMatrix.modByMonicWith modCtx p M).toPoly = p.toPoly %ₘ M.toPoly := by + have hMne : M ≠ 0 := by + intro hzero + have : M.toPoly = 0 := by + rw [hzero, CPolynomial.toPoly_zero] + exact hM.ne_zero this + rw [PolynomialMatrix.modByMonicWith, if_neg (by simpa using hMne), + modCtx.modByMonic_eq_modByMonic] + exact CPolynomial.modByMonic_toPoly_eq_modByMonic p M + ((CPolynomial.monic_toPoly_iff M).mpr hM) + +omit [Nontrivial F] [DecidableEq F] in +/-- `modByMonicWith` is congruent to the identity modulo the modulus. -/ +theorem dvd_modByMonicWith_sub (modCtx : CPolynomial.ModContext F) + {p M : CPolynomial F} (hM : Polynomial.Monic M.toPoly) : + M.toPoly ∣ (PolynomialMatrix.modByMonicWith modCtx p M).toPoly - p.toPoly := by + rw [modByMonicWith_toPoly modCtx hM] + have hdecomp := Polynomial.modByMonic_add_div p.toPoly M.toPoly + refine ⟨-(p.toPoly /ₘ M.toPoly), ?_⟩ + linear_combination hdecomp + +omit [Nontrivial F] [DecidableEq F] in +/-- The executable remainder vanishes exactly on multiples of the modulus. -/ +theorem modByMonicWith_eq_zero_iff_dvd (modCtx : CPolynomial.ModContext F) + {p M : CPolynomial F} (hM : Polynomial.Monic M.toPoly) : + PolynomialMatrix.modByMonicWith modCtx p M = 0 ↔ M.toPoly ∣ p.toPoly := by + rw [← Polynomial.modByMonic_eq_zero_iff_dvd hM] + rw [← modByMonicWith_toPoly modCtx hM] + constructor + · intro h + rw [h, CPolynomial.toPoly_zero] + · intro h + have := (CPolynomial.toPoly_eq_zero_iff _).mp h + exact this + +/-! ## The GS modulus array -/ + +omit [DecidableEq F] in +private theorem gsModuli_loop (mulCtx : CPolynomial.MulContext F) (G : CPolynomial F) : + ∀ (l : List Nat) (k : Nat), + l.foldl + (fun ascending _ ↦ + ascending.push (mulCtx.mul (ascending.getD (ascending.size - 1) 0) G)) + (((List.range (k + 1)).map fun i ↦ G ^ (i + 1)).toArray) = + ((List.range (k + 1 + l.length)).map fun i ↦ G ^ (i + 1)).toArray := by + intro l + induction l with + | nil => + intro k + simp + | cons a l ih => + intro k + rw [List.foldl_cons] + have hsize : (((List.range (k + 1)).map fun i ↦ G ^ (i + 1)).toArray).size = k + 1 := by + simp + have hget : (((List.range (k + 1)).map fun i ↦ G ^ (i + 1)).toArray).getD k 0 = + G ^ (k + 1) := by + rw [Array.getD_eq_getD_getElem?, List.getElem?_toArray, List.getElem?_map, + List.getElem?_range (by omega)] + simp + rw [hsize] + simp only [Nat.add_sub_cancel] + rw [hget, mulCtx.mul_eq_mul, ← pow_succ] + have hpush : (((List.range (k + 1)).map fun i ↦ G ^ (i + 1)).toArray).push + (G ^ (k + 1 + 1)) = + ((List.range (k + 2)).map fun i ↦ G ^ (i + 1)).toArray := by + rw [List.push_toArray] + congr 1 + rw [show k + 2 = (k + 1) + 1 from rfl, List.range_succ] + simp [List.range_succ] + rw [hpush, ih (k + 1)] + have harg : k + 1 + 1 + l.length = k + 1 + (a :: l).length := by + simp + omega + rw [harg] + +omit [DecidableEq F] in +/-- Closed form for the GS modulus array before reversal. -/ +theorem gsModuli_eq (mulCtx : CPolynomial.MulContext F) (G : CPolynomial F) (s : Nat) : + gsModuli mulCtx G s = (((List.range s).map fun i ↦ G ^ (i + 1)).toArray).reverse := by + rcases Nat.eq_zero_or_pos s with hs | hs + · subst hs + rfl + · rw [gsModuli] + simp only [beq_iff_eq, Std.Legacy.Range.forIn_eq_forIn_range', + Std.Legacy.Range.size, Nat.add_sub_cancel, Nat.div_one, + List.forIn_pure_yield_eq_foldl, bind_pure_comp, map_pure, + Id.run_pure, Nat.ne_of_gt hs, if_false] + congr 1 + have hstart : (#[G] : Array (CPolynomial F)) = + ((List.range 1).map fun i ↦ G ^ (i + 1)).toArray := by + simp + rw [hstart, gsModuli_loop mulCtx G (List.range' 1 (s - 1)) 0] + rw [List.length_range'] + congr 2 + congr 1 + omega + +omit [DecidableEq F] in +/-- The GS modulus array has one modulus per multiplicity level. -/ +theorem gsModuli_size (mulCtx : CPolynomial.MulContext F) (G : CPolynomial F) (s : Nat) : + (gsModuli mulCtx G s).size = s := by + rw [gsModuli_eq] + simp + +omit [DecidableEq F] in +/-- The `b`-th GS modulus is `G^(s-b)`. -/ +theorem gsModuli_getD (mulCtx : CPolynomial.MulContext F) (G : CPolynomial F) + {b s : Nat} (hb : b < s) : + (gsModuli mulCtx G s).getD b 0 = G ^ (s - b) := by + rw [gsModuli_eq] + have hsize : ((((List.range s).map fun i ↦ G ^ (i + 1)).toArray).reverse).size = s := by + simp + rw [Array.getD_eq_getD_getElem?, Array.getElem?_eq_getElem (by omega)] + simp only [Option.getD_some] + rw [Array.getElem_reverse] + simp only [List.getElem_toArray, List.getElem_map, List.getElem_range, + List.size_toArray, List.length_map, List.length_range] + congr 1 + omega + +/-! ## The GS relation column -/ + +private def relationColumnStep (mulCtx : CPolynomial.MulContext F) + (modCtx : CPolynomial.ModContext F) (reducedR modulus : CPolynomial F) (b : Nat) + (state : Array (CPolynomial F) × CPolynomial F) (j : Nat) : + Array (CPolynomial F) × CPolynomial F := + (state.1.setIfInBounds j (CPolynomial.C ((j.choose b : F)) * state.2), + PolynomialMatrix.modByMonicWith modCtx (mulCtx.mul state.2 reducedR) modulus) + +omit [DecidableEq F] in +private theorem gsRelationColumn_eq_foldl (mulCtx : CPolynomial.MulContext F) + (modCtx : CPolynomial.ModContext F) + (R modulus : CPolynomial F) (width b : Nat) : + gsRelationColumn mulCtx modCtx R modulus width b = + ((List.range' b (width - b)).foldl + (relationColumnStep mulCtx modCtx + (PolynomialMatrix.modByMonicWith modCtx R modulus) modulus b) + (Array.replicate width (0 : CPolynomial F), 1)).1 := by + rw [gsRelationColumn] + simp only [Std.Legacy.Range.forIn_eq_forIn_range', Std.Legacy.Range.size, + Nat.add_sub_cancel, Nat.div_one, List.forIn_pure_yield_eq_foldl, + bind_pure_comp, map_pure, Id.run_pure] + exact foldl_mprod_fst + (fun col pow j ↦ col.setIfInBounds j (CPolynomial.C ((j.choose b : F)) * pow)) + (fun pow _j ↦ PolynomialMatrix.modByMonicWith modCtx + (mulCtx.mul pow (PolynomialMatrix.modByMonicWith modCtx R modulus)) modulus) + (List.range' b (width - b)) (Array.replicate width 0) 1 + +omit [Nontrivial F] [DecidableEq F] in +private theorem relationColumn_foldl_size_untouched (mulCtx : CPolynomial.MulContext F) + (modCtx : CPolynomial.ModContext F) (reducedR modulus : CPolynomial F) (b : Nat) : + ∀ (n j₀ : Nat) (col : Array (CPolynomial F)) (pow : CPolynomial F), + (((List.range' j₀ n).foldl + (relationColumnStep mulCtx modCtx reducedR modulus b) (col, pow)).1.size = + col.size) ∧ + (∀ j, j < j₀ → + ((List.range' j₀ n).foldl + (relationColumnStep mulCtx modCtx reducedR modulus b) (col, pow)).1.getD j 0 = + col.getD j 0) := by + intro n + induction n with + | zero => + intro j₀ col pow + exact ⟨rfl, fun j _hj ↦ rfl⟩ + | succ n ih => + intro j₀ col pow + rw [List.range'_succ, List.foldl_cons] + have hstep : relationColumnStep mulCtx modCtx reducedR modulus b (col, pow) j₀ = + (col.setIfInBounds j₀ (CPolynomial.C ((j₀.choose b : F)) * pow), + PolynomialMatrix.modByMonicWith modCtx (mulCtx.mul pow reducedR) modulus) := rfl + rw [hstep] + rcases ih (j₀ + 1) (col.setIfInBounds j₀ (CPolynomial.C ((j₀.choose b : F)) * pow)) + (PolynomialMatrix.modByMonicWith modCtx (mulCtx.mul pow reducedR) modulus) + with ⟨hsize, huntouched⟩ + refine ⟨by rw [hsize]; simp, ?_⟩ + intro j hj + rw [huntouched j (by omega)] + rw [Array.getD_eq_getD_getElem?, Array.getD_eq_getD_getElem?, + Array.getElem?_setIfInBounds_ne (by omega)] + +omit [Nontrivial F] [DecidableEq F] in +private theorem relationColumn_foldl_spec (mulCtx : CPolynomial.MulContext F) + (modCtx : CPolynomial.ModContext F) + {R reducedR modulus : CPolynomial F} + (hM : Polynomial.Monic modulus.toPoly) + (hred : modulus.toPoly ∣ reducedR.toPoly - R.toPoly) (b : Nat) : + ∀ (n j₀ : Nat) (col : Array (CPolynomial F)) (pow : CPolynomial F), + b ≤ j₀ → + modulus.toPoly ∣ pow.toPoly - R.toPoly ^ (j₀ - b) → + (((List.range' j₀ n).foldl + (relationColumnStep mulCtx modCtx reducedR modulus b) (col, pow)).1.size = + col.size) ∧ + (∀ j, j < j₀ ∨ j₀ + n ≤ j → + ((List.range' j₀ n).foldl + (relationColumnStep mulCtx modCtx reducedR modulus b) (col, pow)).1.getD j 0 = + col.getD j 0) ∧ + (∀ j, j₀ ≤ j → j < j₀ + n → j < col.size → + modulus.toPoly ∣ + (((List.range' j₀ n).foldl + (relationColumnStep mulCtx modCtx reducedR modulus b) (col, pow)).1.getD j 0).toPoly - + Polynomial.C ((j.choose b : F)) * R.toPoly ^ (j - b)) := by + intro n + induction n with + | zero => + intro j₀ col pow _hb _hpow + refine ⟨rfl, fun j _hj ↦ rfl, fun j hj₁ hj₂ _hj₃ ↦ ?_⟩ + omega + | succ n ih => + intro j₀ col pow hb hpow + rw [List.range'_succ, List.foldl_cons] + set col' := col.setIfInBounds j₀ (CPolynomial.C ((j₀.choose b : F)) * pow) with hcol' + set pow' := PolynomialMatrix.modByMonicWith modCtx (mulCtx.mul pow reducedR) modulus + with hpow' + have hstep : relationColumnStep mulCtx modCtx reducedR modulus b (col, pow) j₀ = + (col', pow') := rfl + rw [hstep] + have hpow'spec : modulus.toPoly ∣ pow'.toPoly - R.toPoly ^ (j₀ + 1 - b) := by + have hmul : modulus.toPoly ∣ + (mulCtx.mul pow reducedR).toPoly - R.toPoly ^ (j₀ - b) * R.toPoly := by + rw [mulCtx.mul_eq_mul, CPolynomial.toPoly_mul] + have hexpand : pow.toPoly * reducedR.toPoly - + R.toPoly ^ (j₀ - b) * R.toPoly = + pow.toPoly * (reducedR.toPoly - R.toPoly) + + (pow.toPoly - R.toPoly ^ (j₀ - b)) * R.toPoly := by + ring + rw [hexpand] + exact dvd_add (Dvd.dvd.mul_left hred _) (Dvd.dvd.mul_right hpow _) + have hmod := dvd_modByMonicWith_sub modCtx + (p := mulCtx.mul pow reducedR) (M := modulus) hM + have hcomb := dvd_add hmod hmul + have hrw : (PolynomialMatrix.modByMonicWith modCtx (mulCtx.mul pow reducedR) + modulus).toPoly - (mulCtx.mul pow reducedR).toPoly + + ((mulCtx.mul pow reducedR).toPoly - R.toPoly ^ (j₀ - b) * R.toPoly) = + (PolynomialMatrix.modByMonicWith modCtx (mulCtx.mul pow reducedR) + modulus).toPoly - R.toPoly ^ (j₀ - b) * R.toPoly := by + ring + rw [hrw] at hcomb + rw [hpow'] + have hpowsucc : R.toPoly ^ (j₀ - b) * R.toPoly = R.toPoly ^ (j₀ + 1 - b) := by + rw [← pow_succ] + congr 1 + omega + rw [← hpowsucc] + exact hcomb + rcases ih (j₀ + 1) col' pow' (by omega) hpow'spec with ⟨hsize, huntouched, hspec⟩ + have hcol'size : col'.size = col.size := by + rw [hcol'] + simp + refine ⟨by rw [hsize, hcol'size], ?_, ?_⟩ + · intro j hj + have hj' : j < j₀ + 1 ∨ j₀ + 1 + n ≤ j := by omega + rw [huntouched j hj'] + rw [hcol'] + rcases hj with hj | hj + · rw [Array.getD_eq_getD_getElem?, Array.getD_eq_getD_getElem?] + rw [Array.getElem?_setIfInBounds_ne (by omega)] + · rw [Array.getD_eq_getD_getElem?, Array.getD_eq_getD_getElem?] + rw [Array.getElem?_setIfInBounds_ne (by omega)] + · intro j hj₁ hj₂ hj₃ + by_cases hj : j = j₀ + · subst hj + have huntouchedj : ((List.range' (j + 1) n).foldl + (relationColumnStep mulCtx modCtx reducedR modulus b) (col', pow')).1.getD j 0 = + col'.getD j 0 := huntouched j (by omega) + rw [huntouchedj, hcol'] + have hset : (col.setIfInBounds j + (CPolynomial.C ((j.choose b : F)) * pow)).getD j 0 = + CPolynomial.C ((j.choose b : F)) * pow := by + rw [Array.getD_eq_getD_getElem?, + Array.getElem?_setIfInBounds_self_of_lt (by omega)] + simp + rw [hset] + rw [CPolynomial.toPoly_mul, CPolynomial.C_toPoly] + have hfactor : Polynomial.C ((j.choose b : F)) * pow.toPoly - + Polynomial.C ((j.choose b : F)) * R.toPoly ^ (j - b) = + Polynomial.C ((j.choose b : F)) * (pow.toPoly - R.toPoly ^ (j - b)) := by + ring + rw [hfactor] + exact Dvd.dvd.mul_left hpow _ + · exact hspec j (by omega) (by omega) (by rw [hcol'size]; exact hj₃) + +omit [DecidableEq F] in +/-- Size of the GS relation column. -/ +theorem gsRelationColumn_size (mulCtx : CPolynomial.MulContext F) + (modCtx : CPolynomial.ModContext F) (R modulus : CPolynomial F) (width b : Nat) : + (gsRelationColumn mulCtx modCtx R modulus width b).size = width := by + rw [gsRelationColumn_eq_foldl] + rw [(relationColumn_foldl_size_untouched mulCtx modCtx + (PolynomialMatrix.modByMonicWith modCtx R modulus) modulus b (width - b) b + (Array.replicate width 0) 1).1] + simp + +omit [DecidableEq F] in +/-- Entries of the GS relation column below the diagonal vanish. -/ +theorem gsRelationColumn_getD_of_lt (mulCtx : CPolynomial.MulContext F) + (modCtx : CPolynomial.ModContext F) (R modulus : CPolynomial F) + {width b j : Nat} (hj : j < b) : + (gsRelationColumn mulCtx modCtx R modulus width b).getD j 0 = 0 := by + rw [gsRelationColumn_eq_foldl] + rw [(relationColumn_foldl_size_untouched mulCtx modCtx + (PolynomialMatrix.modByMonicWith modCtx R modulus) modulus b (width - b) b + (Array.replicate width 0) 1).2 j hj] + rw [Array.getD_eq_getD_getElem?, Array.getElem?_replicate] + split <;> simp + +omit [DecidableEq F] in +/-- Entries of the GS relation column are congruent to the binomial powers. -/ +theorem gsRelationColumn_getD_congr (mulCtx : CPolynomial.MulContext F) + (modCtx : CPolynomial.ModContext F) {R modulus : CPolynomial F} + (hM : Polynomial.Monic modulus.toPoly) + {width b j : Nat} (hbj : b ≤ j) (hj : j < width) : + modulus.toPoly ∣ + ((gsRelationColumn mulCtx modCtx R modulus width b).getD j 0).toPoly - + Polynomial.C ((j.choose b : F)) * R.toPoly ^ (j - b) := by + rw [gsRelationColumn_eq_foldl] + have hred := dvd_modByMonicWith_sub modCtx (p := R) (M := modulus) hM + rcases relationColumn_foldl_spec mulCtx modCtx (R := R) + (reducedR := PolynomialMatrix.modByMonicWith modCtx R modulus) + (modulus := modulus) hM hred b (width - b) b (Array.replicate width 0) 1 + le_rfl (by simp [CPolynomial.toPoly_one]) with ⟨_, _, hspec⟩ + exact hspec j hbj (by omega) (by simpa using hj) + +/-! ## The GS relation matrix and the modular row predicate -/ + +omit [DecidableEq F] in +/-- The `b`-th GS modulus is `G^(s-b)`, with default value `1`. -/ +theorem gsModuli_getD_one (mulCtx : CPolynomial.MulContext F) (G : CPolynomial F) + {b s : Nat} (hb : b < s) : + (gsModuli mulCtx G s).getD b 1 = G ^ (s - b) := by + have hsize : (gsModuli mulCtx G s).size = s := gsModuli_size mulCtx G s + have h0 := gsModuli_getD mulCtx G hb + rw [Array.getD_eq_getD_getElem?, Array.getElem?_eq_getElem (by omega), + Option.getD_some] at h0 ⊢ + exact h0 + +omit [DecidableEq F] in +/-- Entry access for the GS relation matrix. -/ +theorem gsRelationMatrixWithModuli_entry (mulCtx : CPolynomial.MulContext F) + (modCtx : CPolynomial.ModContext F) (R : CPolynomial F) + (moduli : Array (CPolynomial F)) (params : GSInterpParams) {k b : Nat} + (hk : k < interpolationWidth params) (hb : b < params.multiplicity) : + rowGet ((gsRelationMatrixWithModuli mulCtx modCtx R moduli params).getD k #[]) b = + (gsRelationColumn mulCtx modCtx R (moduli.getD b 1) + (interpolationWidth params) b).getD k 0 := by + rw [gsRelationMatrixWithModuli, ofFn_rowGet _ _ _ hk hb] + congr 1 + rw [Array.getD_eq_getD_getElem?, List.getElem?_toArray, List.getElem?_map, + List.getElem?_range hb, Option.map_some, Option.getD_some] + +omit [DecidableEq F] in +/-- The GS relation matrix has one row per interpolation coefficient. -/ +theorem gsRelationMatrixWithModuli_size (mulCtx : CPolynomial.MulContext F) + (modCtx : CPolynomial.ModContext F) (R : CPolynomial F) + (moduli : Array (CPolynomial F)) (params : GSInterpParams) : + (gsRelationMatrixWithModuli mulCtx modCtx R moduli params).size = + interpolationWidth params := + ofFn_size _ _ _ + +omit [DecidableEq F] in +/-- The GS relation matrix has one column per multiplicity level. -/ +theorem gsRelationMatrixWithModuli_matrixWidth (mulCtx : CPolynomial.MulContext F) + (modCtx : CPolynomial.ModContext F) (R : CPolynomial F) + (moduli : Array (CPolynomial F)) (params : GSInterpParams) : + MatrixWidth (gsRelationMatrixWithModuli mulCtx modCtx R moduli params) = + params.multiplicity := + ofFn_matrixWidth _ _ _ (Nat.succ_pos _) + +omit [Nontrivial F] [DecidableEq F] in +/-- Outer-coefficient expansion of a coefficient row under `toPoly`. -/ +private theorem toPoly_ofCoeffRow_eq_sum (row : PolynomialRow F) : + (CBivariate.ofCoeffRow row).toPoly = + ∑ k ∈ Finset.range row.size, + Polynomial.monomial k ((row.getD k 0).toPoly) := by + ext n + rw [CBivariate.toPoly_coeff, Polynomial.finset_sum_coeff] + by_cases hn : n < row.size + · rw [Finset.sum_eq_single n + (fun k _hk hkn ↦ by rw [Polynomial.coeff_monomial, if_neg hkn]) + (fun hnotin ↦ absurd (Finset.mem_range.mpr hn) hnotin)] + rw [Polynomial.coeff_monomial, if_pos rfl, CBivariate.ofCoeffRow, + CPolynomial.coeff_ofArray] + · rw [Finset.sum_eq_zero + (fun k hk ↦ by + rw [Polynomial.coeff_monomial, + if_neg (by rcases Finset.mem_range.mp hk with h; omega)])] + rw [CBivariate.ofCoeffRow, CPolynomial.coeff_ofArray, + Array.getD_eq_getD_getElem?, Array.getElem?_eq_none (by omega)] + simp [CPolynomial.toPoly_zero] + +omit [Nontrivial F] [DecidableEq F] in +/-- Sheared coefficients of a coefficient row: the outer Hasse derivative of +`ofCoeffRow row` evaluated at `R` is the binomial-weighted power sum. -/ +theorem hasseDeriv_toPoly_ofCoeffRow_eval (row : PolynomialRow F) + (R : Polynomial F) (b : Nat) : + (Polynomial.hasseDeriv b (CBivariate.ofCoeffRow row).toPoly).eval R = + ∑ k ∈ Finset.range row.size, + Polynomial.C ((k.choose b : F)) * (row.getD k 0).toPoly * R ^ (k - b) := by + rw [toPoly_ofCoeffRow_eq_sum, map_sum, Polynomial.eval_finset_sum] + refine Finset.sum_congr rfl fun k _hk ↦ ?_ + rw [Polynomial.hasseDeriv_monomial, Polynomial.eval_monomial, + Polynomial.C_eq_natCast] + +omit [Nontrivial F] [DecidableEq F] in +/-- Column entries of a row-by-matrix product as binomial sums. -/ +private theorem rowGet_rowMulMatrixWith_eq_sum (mulCtx : CPolynomial.MulContext F) + (row : PolynomialRow F) (M : PolynomialMatrix F) {j : Nat} + (hj : j < MatrixWidth M) : + rowGet (rowMulMatrixWith mulCtx row M) j = + ∑ k ∈ Finset.range row.size, rowGet row k * rowGet (M.getD k #[]) j := by + rw [rowMulMatrixWith, rowGet] + rw [Array.getD_eq_getD_getElem?, List.getElem?_toArray, List.getElem?_map, + List.getElem?_range hj, Option.map_some, Option.getD_some] + rw [foldl_add_eq_sum (fun k ↦ mulCtx.mul (rowGet row k) (rowGet (M.getD k #[]) j))] + refine Finset.sum_congr rfl fun k _hk ↦ ?_ + rw [mulCtx.mul_eq_mul] + +omit [Nontrivial F] [DecidableEq F] in +/-- One product-entry fold as a sum over the matrix height. -/ +private theorem rowMulMatrix_foldl_eq_sum_size (mulCtx : CPolynomial.MulContext F) + (row : PolynomialRow F) (M : PolynomialMatrix F) (j : Nat) : + (List.range row.size).foldl + (fun acc k ↦ acc + mulCtx.mul (rowGet row k) (rowGet (M.getD k #[]) j)) 0 = + ∑ k ∈ Finset.range M.size, rowGet row k * rowGet (M.getD k #[]) j := by + rw [foldl_add_eq_sum (fun k ↦ mulCtx.mul (rowGet row k) (rowGet (M.getD k #[]) j))] + have hterm : ∀ k, mulCtx.mul (rowGet row k) (rowGet (M.getD k #[]) j) = + rowGet row k * rowGet (M.getD k #[]) j := fun k ↦ mulCtx.mul_eq_mul _ _ + simp only [hterm] + rcases Nat.le_total row.size M.size with h | h + · refine Finset.sum_subset + (by intro x hx; simp only [Finset.mem_range] at hx ⊢; omega) fun k _hk hknot ↦ ?_ + have hk : row.size ≤ k := by simpa using hknot + have hzero : rowGet row k = 0 := by + rw [rowGet, Array.getD_eq_getD_getElem?, Array.getElem?_eq_none hk] + rfl + rw [hzero, zero_mul] + · symm + refine Finset.sum_subset + (by intro x hx; simp only [Finset.mem_range] at hx ⊢; omega) fun k _hk hknot ↦ ?_ + have hk : M.size ≤ k := by simpa using hknot + have hzero : M.getD k #[] = #[] := by + rw [Array.getD_eq_getD_getElem?, Array.getElem?_eq_none hk] + rfl + rw [hzero, show rowGet (#[] : PolynomialRow F) j = 0 from rfl, mul_zero] + +omit [Nontrivial F] [DecidableEq F] in +/-- The row-by-matrix product only sees the first `M.size` row entries. -/ +theorem rowMulMatrixWith_congr_of_agree (mulCtx : CPolynomial.MulContext F) + {row row' : PolynomialRow F} (M : PolynomialMatrix F) + (hagree : ∀ k, k < M.size → rowGet row k = rowGet row' k) : + rowMulMatrixWith mulCtx row M = rowMulMatrixWith mulCtx row' M := by + rw [rowMulMatrixWith, rowMulMatrixWith] + congr 1 + refine List.map_congr_left fun j _hj ↦ ?_ + rw [rowMulMatrix_foldl_eq_sum_size, rowMulMatrix_foldl_eq_sum_size] + refine Finset.sum_congr rfl fun k hk ↦ ?_ + rw [hagree k (Finset.mem_range.mp hk)] + +omit [Nontrivial F] [DecidableEq F] in +/-- The modular row predicate only sees the first `M.size` row entries. -/ +theorem rowSatisfiesModularBool_congr_of_agree (mulCtx : CPolynomial.MulContext F) + (modCtx : CPolynomial.ModContext F) {row row' : PolynomialRow F} + (M : PolynomialMatrix F) (moduli : Array (CPolynomial F)) + (hagree : ∀ k, k < M.size → rowGet row k = rowGet row' k) : + rowSatisfiesModularBool mulCtx modCtx row M moduli = + rowSatisfiesModularBool mulCtx modCtx row' M moduli := by + rw [rowSatisfiesModularBool, rowSatisfiesModularBool, rowMulMatrixModDiagonalWith, + rowMulMatrixModDiagonalWith, rowMulMatrixWith_congr_of_agree mulCtx M hagree] + +omit [Nontrivial F] [DecidableEq F] in +/-- The executable modular row predicate, columnwise. -/ +private theorem rowSatisfiesModularBool_iff_forall (mulCtx : CPolynomial.MulContext F) + (modCtx : CPolynomial.ModContext F) (row : PolynomialRow F) + (M : PolynomialMatrix F) (moduli : Array (CPolynomial F)) : + rowSatisfiesModularBool mulCtx modCtx row M moduli = true ↔ + ∀ b, b < moduli.size → + PolynomialMatrix.modByMonicWith modCtx + (rowGet (rowMulMatrixWith mulCtx row M) b) (moduli.getD b 0) = 0 := by + rw [rowSatisfiesModularBool, rowMulMatrixModDiagonalWith, rowModDiagonalWith] + rw [Array.all_eq_true] + constructor + · intro h b hb + have hb' : b < (((List.range moduli.size).map fun j ↦ + PolynomialMatrix.modByMonicWith modCtx + (rowGet (rowMulMatrixWith mulCtx row M) j) (moduli.getD j 0)).toArray).size := by + simpa using hb + have := h b hb' + simpa using this + · intro h b hb + have hb' : b < moduli.size := by simpa using hb + simpa using h b hb' + +omit [DecidableEq F] in +/-- The executable GS modular row predicate over the relation matrix and +modulus array is exactly divisibility of every sheared coefficient +`(hasseDeriv b (ofCoeffRow row).toPoly).eval R` by `G^(s-b)`. -/ +theorem rowSatisfiesModularBool_gsRelationMatrix_iff + (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) + {G : CPolynomial F} (hG : Polynomial.Monic G.toPoly) + (R : CPolynomial F) (params : GSInterpParams) {row : PolynomialRow F} + (hwidth : row.size ≤ interpolationWidth params) : + rowSatisfiesModularBool mulCtx modCtx row + (gsRelationMatrixWithRG mulCtx modCtx R G params) + (gsModuli mulCtx G params.multiplicity) = true ↔ + ∀ b, b < params.multiplicity → + G.toPoly ^ (params.multiplicity - b) ∣ + (Polynomial.hasseDeriv b (CBivariate.ofCoeffRow row).toPoly).eval R.toPoly := by + set s := params.multiplicity with hs + set width := interpolationWidth params with hwidthdef + set M := gsRelationMatrixWithRG mulCtx modCtx R G params with hM + have hMsize : MatrixWidth M = s := + gsRelationMatrixWithModuli_matrixWidth mulCtx modCtx R _ params + have hmodsize : (gsModuli mulCtx G s).size = s := gsModuli_size mulCtx G s + rw [rowSatisfiesModularBool_iff_forall, hmodsize] + refine forall_congr' fun b ↦ ?_ + refine imp_congr_right fun hb ↦ ?_ + have hmonic : Polynomial.Monic ((G ^ (s - b)).toPoly) := by + rw [CPolynomial.toPoly_pow] + exact hG.pow _ + rw [gsModuli_getD mulCtx G hb] + rw [modByMonicWith_eq_zero_iff_dvd modCtx hmonic] + rw [CPolynomial.toPoly_pow] + rw [hasseDeriv_toPoly_ofCoeffRow_eval] + have hentry : ∀ k, k < row.size → + G.toPoly ^ (s - b) ∣ + (rowGet row k * rowGet (M.getD k #[]) b).toPoly - + Polynomial.C ((k.choose b : F)) * (rowGet row k).toPoly * + R.toPoly ^ (k - b) := by + intro k hk + have hkwidth : k < width := by omega + have hMk : rowGet (M.getD k #[]) b = + (gsRelationColumn mulCtx modCtx R (G ^ (s - b)) width b).getD k 0 := by + rw [hM, gsRelationMatrixWithRG, + gsRelationMatrixWithModuli_entry mulCtx modCtx R _ params hkwidth hb, + gsModuli_getD_one mulCtx G hb] + rw [hMk, CPolynomial.toPoly_mul] + rcases Nat.lt_or_ge k b with hkb | hkb + · rw [gsRelationColumn_getD_of_lt mulCtx modCtx R (G ^ (s - b)) hkb] + rw [Nat.choose_eq_zero_of_lt hkb] + simp [CPolynomial.toPoly_zero] + · have hcongr := gsRelationColumn_getD_congr mulCtx modCtx + (R := R) (modulus := G ^ (s - b)) hmonic hkb hkwidth + have hmul := Dvd.dvd.mul_left hcongr (rowGet row k).toPoly + have hexpand : (rowGet row k).toPoly * + (((gsRelationColumn mulCtx modCtx R (G ^ (s - b)) width b).getD k 0).toPoly - + Polynomial.C ((k.choose b : F)) * R.toPoly ^ (k - b)) = + (rowGet row k).toPoly * + ((gsRelationColumn mulCtx modCtx R (G ^ (s - b)) width b).getD k 0).toPoly - + Polynomial.C ((k.choose b : F)) * (rowGet row k).toPoly * + R.toPoly ^ (k - b) := by + ring + rw [hexpand] at hmul + rwa [CPolynomial.toPoly_pow] at hmul + have hsum : G.toPoly ^ (s - b) ∣ + (rowGet (rowMulMatrixWith mulCtx row M) b).toPoly - + ∑ k ∈ Finset.range row.size, + Polynomial.C ((k.choose b : F)) * (rowGet row k).toPoly * + R.toPoly ^ (k - b) := by + rw [rowGet_rowMulMatrixWith_eq_sum mulCtx row M (by omega)] + rw [toPoly_finset_sum] + rw [← Finset.sum_sub_distrib] + exact Finset.dvd_sum fun k hk ↦ hentry k (Finset.mem_range.mp hk) + rw [dvd_iff_dvd_of_dvd_sub hsum] + congr! 1 + +end ApproximantBasis + +end GuruswamiSudan + +end CompPoly diff --git a/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Correctness/Multiplicity.lean b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Correctness/Multiplicity.lean new file mode 100644 index 00000000..c7c36f07 --- /dev/null +++ b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Correctness/Multiplicity.lean @@ -0,0 +1,258 @@ +/- +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.Interpolation.Basic +import CompPoly.Bivariate.GuruswamiSudan.PolynomialCorrectness +import CompPoly.Univariate.Vanishing +import Mathlib.Algebra.Polynomial.Taylor + +/-! +# Shear Equivalence for Guruswami-Sudan Multiplicity Constraints + +The approximant-basis backend reduces GS interpolation to diagonal modular +congruences on the `Z^b`-coefficients of the sheared polynomial `Q(X, R + Z)`. +This file proves the underlying semantic equivalence: for distinct +interpolation nodes, the packed multiplicity constraints on `Q` hold iff every +sheared coefficient `C_b = (hasseDeriv b Q.toPoly).eval R` is divisible by +`G^(s-b)`, where `G` is the vanishing polynomial of the nodes and `R` +interpolates the values. + +The proof works one node at a time. Writing `M_b = (hasseDeriv b Q.toPoly)` +for the outer `Y`-Hasse derivatives, multiplicity at `(x, y)` says that the +family `M_b.eval (C y)` is `(X - x)^(s-b)`-divisible, while the modular +congruence constrains the family `M_b.eval R`. Because `R` and `C y` agree at +`x`, an outer Taylor expansion transfers each divisibility family to the +other; distinctness of the nodes then glues the per-node factors into powers +of `G`. +-/ + +namespace CompPoly + +namespace GuruswamiSudan + +namespace ApproximantBasis + +open Polynomial + +variable {F : Type*} [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + +omit [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] in +/-- `(X - x)^k` divides `A` iff the first `k` Hasse derivatives of `A` vanish +at `x`. -/ +theorem X_sub_C_pow_dvd_iff_hasseDeriv_eval_eq_zero + (A : Polynomial F) (x : F) (k : Nat) : + (X - C x) ^ k ∣ A ↔ + ∀ a, a < k → (Polynomial.hasseDeriv a A).eval x = 0 := by + rw [Polynomial.X_sub_C_pow_dvd_iff, Polynomial.X_pow_dvd_iff] + constructor + · intro h a ha + have hcoeff := h a ha + rwa [← Polynomial.taylor_apply, Polynomial.taylor_coeff] at hcoeff + · intro h d hd + rw [← Polynomial.taylor_apply, Polynomial.taylor_coeff] + exact h d hd + +omit [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] in +/-- Shear-transfer core: the `(X - x)`-adic divisibility family of the outer +Hasse-derivative evaluations of `P` moves between outer evaluation points `u` +and `v` that agree modulo `X - x`. -/ +theorem X_sub_C_pow_dvd_hasseDeriv_eval_of_dvd_sub + {P : Polynomial (Polynomial F)} {u v : Polynomial F} {x : F} {s : Nat} + (huv : (X - C x) ∣ (v - u)) + (h : ∀ b, b < s → + (X - C x) ^ (s - b) ∣ (Polynomial.hasseDeriv b P).eval u) + {b : Nat} (hb : b < s) : + (X - C x) ^ (s - b) ∣ (Polynomial.hasseDeriv b P).eval v := by + have heval : (Polynomial.hasseDeriv b P).eval v = + (Polynomial.taylor u (Polynomial.hasseDeriv b P)).eval (v - u) := by + rw [Polynomial.taylor_apply, Polynomial.eval_comp] + simp + rw [heval, Polynomial.eval_eq_sum, Polynomial.sum_def] + refine Finset.dvd_sum fun t _ht ↦ ?_ + rw [Polynomial.taylor_coeff] + by_cases hts : t + b < s + · have hsplit : (X - C x) ^ (s - b) = + (X - C x) ^ (s - (t + b)) * (X - C x) ^ t := by + rw [← pow_add] + congr 1 + omega + rw [hsplit] + refine mul_dvd_mul ?_ (pow_dvd_pow_of_dvd huv t) + have hcomp : Polynomial.hasseDeriv t (Polynomial.hasseDeriv b P) = + (t + b).choose t • Polynomial.hasseDeriv (t + b) P := by + have hmap := Polynomial.hasseDeriv_comp (R := Polynomial F) t b + calc Polynomial.hasseDeriv t (Polynomial.hasseDeriv b P) + = ((Polynomial.hasseDeriv t).comp (Polynomial.hasseDeriv b)) P := rfl + _ = ((t + b).choose t • Polynomial.hasseDeriv (t + b)) P := by rw [hmap] + _ = (t + b).choose t • Polynomial.hasseDeriv (t + b) P := rfl + rw [hcomp] + have heval_smul : + (((t + b).choose t • Polynomial.hasseDeriv (t + b) P).eval u) = + (t + b).choose t • ((Polynomial.hasseDeriv (t + b) P).eval u) := by + simp + rw [heval_smul, nsmul_eq_mul] + exact Dvd.dvd.mul_left (h (t + b) hts) _ + · have hpow : (X - C x) ^ (s - b) ∣ (v - u) ^ t := + dvd_trans (pow_dvd_pow _ (by omega)) (pow_dvd_pow_of_dvd huv t) + exact Dvd.dvd.mul_left hpow _ + +/-- At a single node `(x, y)` with `R(x) = y`, the `(X - x)`-adic divisibility +family of the sheared coefficients `(hasseDeriv b Q.toPoly).eval R` is +equivalent to GS multiplicity of `Q` at the node. -/ +theorem X_sub_C_pow_dvd_hasseDeriv_eval_iff_hasMultiplicity + {Q : CBivariate F} {R : Polynomial F} {x y : F} {s : Nat} + (hRx : R.eval x = y) : + (∀ b, b < s → + (X - C x) ^ (s - b) ∣ (Polynomial.hasseDeriv b Q.toPoly).eval R) ↔ + CBivariate.hasMultiplicity Q s x y := by + have hdvd_sub : (X - C x) ∣ (R - C y) := by + rw [Polynomial.dvd_iff_isRoot] + simp [Polynomial.IsRoot, hRx] + have hdvd_sub' : (X - C x) ∣ (C y - R) := by + have hneg := dvd_neg.mpr hdvd_sub + rwa [neg_sub] at hneg + have hfamilyCy : (∀ b, b < s → (X - C x) ^ (s - b) ∣ + (Polynomial.hasseDeriv b Q.toPoly).eval (C y)) ↔ + CBivariate.hasMultiplicity Q s x y := by + rw [CBivariate.hasMultiplicity_iff_hasMultiplicityAtLeast] + unfold CBivariate.HasMultiplicityAtLeast + constructor + · intro h a b hab + have hb : b < s := by omega + have hz := (X_sub_C_pow_dvd_iff_hasseDeriv_eval_eq_zero _ x (s - b)).1 + (h b hb) a (by omega) + rwa [CBivariate.eval_hasseDeriv_eval_hasseDeriv_toPoly] at hz + · intro h b hb + rw [X_sub_C_pow_dvd_iff_hasseDeriv_eval_eq_zero] + intro a ha + rw [CBivariate.eval_hasseDeriv_eval_hasseDeriv_toPoly] + exact h a b (by omega) + constructor + · intro h + refine hfamilyCy.1 fun b hb ↦ ?_ + exact X_sub_C_pow_dvd_hasseDeriv_eval_of_dvd_sub hdvd_sub' h hb + · intro h b hb + exact X_sub_C_pow_dvd_hasseDeriv_eval_of_dvd_sub hdvd_sub + (hfamilyCy.2 h) hb + +omit [Nontrivial F] [DecidableEq F] in +/-- The linear factor of one node under `toPoly`. -/ +theorem linearFactor_toPoly_eq (x : F) : + (CPolynomial.linearFactor x).toPoly = (X - C x : Polynomial F) := by + rw [CPolynomial.linearFactor, CPolynomial.toPoly_add, CPolynomial.X_toPoly, + CPolynomial.C_toPoly] + simp [sub_eq_add_neg, add_comm] + +omit [Nontrivial F] [DecidableEq F] in +private theorem vanishingPolynomialArray_toPoly_list + (xs : List F) (acc : CPolynomial F) : + (xs.foldl (fun acc x ↦ acc * CPolynomial.linearFactor x) acc).toPoly = + acc.toPoly * (xs.map fun x ↦ (X - C x : Polynomial F)).prod := by + induction xs generalizing acc with + | nil => + simp + | cons x xs ih => + rw [List.foldl_cons, ih, CPolynomial.toPoly_mul, linearFactor_toPoly_eq] + simp only [List.map_cons, List.prod_cons] + ring + +omit [DecidableEq F] in +/-- The array vanishing polynomial is the product of the node linear factors +under `toPoly`. -/ +theorem vanishingPolynomialArray_toPoly (xs : Array F) : + (CPolynomial.vanishingPolynomialArray xs).toPoly = + (((xs.toList : Multiset F).map fun x ↦ + (X - C x : Polynomial F))).prod := by + rw [CPolynomial.vanishingPolynomialArray] + have hlist := vanishingPolynomialArray_toPoly_list xs.toList (1 : CPolynomial F) + simpa [CPolynomial.toPoly_one, Multiset.map_coe, Multiset.prod_coe] using hlist + +omit [DecidableEq F] in +/-- The array vanishing polynomial is monic over the underlying `toPoly` +image. -/ +theorem vanishingPolynomialArray_toPoly_monic (xs : Array F) : + Polynomial.Monic (CPolynomial.vanishingPolynomialArray xs).toPoly := by + rw [vanishingPolynomialArray_toPoly] + exact Polynomial.monic_multiset_prod_of_monic _ _ + fun x _hx ↦ Polynomial.monic_X_sub_C x + +omit [BEq F] [LawfulBEq F] [Nontrivial F] in +/-- Per-node `(X - x)^k` divisibility glues to divisibility by the `k`-th +power of the product of distinct linear factors. -/ +theorem prod_X_sub_C_pow_dvd_of_nodup + {A : Polynomial F} {xs : List F} {k : Nat} + (hA : A ≠ 0) (hnodup : xs.Nodup) + (hroot : ∀ x, x ∈ xs → (X - C x) ^ k ∣ A) : + (((xs : Multiset F).map fun x ↦ (X - C x : Polynomial F)).prod) ^ k ∣ A := by + let ms : Multiset F := xs + have hmsNodup : ms.Nodup := by + simpa [ms] using hnodup + have hle : k • ms ≤ A.roots := by + rw [Multiset.le_iff_count] + intro x + rw [Multiset.count_nsmul] + by_cases hx : x ∈ ms + · have hxCount : Multiset.count x ms = 1 := + Multiset.count_eq_one_of_mem hmsNodup hx + rw [hxCount, mul_one] + have hdiv : (X - C x) ^ k ∣ A := hroot x (by simpa [ms] using hx) + rw [Polynomial.count_roots] + exact (Polynomial.le_rootMultiplicity_iff hA).2 hdiv + · rw [Multiset.count_eq_zero_of_notMem hx, mul_zero] + exact Nat.zero_le _ + have hprod : + (((k • ms).map fun x ↦ (X - C x : Polynomial F)).prod) ∣ A := + (Multiset.prod_X_sub_C_dvd_iff_le_roots hA (k • ms)).2 hle + rw [Multiset.map_nsmul, Multiset.prod_nsmul] at hprod + simpa [ms] using hprod + +/-- Batch shear equivalence: over distinct nodes, divisibility of every +sheared coefficient `C_b` by `G^(s-b)` is equivalent to the packed GS +multiplicity constraints. -/ +theorem vanishing_pow_dvd_hasseDeriv_eval_iff_satisfiesMultiplicityConstraints + {points : Array (F × F)} {Q : CBivariate F} {R : CPolynomial F} {s : Nat} + (hdistinct : DistinctXCoordinates points) + (hR : ∀ point, point ∈ points.toList → CPolynomial.eval point.1 R = point.2) : + (∀ b, b < s → + (CPolynomial.vanishingPolynomialArray (points.map fun p ↦ p.1)).toPoly ^ (s - b) ∣ + (Polynomial.hasseDeriv b Q.toPoly).eval R.toPoly) ↔ + CBivariate.SatisfiesMultiplicityConstraints Q points s := by + have hnodup : (points.map fun p ↦ p.1).toList.Nodup := by + simpa [DistinctXCoordinates, Array.toList_map] using hdistinct + have hRp : ∀ point, point ∈ points.toList → + Polynomial.eval point.1 R.toPoly = point.2 := by + intro point hpoint + rw [← CPolynomial.eval_toPoly] + exact hR point hpoint + rw [CBivariate.satisfiesMultiplicityConstraints_iff_hasMultiplicity] + constructor + · intro h point hpoint + refine (X_sub_C_pow_dvd_hasseDeriv_eval_iff_hasMultiplicity + (hRp point hpoint)).1 ?_ + intro b hb + refine dvd_trans (pow_dvd_pow_of_dvd ?_ (s - b)) (h b hb) + rw [vanishingPolynomialArray_toPoly] + refine Multiset.dvd_prod ?_ + rw [Multiset.mem_map] + refine ⟨point.1, ?_, rfl⟩ + rw [Multiset.mem_coe, Array.toList_map] + exact List.mem_map.mpr ⟨point, hpoint, rfl⟩ + · intro h b hb + by_cases hzero : (Polynomial.hasseDeriv b Q.toPoly).eval R.toPoly = 0 + · simp [hzero] + · rw [vanishingPolynomialArray_toPoly] + refine prod_X_sub_C_pow_dvd_of_nodup hzero hnodup ?_ + intro x hx + rw [Array.toList_map] at hx + rcases List.mem_map.mp hx with ⟨point, hpoint, rfl⟩ + exact (X_sub_C_pow_dvd_hasseDeriv_eval_iff_hasMultiplicity + (hRp point hpoint)).2 (h point hpoint) b hb + +end ApproximantBasis + +end GuruswamiSudan + +end CompPoly diff --git a/CompPoly/Bivariate/GuruswamiSudan/PolynomialCorrectness.lean b/CompPoly/Bivariate/GuruswamiSudan/PolynomialCorrectness.lean index 848d0c9e..1bd8dc1b 100644 --- a/CompPoly/Bivariate/GuruswamiSudan/PolynomialCorrectness.lean +++ b/CompPoly/Bivariate/GuruswamiSudan/PolynomialCorrectness.lean @@ -920,7 +920,7 @@ private theorem toPoly_hasseDerivative_eq_coeffwise_hasseDeriv_hasseDeriv {F : T /-- 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*} +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 diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/Correctness.lean b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/Correctness.lean index a77072cb..cb157c30 100644 --- a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/Correctness.lean +++ b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/Correctness.lean @@ -37,12 +37,23 @@ developed. -/ theorem modularSolutionBasis_complete_minimal (ctx : ModularSolutionBasisContext F) (equation : ModularEquation F) (shift : Array Nat) {row : PolynomialRow F} + (hmonic : ∀ b, b < equation.moduli.size → (equation.moduli.getD b 0).monic) + (hcols : equation.moduli.size ≤ MatrixWidth equation.matrix) + (hshift : shift.size = equation.solutionWidth) (hrow : rowSatisfiesModularBool ctx.mulContext ctx.modContext row - equation.matrix equation.moduli = true) : - ∃ basisRow, - basisRow ∈ MatrixRows (ctx.solutionBasis equation shift) := - ctx.complete_minimal equation shift row hrow + equation.matrix equation.moduli = true) + (hnonzero : rowIsZero row = false) + (hwidth : row.size ≤ equation.solutionWidth) : + (∀ basisRow, + basisRow ∈ MatrixRows (ctx.solutionBasis equation shift) → + basisRow.size ≤ equation.solutionWidth) ∧ + ∃ basisRow degree, + basisRow ∈ MatrixRows (ctx.solutionBasis equation shift) ∧ + rowShiftedDegree? basisRow shift = some degree ∧ + ∀ rowDegree, rowShiftedDegree? row shift = some rowDegree → + degree ≤ rowDegree := + ctx.complete_minimal equation shift row hmonic hcols hshift hrow hnonzero hwidth end Approximant diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation.lean b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation.lean index b974dedb..f557d825 100644 --- a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation.lean +++ b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation.lean @@ -4,36 +4,14 @@ Released under Apache 2.0 license as described in the file LICENSE. Authors: Valerii Huhnin -/ -import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant.PartialLinearization +import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant.ModularEquation.Completeness /-! -# Diagonal Modular Equations +# Diagonal Modular Equation Solver Context -GS-independent solution-basis interface for systems -`p * matrix = 0 mod diag(moduli)`. - -The production solver follows the degree-first pattern: it solves chunked -exact-nullspace X-adic problems whose partial-linearization windows are grown -adaptively per principal coordinate, doubling only the windows of coordinates -whose shifted pivot degree has not been discovered yet. Because a window stops -growing once its coordinate's pivot is found, the total window mass stays -within a constant factor of the true pivot-degree mass, which is at most the -modulus degree mass `sigma`. Every round is therefore an X-adic problem with -`O(m)` chunk rows and total order `O(sigma)`, and the number of rounds is -logarithmic, preserving the `~O(m^(omega-1) * sigma)` solver target. - -The chunked X-adic problems are relaxations: their orders certify exactness -only for rows whose chunk coefficients stay below the chunk size, and the -relaxed kernel module also contains uncertified rows. When such rows have -lower shifted degrees than every exact solution, the minimal basis consists -entirely of uncertified rows, no round discovers a pivot, and escalation can -never make progress. The solver detects this regime (one round with no -filtered row and an empty profile) and switches to the certified full-window -fallback `fullWindowSolutionBasisViaPMBasis`: an unchunked exact-nullspace -problem on `m + s` rows whose orders cover the whole pivot window, so no -uncertified row can sit below an in-window solution. Its order mass is larger -by a factor of the module width, but on the original `m + s` rows this stays -far below an escalated chunked round. +Umbrella module for the diagonal modular-equation solver: definitions, +soundness, the completeness development, and the production +`ModularSolutionBasisContext` instance backed by the X-adic PM-basis. -/ namespace CompPoly @@ -44,500 +22,6 @@ namespace Approximant variable {F : Type*} [Field F] [BEq F] [LawfulBEq F] -/-- Diagonal modular-equation data. Rows of `matrix` are solution coordinates; -columns are reduced independently by `moduli`. -/ -structure ModularEquation (F : Type*) [Zero F] where - moduli : Array (CPolynomial F) - matrix : PolynomialMatrix F - -/-- Number of principal solution coordinates. -/ -def ModularEquation.solutionWidth [Zero F] (equation : ModularEquation F) : Nat := - equation.matrix.size - -/-- Number of modular columns. -/ -def ModularEquation.modularWidth [Zero F] (equation : ModularEquation F) : Nat := - equation.moduli.size - -/-- Diagonal rows `-diag(moduli)` for the exact-nullspace lift. -/ -def negativeDiagonalRows (moduli : Array (CPolynomial F)) : PolynomialMatrix F := - ofFn moduli.size moduli.size fun i j ↦ - if i == j then -moduli.getD i 0 else 0 - -/-- Exact-nullspace lift `[F; -diag(M)]`. -/ -def exactNullspaceLift (equation : ModularEquation F) : PolynomialMatrix F := - equation.matrix ++ negativeDiagonalRows equation.moduli - -/-- Principal solution rows: keep the first `solutionWidth` entries of each -expanded nullspace row. -/ -def principalSolutionRows (solutionWidth : Nat) (basis : PolynomialMatrix F) : - PolynomialMatrix F := - basis.map fun row ↦ - (List.range solutionWidth).map (fun j ↦ rowGet row j) |>.toArray - -/-- Build the X-adic exact-nullspace problem used by the modular-equation -solver. -/ -def exactNullspaceProblem (equation : ModularEquation F) : XAdicProblem F := - { orders := linearizedOrders equation.solutionWidth equation.moduli - matrix := exactNullspaceLift equation } - -/-- Entry-aware X-adic orders for a chunked exact-nullspace lift. A balanced -in-window solution has chunk coefficients of degree below `delta`, and the -chunked principal entries of column `b` are reduced below `deg M_b`, so -`delta + maxEntryDeg + 1` low coefficients certify that its principal product -vanishes exactly. This is never larger than the generic -`deg M_b + delta + 1` order and is much smaller when the relation entries have -low degree. -/ -def chunkedLiftOrders (delta modularWidth : Nat) - (chunkedPrincipal : PolynomialMatrix F) : Array Nat := - (List.range modularWidth).map - (fun b ↦ - let maxEntryDegree := chunkedPrincipal.foldl - (fun acc row ↦ - let entry := rowGet row b - if entry == 0 then acc else max acc entry.natDegree) - 0 - delta + maxEntryDegree + 1) |>.toArray - -/-- Principal rows for the chunked exact-nullspace lift. Chunk row -`(coord, offset)` stores `X^offset` times the corresponding original relation -row, reduced columnwise by the diagonal moduli. Reducing keeps every entry of -column `b` below `deg M_b`, matching the `E * F mod M` expansion from the -design notes and keeping chunked entry degrees independent of the offsets. -/ -def chunkedPrincipalRows (modCtx : CPolynomial.ModContext F) - (equation : ModularEquation F) - (plan : PartialLinearizationPlan) : PolynomialMatrix F := - ofFn plan.chunks.size (MatrixWidth equation.matrix) fun i j ↦ - let chunk := plan.chunks.getD i { coord := 0, offset := 0 } - modByMonicWith modCtx - (shiftPolynomialX chunk.offset - (rowGet (equation.matrix.getD chunk.coord #[]) j)) - (equation.moduli.getD j 0) - -/-- Chunked exact-nullspace lift for a partial-linearization plan. -/ -def chunkedExactNullspaceLift (modCtx : CPolynomial.ModContext F) - (equation : ModularEquation F) - (plan : PartialLinearizationPlan) : PolynomialMatrix F := - chunkedPrincipalRows modCtx equation plan ++ negativeDiagonalRows equation.moduli - -/-- Build the X-adic exact-nullspace problem after principal-coordinate chunk -expansion, with generic partial-linearization orders. -/ -def chunkedExactNullspaceProblem (modCtx : CPolynomial.ModContext F) - (equation : ModularEquation F) - (plan : PartialLinearizationPlan) : XAdicProblem F := - { orders := linearizedOrders equation.solutionWidth equation.moduli - matrix := chunkedExactNullspaceLift modCtx equation plan } - -/-- Build the chunked X-adic exact-nullspace problem with entry-aware orders. -The `shift` argument is kept for call-site symmetry; orders depend only on the -chunked entry degrees and the chunk size. -/ -def chunkedExactNullspaceProblemForShift (modCtx : CPolynomial.ModContext F) - (equation : ModularEquation F) - (plan : PartialLinearizationPlan) (_shift : Array Nat) : XAdicProblem F := - let principal := chunkedPrincipalRows modCtx equation plan - { orders := chunkedLiftOrders plan.delta equation.modularWidth principal - matrix := principal ++ negativeDiagonalRows equation.moduli } - -/-- Shifted pivot-degree profile discovered for the principal solution -coordinates. `none` means that the discovery pass did not see a row pivoting in -that coordinate, so partial linearization uses its conservative fallback. -/ -structure PivotDegreeProfile where - degrees : Array (Option Nat) -deriving Repr, BEq - -/-- Empty shifted pivot-degree profile for a fixed principal width. -/ -def emptyPivotDegreeProfile (solutionWidth : Nat) : PivotDegreeProfile := - { degrees := Array.replicate solutionWidth none } - -/-- Insert a discovered pivot degree, keeping the smallest degree for each -principal leading position. -/ -def PivotDegreeProfile.insert (profile : PivotDegreeProfile) - (position degree : Nat) : PivotDegreeProfile := - let current := profile.degrees.getD position none - let next := - match current with - | none => degree - | some old => min old degree - { degrees := profile.degrees.setIfInBounds position (some next) } - -/-- Whether the profile has discovered a pivot degree for every coordinate. -/ -def PivotDegreeProfile.coversAll (profile : PivotDegreeProfile) : Bool := - profile.degrees.all fun degree ↦ degree.isSome - -/-- Whether the profile has discovered a pivot degree for any coordinate. -/ -def PivotDegreeProfile.discoveredAny (profile : PivotDegreeProfile) : Bool := - profile.degrees.any fun degree ↦ degree.isSome - -/-- Merge the principal pivot degrees observed in `rows` into a profile. -/ -def pivotDegreeProfileMergeRows (profile : PivotDegreeProfile) - (solutionWidth : Nat) (rows : PolynomialMatrix F) (shift : Array Nat) : - PivotDegreeProfile := - (List.range rows.size).foldl - (fun profile i ↦ - let row := rows.getD i #[] - match rowShiftedLeadingPosition? row shift, rowShiftedDegree? row shift with - | some position, some degree => - if position < solutionWidth then - profile.insert position degree - else - profile - | _, _ => profile) - profile - -/-- Discover principal pivot degrees from compressed candidate rows. -/ -def pivotDegreeProfileFromRows (solutionWidth : Nat) - (rows : PolynomialMatrix F) (shift : Array Nat) : - PivotDegreeProfile := - pivotDegreeProfileMergeRows (emptyPivotDegreeProfile solutionWidth) - solutionWidth rows shift - -/-- Solve a diagonal modular equation with a supplied partial-linearization plan. -/ -def solutionBasisWithPlanViaPMBasis (modCtx : CPolynomial.ModContext F) - (pmCtx : PMBasisContext F) - (equation : ModularEquation F) (shift : Array Nat) - (plan : PartialLinearizationPlan) : PolynomialMatrix F := - let xadic := chunkedExactNullspaceProblemForShift modCtx equation plan shift - let expandedShift := chunkedExactNullspaceShift plan shift - compactNonzeroRows (compressChunkedPrincipalRows plan (pmCtx.basis xadic expandedShift)) - -/-- Solve once with the conservative shift-spread window. This is a debug -entry point only: its chunk count can grow quadratically in the module width -for spread-out shifts, so the production solver uses the adaptive -window-escalation loop instead. -/ -def solutionBasisViaPMBasis (modCtx : CPolynomial.ModContext F) - (pmCtx : PMBasisContext F) - (equation : ModularEquation F) (shift : Array Nat) : PolynomialMatrix F := - let plan := partialLinearizationPlan equation.solutionWidth equation.modularWidth - equation.moduli shift - solutionBasisWithPlanViaPMBasis modCtx pmCtx equation shift plan - -/-- Known-degree reconstruction pass: build the partial-linearization plan from -the discovered pivot degrees and solve the X-adic problem directly for that -profile. -/ -def knownDegreeSolutionBasisViaPMBasis (modCtx : CPolynomial.ModContext F) - (pmCtx : PMBasisContext F) - (equation : ModularEquation F) (shift : Array Nat) - (profile : PivotDegreeProfile) : PolynomialMatrix F := - let plan := partialLinearizationPlanFromPivotDegrees equation.solutionWidth - equation.modularWidth equation.moduli shift profile.degrees - solutionBasisWithPlanViaPMBasis modCtx pmCtx equation shift plan - -/-- Solve once without principal-coordinate chunking. This is an explicit -tiny-leaf/debug entry point; the default modular-equation context does not use -it as a production fallback. -/ -def unchunkedSolutionBasisViaPMBasis (modCtx : CPolynomial.ModContext F) - (pmCtx : PMBasisContext F) - (equation : ModularEquation F) (shift : Array Nat) : PolynomialMatrix F := - let plan := unchunkedPartialLinearizationPlan equation.solutionWidth - equation.modularWidth equation.moduli - solutionBasisWithPlanViaPMBasis modCtx pmCtx equation shift plan - -/-- Keep only rows that satisfy the original diagonal modular equation after -compression from the exact-nullspace / X-adic bridge. -/ -def filterModularSolutionRows - (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) - (equation : ModularEquation F) (rows : PolynomialMatrix F) : - PolynomialMatrix F := - rows.filter fun row ↦ - if rowIsZero row then - false - else - rowSatisfiesModularBool mulCtx modCtx row equation.matrix equation.moduli - -/-- Residual rows `B * F mod diag(M)` for candidate solution rows `B`. -/ -def modularResidualRows - (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) - (equation : ModularEquation F) (rows : PolynomialMatrix F) : - PolynomialMatrix F := - modDiagonalWith modCtx equation.moduli - (mulWith mulCtx rows equation.matrix) - -/-- Shift used for polynomial combinations of compressed candidate rows. The -degree of a coefficient multiplying row `i` is measured relative to the shifted -degree already carried by that candidate row. -/ -def candidateRowShift (rows : PolynomialMatrix F) (shift : Array Nat) : Array Nat := - updateShiftByRows rows shift - -/-- Upper bound for the adaptive search window above each coordinate's shift. -The row `e_j * lcm(moduli)` is always a solution and `deg lcm(moduli)` is at -most the modulus degree mass, so every coordinate's minimal shifted pivot -degree is within this window. -/ -def pivotWindowCap [Zero F] (equation : ModularEquation F) : Nat := - modulusDegreeMass equation.moduli - -/-- Coefficient-degree bound for the full-window fallback problem. Any row of -the lifted module whose shifted degree does not exceed the shifted degree of an -in-window solution has plain coefficient degrees at most -`pivotWindowCap + maxShiftDegree shift`: comparing two principal coordinates -costs at most the shift spread, and `pivotWindowCap` bounds every minimal -pivot degree. -/ -def fullWindowDegreeBound [Zero F] (equation : ModularEquation F) - (shift : Array Nat) : Nat := - pivotWindowCap equation + maxShiftDegree shift - -/-- Exact-nullspace lift with the relation entries reduced columnwise by the -moduli, so every principal entry of column `b` has degree below `deg M_b` and -the quotient coefficients of exact solutions stay below the solution degree. -/ -def reducedExactNullspaceLift (modCtx : CPolynomial.ModContext F) - (equation : ModularEquation F) : PolynomialMatrix F := - ofFn equation.solutionWidth equation.modularWidth - (fun i j ↦ - modByMonicWith modCtx (rowGet (equation.matrix.getD i #[]) j) - (equation.moduli.getD j 0)) ++ - negativeDiagonalRows equation.moduli - -/-- Unchunked exact-nullspace problem with orders certifying exactness across -the whole pivot window: a lifted row with principal coefficient degrees at most -`bound` and quotient coefficient degrees at most `bound + 1` produces column-`b` -products of degree below `deg M_b + bound + 2`, so vanishing to that X-adic -order forces the product to vanish exactly. Unlike the chunked relaxation, -this problem admits no uncertified rows below the in-window solution degrees, -at the cost of an order mass larger by a factor of the module width. -/ -def fullWindowExactNullspaceProblem (modCtx : CPolynomial.ModContext F) - (equation : ModularEquation F) (bound : Nat) : XAdicProblem F := - { orders := equation.moduli.map fun modulus ↦ modulus.natDegree + bound + 2 - matrix := reducedExactNullspaceLift modCtx equation } - -/-- Certified full-window solve: compute a minimal basis of the unchunked -exact-nullspace problem whose orders cover the entire pivot window, and keep -the principal columns. Every returned row that pivots at or below an in-window -solution degree is an exact modular solution, so this entry point cannot lose -the solution basis to uncertified low-degree rows. It is used as the fallback -when the chunked adaptive solver discovers nothing. -/ -def fullWindowSolutionBasisViaPMBasis (modCtx : CPolynomial.ModContext F) - (pmCtx : PMBasisContext F) (equation : ModularEquation F) - (shift : Array Nat) : PolynomialMatrix F := - let bound := fullWindowDegreeBound equation shift - let problem := fullWindowExactNullspaceProblem modCtx equation bound - let expandedShift := exactNullspaceShift shift equation.modularWidth bound - compactNonzeroRows - (principalSolutionRows equation.solutionWidth - (pmCtx.basis problem expandedShift)) - -/-- Pivot-degree assignment for one adaptive round: discovered coordinates use -their observed pivot degrees, undiscovered coordinates use the current -escalation window above their shift entry. -/ -def adaptiveProfileDegrees (shift : Array Nat) (profile : PivotDegreeProfile) - (budgets : Array Nat) (solutionWidth : Nat) : Array (Option Nat) := - (List.range solutionWidth).map - (fun j ↦ - match profile.degrees.getD j none with - | some degree => some degree - | none => some (shift.getD j 0 + budgets.getD j 0)) |>.toArray - -/-- Least shifted degree among accumulated solution rows. -/ -def leastSolutionRowDegree? (rows : PolynomialMatrix F) (shift : Array Nat) : - Option Nat := - (leastShiftedDegreeChoice? rows shift).map fun choice ↦ choice.degree - -/-- A coordinate needs no wider search window once its pivot degree is -discovered, its window has reached the cap, or its window already covers every -degree below the best solution row found so far. The last rule is what keeps -the loop from growing windows for coordinates that cannot improve the answer: -a row pivoting at `j` with shifted degree below the current best would lie -inside the already-searched window. Improving on `best` needs a row of degree -at most `best - 1`, so the window `shift[j] + budget[j]` suffices once -`best <= shift[j] + budget[j] + 1`. -/ -def coordinateSettled (profile : PivotDegreeProfile) (budgets : Array Nat) - (cap : Nat) (bestDegree? : Option Nat) (shift : Array Nat) (j : Nat) : Bool := - (profile.degrees.getD j none).isSome || - cap ≤ budgets.getD j 0 || - (match bestDegree? with - | some best => best ≤ shift.getD j 0 + budgets.getD j 0 + 1 - | none => false) - -/-- Whether every principal coordinate is settled for the current windows. -/ -def allCoordinatesSettled (profile : PivotDegreeProfile) (budgets : Array Nat) - (cap : Nat) (bestDegree? : Option Nat) (shift : Array Nat) - (solutionWidth : Nat) : Bool := - (List.range solutionWidth).all fun j ↦ - coordinateSettled profile budgets cap bestDegree? shift j - -/-- Double the escalation windows of coordinates that are not settled, clamped -at the window cap. Settled coordinates keep their window so the total window -mass stays within a constant factor of the useful pivot-degree mass. -/ -def escalateUnsettledBudgets (profile : PivotDegreeProfile) - (budgets : Array Nat) (cap : Nat) (bestDegree? : Option Nat) - (shift : Array Nat) : Array Nat := - (List.range budgets.size).map - (fun j ↦ - let budget := budgets.getD j 0 - if coordinateSettled profile budgets cap bestDegree? shift j then - budget - else - min cap (2 * max 1 budget)) |>.toArray - -/-- State carried between adaptive solution-basis rounds. `filtered` -accumulates the exact solution rows found across all rounds. -/ -structure AdaptiveSolveState (F : Type*) [Zero F] where - profile : PivotDegreeProfile - budgets : Array Nat - filtered : PolynomialMatrix F - raw : PolynomialMatrix F - -/-- One adaptive round: solve the chunked exact-nullspace problem for the -current window assignment, keep the rows that satisfy the original diagonal -congruences, and merge the observed shifted pivot degrees into the profile. -/ -def adaptiveSolutionRound - (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) - (pmCtx : PMBasisContext F) (equation : ModularEquation F) - (shift : Array Nat) (state : AdaptiveSolveState F) : AdaptiveSolveState F := - let degrees := adaptiveProfileDegrees shift state.profile state.budgets - equation.solutionWidth - let plan := partialLinearizationPlanFromPivotDegrees equation.solutionWidth - equation.modularWidth equation.moduli shift degrees - let rows := solutionBasisWithPlanViaPMBasis modCtx pmCtx equation shift plan - let filtered := filterModularSolutionRows mulCtx modCtx equation rows - { profile := pivotDegreeProfileMergeRows state.profile equation.solutionWidth - filtered shift - budgets := state.budgets - filtered := state.filtered ++ filtered - raw := rows } - -/-- Fuel-bounded adaptive window-escalation loop. Rounds stop as soon as every -principal coordinate is settled: discovered, saturated, or unable to beat the -best solution row already in hand. -/ -def adaptiveSolutionLoop - (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) - (pmCtx : PMBasisContext F) (equation : ModularEquation F) - (shift : Array Nat) (cap : Nat) : - Nat → AdaptiveSolveState F → AdaptiveSolveState F - | 0, state => state - | fuel + 1, state => - let next := adaptiveSolutionRound mulCtx modCtx pmCtx equation shift state - let bestDegree? := leastSolutionRowDegree? next.filtered shift - if next.filtered.size == 0 && !next.profile.discoveredAny then - -- Zero discovery means the chunked relaxation is dominated by - -- uncertified rows below the solution degrees; growing the windows - -- multiplies the round cost without producing new pivot information, - -- so stop here and let the caller run the certified full-window - -- fallback instead. - next - else if allCoordinatesSettled next.profile next.budgets cap bestDegree? shift - equation.solutionWidth then - next - else - let escalated := escalateUnsettledBudgets next.profile next.budgets cap - bestDegree? shift - if escalated == next.budgets then - next - else - adaptiveSolutionLoop mulCtx modCtx pmCtx equation shift cap fuel - { next with budgets := escalated } - -/-- Run the adaptive degree-first solver: discover the shifted pivot-degree -profile with geometrically growing per-coordinate windows, where the final -round doubles as the known-degree reconstruction for all discovered -coordinates. The initial window is one chunk per coordinate. -/ -def adaptiveSolutionBasis - (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) - (pmCtx : PMBasisContext F) (equation : ModularEquation F) - (shift : Array Nat) : AdaptiveSolveState F := - let delta := chunkDelta equation.solutionWidth equation.moduli - let cap := max delta (pivotWindowCap equation) - let fuel := Nat.log2 (max 1 cap) + 2 - adaptiveSolutionLoop mulCtx modCtx pmCtx equation shift cap fuel - { profile := emptyPivotDegreeProfile equation.solutionWidth - budgets := Array.replicate equation.solutionWidth (max 1 (delta - 1)) - filtered := #[] - raw := #[] } - -/-- Discover the shifted pivot-degree profile through the adaptive solver. -/ -def discoverPivotDegreeProfileViaPMBasis - (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) - (pmCtx : PMBasisContext F) (equation : ModularEquation F) - (shift : Array Nat) : PivotDegreeProfile := - (adaptiveSolutionBasis mulCtx modCtx pmCtx equation shift).profile - -/-- One residual reconstruction pass. If compressed rows `B` are not themselves -exact modular solutions, solve for polynomial combinations `C` such that -`C * (B * F mod M) = 0 mod M`, then return `C * B`. The residual equation is -solved with the same adaptive solver, without a further repair recursion. - -The candidate rows are first reduced to one representative per shifted leading -position. The reduction steps are unimodular, so the generated row module is -unchanged, while the residual equation's solution width stays bounded by the -principal width instead of the raw candidate count; without this bound the -repair solve can be quadratically wider than the original equation. -/ -def repairSolutionRowsViaPMBasis - (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) - (pmCtx : PMBasisContext F) (equation : ModularEquation F) - (shift : Array Nat) (rows : PolynomialMatrix F) : PolynomialMatrix F := - let reduced := compactNonzeroRows (reduceKernelLeafRowsByPivots rows shift) - let residualEquation : ModularEquation F := - { moduli := equation.moduli - matrix := modularResidualRows mulCtx modCtx equation reduced } - let repairState := adaptiveSolutionBasis mulCtx modCtx pmCtx residualEquation - (candidateRowShift reduced shift) - PolynomialMatrix.mulStrassenWith pmCtx.runtime.lowMulContext - pmCtx.runtime.leafCutoff repairState.filtered reduced - -/-- Debug helper for tiny problems that intentionally disables -principal-coordinate chunking. This keeps the old unchunked behavior available -for inspection without letting the production context bypass partial -linearization. -/ -def debugUnchunkedFilteredSolutionBasisViaPMBasis - (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) - (pmCtx : PMBasisContext F) (equation : ModularEquation F) - (shift : Array Nat) : PolynomialMatrix F := - filterModularSolutionRows mulCtx modCtx equation - (unchunkedSolutionBasisViaPMBasis modCtx pmCtx equation shift) - -/-- Known-degree reconstruction followed by the original diagonal-equation -guard. The reconstruction plan is built from discovered shifted pivot degrees, -solved as a chunked exact-nullspace PM-basis problem, and compressed back to the -principal solution coordinates. -/ -def knownDegreeFilteredSolutionBasisViaPMBasis - (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) - (pmCtx : PMBasisContext F) (equation : ModularEquation F) - (shift : Array Nat) (profile : PivotDegreeProfile) : PolynomialMatrix F := - let rows := knownDegreeSolutionBasisViaPMBasis modCtx pmCtx equation shift profile - let filtered := filterModularSolutionRows mulCtx modCtx equation rows - if filtered.size == 0 then - filterModularSolutionRows mulCtx modCtx equation - (repairSolutionRowsViaPMBasis mulCtx modCtx pmCtx equation shift rows) - else - filtered - -/-- Solver exposed through the modular-equation context: run the adaptive -degree-first window-escalation loop, whose final round is the known-degree -reconstruction for every discovered coordinate, and discard any row that fails -the original diagonal congruences. When the chunked loop produces no exact -row — the regime where uncertified low-degree rows of the relaxed X-adic -module crowd out every solution — fall back to the certified full-window -unchunked solve, and only then to the residual repair pass. The filter, -fallback, and repair are semantic guards around the exact-nullspace bridge; -they do not call any alternate interpolation backend. -/ -def filteredSolutionBasisViaPMBasis - (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) - (pmCtx : PMBasisContext F) (equation : ModularEquation F) - (shift : Array Nat) : PolynomialMatrix F := - let final := adaptiveSolutionBasis mulCtx modCtx pmCtx equation shift - if final.filtered.size == 0 then - let fallback := filterModularSolutionRows mulCtx modCtx equation - (fullWindowSolutionBasisViaPMBasis modCtx pmCtx equation shift) - if fallback.size == 0 then - filterModularSolutionRows mulCtx modCtx equation - (repairSolutionRowsViaPMBasis mulCtx modCtx pmCtx equation shift final.raw) - else - fallback - else - final.filtered - -/-- Modular-equation solution-basis context with theorem fields. -/ -structure ModularSolutionBasisContext (F : Type*) [Field F] [BEq F] [LawfulBEq F] where - mulContext : CPolynomial.MulContext F - modContext : CPolynomial.ModContext F - solutionBasis : ModularEquation F → Array Nat → PolynomialMatrix F - sound : - ∀ equation shift row, - row ∈ MatrixRows (solutionBasis equation shift) → - rowSatisfiesModularBool mulContext modContext row equation.matrix equation.moduli = true - complete_minimal : - ∀ equation shift row, - rowSatisfiesModularBool mulContext modContext row equation.matrix equation.moduli = true → - ∃ basisRow, - basisRow ∈ MatrixRows (solutionBasis equation shift) - /-- Diagonal modular-equation solution-basis context obtained from the exact-nullspace lift and an X-adic PM-basis context. -/ def modularSolutionBasisContextViaPMBasis @@ -547,9 +31,145 @@ def modularSolutionBasisContextViaPMBasis modContext := modCtx solutionBasis := filteredSolutionBasisViaPMBasis mulCtx modCtx pmCtx sound := by - sorry + intro equation shift row hrow + exact filteredSolutionBasisViaPMBasis_sound hrow complete_minimal := by - sorry + -- Certified verification window: solutions at or above the best adaptive + -- degree are dominated by the adaptive rows of `combined`; solutions + -- strictly below it (or with no adaptive row at all) are dominated by a + -- verification row through `me_verification_dominates`, with the fallback + -- solution `e_p * prod(moduli)` covering degrees beyond the saturated + -- window. + intro equation shift row hmonic hcols hshift hsat hnz hwidth + classical + obtain ⟨d, hd⟩ := me_rowShiftedDegree_isSome (shift := shift) hnz + obtain ⟨j0, hj0, hj0ne⟩ := exists_nonzero_entry_of_rowIsZero_false hnz + have hpos : 0 < equation.solutionWidth := by omega + -- Main existence in the combined candidate set. + have hmain : ∃ basisRow degree, + basisRow ∈ MatrixRows + ((adaptiveSolutionBasis mulCtx modCtx pmCtx equation shift).filtered ++ + filterModularSolutionRows mulCtx modCtx equation + (verificationSolutionBasisViaPMBasis modCtx pmCtx equation shift + (leastSolutionRowDegree? + (adaptiveSolutionBasis mulCtx modCtx pmCtx equation + shift).filtered shift))) ∧ + rowShiftedDegree? basisRow shift = some degree ∧ degree ≤ d := by + by_cases hcase : ∃ B, leastSolutionRowDegree? + (adaptiveSolutionBasis mulCtx modCtx pmCtx equation shift).filtered + shift = some B ∧ B ≤ d + · -- An adaptive row already dominates. + obtain ⟨B, hB, hBd⟩ := hcase + rw [leastSolutionRowDegree?] at hB + rcases Option.map_eq_some_iff.mp hB with ⟨choice, hchoice, hchoicedeg⟩ + obtain ⟨hidx, hrowEq, hcdeg⟩ := leastShiftedDegreeChoice?_some_valid hchoice + refine ⟨choice.row, choice.degree, ?_, hcdeg, by omega⟩ + rw [MatrixRows, Array.toList_append] + refine List.mem_append.mpr (Or.inl ?_) + rw [hrowEq] + exact me_getD_mem_toList #[] hidx + · -- Route through the certified verification window. + push Not at hcase + have hwindow : ∃ (rowStar : PolynomialRow F) (e : Nat), + rowSatisfiesModularBool mulCtx modCtx rowStar equation.matrix + equation.moduli = true ∧ + rowIsZero rowStar = false ∧ + rowStar.size ≤ equation.solutionWidth ∧ + rowShiftedDegree? rowStar shift = some e ∧ + e ≤ verificationWindowBound equation shift + (leastSolutionRowDegree? + (adaptiveSolutionBasis mulCtx modCtx pmCtx equation + shift).filtered shift) ∧ + e ≤ d := by + by_cases hdbound : d ≤ verificationWindowBound equation shift + (leastSolutionRowDegree? + (adaptiveSolutionBasis mulCtx modCtx pmCtx equation + shift).filtered shift) + · exact ⟨row, d, hsat, hnz, hwidth, hd, hdbound, le_refl d⟩ + · -- The window is saturated at the full pivot window. + have hfull : verificationWindowBound equation shift + (leastSolutionRowDegree? + (adaptiveSolutionBasis mulCtx modCtx pmCtx equation + shift).filtered shift) = + pivotWindowCap equation + maxShiftDegree shift := by + cases hbest : leastSolutionRowDegree? + (adaptiveSolutionBasis mulCtx modCtx pmCtx equation + shift).filtered shift with + | none => + simp [verificationWindowBound, fullWindowDegreeBound] + | some B => + have hdB : d < B := hcase B hbest + rw [hbest] at hdbound + simp only [verificationWindowBound] at hdbound ⊢ + omega + obtain ⟨prow, e, hsatP, hnzP, hsizeP, hdegP, heP⟩ := + me_prodRow_facts mulCtx modCtx equation shift (p := j0) hmonic + hcols (by omega) + exact ⟨prow, e, hsatP, hnzP, le_of_eq hsizeP, hdegP, by omega, + by omega⟩ + obtain ⟨rowStar, e, hsatS, hnzS, hwidthS, hdegS, heB, heD⟩ := hwindow + obtain ⟨bRow, degB, hmem, hdegB, hle⟩ := me_verification_dominates + mulCtx modCtx pmCtx equation shift + (verificationWindowBound equation shift + (leastSolutionRowDegree? + (adaptiveSolutionBasis mulCtx modCtx pmCtx equation + shift).filtered shift)) + hmonic hcols hshift hpos hsatS hnzS hwidthS hdegS heB + refine ⟨bRow, degB, ?_, hdegB, by omega⟩ + rw [MatrixRows, Array.toList_append] + refine List.mem_append.mpr (Or.inr ?_) + exact hmem + obtain ⟨bRow, degB, hmemC, hdegB, hled⟩ := hmain + -- The combined candidate set is nonempty, so the repair branch is skipped. + have hsizepos : 0 < + ((adaptiveSolutionBasis mulCtx modCtx pmCtx equation shift).filtered ++ + filterModularSolutionRows mulCtx modCtx equation + (verificationSolutionBasisViaPMBasis modCtx pmCtx equation shift + (leastSolutionRowDegree? + (adaptiveSolutionBasis mulCtx modCtx pmCtx equation + shift).filtered shift))).size := by + rcases List.getElem_of_mem hmemC with ⟨i, hi, _⟩ + rw [MatrixRows, Array.length_toList] at hi + omega + have hresult : filteredSolutionBasisViaPMBasis mulCtx modCtx pmCtx equation + shift = + (adaptiveSolutionBasis mulCtx modCtx pmCtx equation shift).filtered ++ + filterModularSolutionRows mulCtx modCtx equation + (verificationSolutionBasisViaPMBasis modCtx pmCtx equation shift + (leastSolutionRowDegree? + (adaptiveSolutionBasis mulCtx modCtx pmCtx equation + shift).filtered shift)) := by + simp only [filteredSolutionBasisViaPMBasis] + rw [if_neg (by simp only [beq_iff_eq]; omega)] + constructor + · -- Width discipline of the returned rows. + intro basisRow hbasisRow + rw [hresult, MatrixRows, Array.toList_append, List.mem_append] at hbasisRow + rcases hbasisRow with h | h + · exact le_of_eq (me_adaptiveBasis_width basisRow h) + · have hsub := me_filterModularSolutionRows_subset h + have hsub' : basisRow ∈ MatrixRows (compactNonzeroRows + (principalSolutionRows equation.solutionWidth + (pmCtx.basis + (fullWindowExactNullspaceProblem modCtx equation + (verificationWindowBound equation shift + (leastSolutionRowDegree? + (adaptiveSolutionBasis mulCtx modCtx pmCtx equation + shift).filtered shift))) + (exactNullspaceShift shift equation.modularWidth + (verificationWindowBound equation shift + (leastSolutionRowDegree? + (adaptiveSolutionBasis mulCtx modCtx pmCtx equation + shift).filtered shift)))))) := hsub + exact le_of_eq + (me_principalSolutionRows_width (compactNonzeroRows_subset hsub')) + · refine ⟨bRow, degB, ?_, hdegB, ?_⟩ + · rw [hresult] + exact hmemC + · intro rowDegree hrowDegree + rw [hd] at hrowDegree + have hEq := Option.some.inj hrowDegree + omega end Approximant diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation/Basic.lean b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation/Basic.lean new file mode 100644 index 00000000..a2425237 --- /dev/null +++ b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation/Basic.lean @@ -0,0 +1,687 @@ +/- +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.LinearAlgebra.PolynomialMatrix.Approximant.PartialLinearization +import CompPoly.LinearAlgebra.PolynomialMatrix.RowSelection + +/-! +# Diagonal Modular Equations + +GS-independent solution-basis interface for systems +`p * matrix = 0 mod diag(moduli)`. + +The production solver follows the degree-first pattern: it solves chunked +exact-nullspace X-adic problems whose partial-linearization windows are grown +adaptively per principal coordinate, doubling only the windows of coordinates +whose shifted pivot degree has not been discovered yet. Because a window stops +growing once its coordinate's pivot is found, the total window mass stays +within a constant factor of the true pivot-degree mass, which is at most the +modulus degree mass `sigma`. Every round is therefore an X-adic problem with +`O(m)` chunk rows and total order `O(sigma)`, and the number of rounds is +logarithmic, preserving the `~O(m^(omega-1) * sigma)` solver target. + +The chunked X-adic problems are relaxations: their orders certify exactness +only for rows whose chunk coefficients stay below the chunk size, and the +relaxed kernel module also contains uncertified rows that can crowd exact +solutions out of the adaptive rounds. The solver therefore always runs a +certified verification solve over the window shrunk to the best exact degree +already found (`verificationSolutionBasisViaPMBasis`) and returns the union: +solutions at or above the adaptive best are dominated by the adaptive rows, +and solutions below it lie inside the certified window, whose orders rule out +uncertified rows. +-/ + +namespace CompPoly + +namespace PolynomialMatrix + +namespace Approximant + +variable {F : Type*} [Field F] [BEq F] [LawfulBEq F] + +/-- Diagonal modular-equation data. Rows of `matrix` are solution coordinates; +columns are reduced independently by `moduli`. -/ +structure ModularEquation (F : Type*) [Zero F] where + moduli : Array (CPolynomial F) + matrix : PolynomialMatrix F + +/-- Number of principal solution coordinates. -/ +def ModularEquation.solutionWidth [Zero F] (equation : ModularEquation F) : Nat := + equation.matrix.size + +/-- Number of modular columns. -/ +def ModularEquation.modularWidth [Zero F] (equation : ModularEquation F) : Nat := + equation.moduli.size + +/-- Diagonal rows `-diag(moduli)` for the exact-nullspace lift. -/ +def negativeDiagonalRows (moduli : Array (CPolynomial F)) : PolynomialMatrix F := + ofFn moduli.size moduli.size fun i j ↦ + if i == j then -moduli.getD i 0 else 0 + +/-- Exact-nullspace lift `[F; -diag(M)]`. -/ +def exactNullspaceLift (equation : ModularEquation F) : PolynomialMatrix F := + equation.matrix ++ negativeDiagonalRows equation.moduli + +/-- Principal solution rows: keep the first `solutionWidth` entries of each +expanded nullspace row. -/ +def principalSolutionRows (solutionWidth : Nat) (basis : PolynomialMatrix F) : + PolynomialMatrix F := + basis.map fun row ↦ + (List.range solutionWidth).map (fun j ↦ rowGet row j) |>.toArray + +/-- Build the X-adic exact-nullspace problem used by the modular-equation +solver. -/ +def exactNullspaceProblem (equation : ModularEquation F) : XAdicProblem F := + { orders := linearizedOrders equation.solutionWidth equation.moduli + matrix := exactNullspaceLift equation } + +/-- Entry-aware X-adic orders for a chunked exact-nullspace lift. A balanced +in-window solution has chunk coefficients of degree below `delta`, and the +chunked principal entries of column `b` are reduced below `deg M_b`, so +`delta + maxEntryDeg + 1` low coefficients certify that its principal product +vanishes exactly. This is never larger than the generic +`deg M_b + delta + 1` order and is much smaller when the relation entries have +low degree. -/ +def chunkedLiftOrders (delta modularWidth : Nat) + (chunkedPrincipal : PolynomialMatrix F) : Array Nat := + (List.range modularWidth).map + (fun b ↦ + let maxEntryDegree := chunkedPrincipal.foldl + (fun acc row ↦ + let entry := rowGet row b + if entry == 0 then acc else max acc entry.natDegree) + 0 + delta + maxEntryDegree + 1) |>.toArray + +/-- Principal rows for the chunked exact-nullspace lift. Chunk row +`(coord, offset)` stores `X^offset` times the corresponding original relation +row, reduced columnwise by the diagonal moduli. Reducing keeps every entry of +column `b` below `deg M_b`, matching the `E * F mod M` expansion from the +design notes and keeping chunked entry degrees independent of the offsets. -/ +def chunkedPrincipalRows (modCtx : CPolynomial.ModContext F) + (equation : ModularEquation F) + (plan : PartialLinearizationPlan) : PolynomialMatrix F := + ofFn plan.chunks.size (MatrixWidth equation.matrix) fun i j ↦ + let chunk := plan.chunks.getD i { coord := 0, offset := 0 } + modByMonicWith modCtx + (shiftPolynomialX chunk.offset + (rowGet (equation.matrix.getD chunk.coord #[]) j)) + (equation.moduli.getD j 0) + +/-- Chunked exact-nullspace lift for a partial-linearization plan. -/ +def chunkedExactNullspaceLift (modCtx : CPolynomial.ModContext F) + (equation : ModularEquation F) + (plan : PartialLinearizationPlan) : PolynomialMatrix F := + chunkedPrincipalRows modCtx equation plan ++ negativeDiagonalRows equation.moduli + +/-- Build the X-adic exact-nullspace problem after principal-coordinate chunk +expansion, with generic partial-linearization orders. -/ +def chunkedExactNullspaceProblem (modCtx : CPolynomial.ModContext F) + (equation : ModularEquation F) + (plan : PartialLinearizationPlan) : XAdicProblem F := + { orders := linearizedOrders equation.solutionWidth equation.moduli + matrix := chunkedExactNullspaceLift modCtx equation plan } + +/-- Build the chunked X-adic exact-nullspace problem with entry-aware orders. +The `shift` argument is kept for call-site symmetry; orders depend only on the +chunked entry degrees and the chunk size. -/ +def chunkedExactNullspaceProblemForShift (modCtx : CPolynomial.ModContext F) + (equation : ModularEquation F) + (plan : PartialLinearizationPlan) (_shift : Array Nat) : XAdicProblem F := + let principal := chunkedPrincipalRows modCtx equation plan + { orders := chunkedLiftOrders plan.delta equation.modularWidth principal + matrix := principal ++ negativeDiagonalRows equation.moduli } + +/-- Shifted pivot-degree profile discovered for the principal solution +coordinates. `none` means that the discovery pass did not see a row pivoting in +that coordinate, so partial linearization uses its conservative fallback. -/ +structure PivotDegreeProfile where + degrees : Array (Option Nat) +deriving Repr, BEq + +/-- Empty shifted pivot-degree profile for a fixed principal width. -/ +def emptyPivotDegreeProfile (solutionWidth : Nat) : PivotDegreeProfile := + { degrees := Array.replicate solutionWidth none } + +/-- Insert a discovered pivot degree, keeping the smallest degree for each +principal leading position. -/ +def PivotDegreeProfile.insert (profile : PivotDegreeProfile) + (position degree : Nat) : PivotDegreeProfile := + let current := profile.degrees.getD position none + let next := + match current with + | none => degree + | some old => min old degree + { degrees := profile.degrees.setIfInBounds position (some next) } + +/-- Whether the profile has discovered a pivot degree for every coordinate. -/ +def PivotDegreeProfile.coversAll (profile : PivotDegreeProfile) : Bool := + profile.degrees.all fun degree ↦ degree.isSome + +/-- Whether the profile has discovered a pivot degree for any coordinate. -/ +def PivotDegreeProfile.discoveredAny (profile : PivotDegreeProfile) : Bool := + profile.degrees.any fun degree ↦ degree.isSome + +/-- Merge the principal pivot degrees observed in `rows` into a profile. -/ +def pivotDegreeProfileMergeRows (profile : PivotDegreeProfile) + (solutionWidth : Nat) (rows : PolynomialMatrix F) (shift : Array Nat) : + PivotDegreeProfile := + (List.range rows.size).foldl + (fun profile i ↦ + let row := rows.getD i #[] + match rowShiftedLeadingPosition? row shift, rowShiftedDegree? row shift with + | some position, some degree => + if position < solutionWidth then + profile.insert position degree + else + profile + | _, _ => profile) + profile + +/-- Discover principal pivot degrees from compressed candidate rows. -/ +def pivotDegreeProfileFromRows (solutionWidth : Nat) + (rows : PolynomialMatrix F) (shift : Array Nat) : + PivotDegreeProfile := + pivotDegreeProfileMergeRows (emptyPivotDegreeProfile solutionWidth) + solutionWidth rows shift + +/-- Solve a diagonal modular equation with a supplied partial-linearization plan. -/ +def solutionBasisWithPlanViaPMBasis (modCtx : CPolynomial.ModContext F) + (pmCtx : PMBasisContext F) + (equation : ModularEquation F) (shift : Array Nat) + (plan : PartialLinearizationPlan) : PolynomialMatrix F := + let xadic := chunkedExactNullspaceProblemForShift modCtx equation plan shift + let expandedShift := chunkedExactNullspaceShift plan shift + compactNonzeroRows (compressChunkedPrincipalRows plan (pmCtx.basis xadic expandedShift)) + +/-- Solve once with the conservative shift-spread window. This is a debug +entry point only: its chunk count can grow quadratically in the module width +for spread-out shifts, so the production solver uses the adaptive +window-escalation loop instead. -/ +def solutionBasisViaPMBasis (modCtx : CPolynomial.ModContext F) + (pmCtx : PMBasisContext F) + (equation : ModularEquation F) (shift : Array Nat) : PolynomialMatrix F := + let plan := partialLinearizationPlan equation.solutionWidth equation.modularWidth + equation.moduli shift + solutionBasisWithPlanViaPMBasis modCtx pmCtx equation shift plan + +/-- Known-degree reconstruction pass: build the partial-linearization plan from +the discovered pivot degrees and solve the X-adic problem directly for that +profile. -/ +def knownDegreeSolutionBasisViaPMBasis (modCtx : CPolynomial.ModContext F) + (pmCtx : PMBasisContext F) + (equation : ModularEquation F) (shift : Array Nat) + (profile : PivotDegreeProfile) : PolynomialMatrix F := + let plan := partialLinearizationPlanFromPivotDegrees equation.solutionWidth + equation.modularWidth equation.moduli shift profile.degrees + solutionBasisWithPlanViaPMBasis modCtx pmCtx equation shift plan + +/-- Solve once without principal-coordinate chunking. This is an explicit +tiny-leaf/debug entry point; the default modular-equation context does not use +it as a production fallback. -/ +def unchunkedSolutionBasisViaPMBasis (modCtx : CPolynomial.ModContext F) + (pmCtx : PMBasisContext F) + (equation : ModularEquation F) (shift : Array Nat) : PolynomialMatrix F := + let plan := unchunkedPartialLinearizationPlan equation.solutionWidth + equation.modularWidth equation.moduli + solutionBasisWithPlanViaPMBasis modCtx pmCtx equation shift plan + +/-- Keep only rows that satisfy the original diagonal modular equation after +compression from the exact-nullspace / X-adic bridge. -/ +def filterModularSolutionRows + (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) + (equation : ModularEquation F) (rows : PolynomialMatrix F) : + PolynomialMatrix F := + rows.filter fun row ↦ + if rowIsZero row then + false + else + rowSatisfiesModularBool mulCtx modCtx row equation.matrix equation.moduli + +/-- Residual rows `B * F mod diag(M)` for candidate solution rows `B`. -/ +def modularResidualRows + (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) + (equation : ModularEquation F) (rows : PolynomialMatrix F) : + PolynomialMatrix F := + modDiagonalWith modCtx equation.moduli + (mulWith mulCtx rows equation.matrix) + +/-- Shift used for polynomial combinations of compressed candidate rows. The +degree of a coefficient multiplying row `i` is measured relative to the shifted +degree already carried by that candidate row. -/ +def candidateRowShift (rows : PolynomialMatrix F) (shift : Array Nat) : Array Nat := + updateShiftByRows rows shift + +/-- Upper bound for the adaptive search window above each coordinate's shift. +The row `e_j * lcm(moduli)` is always a solution and `deg lcm(moduli)` is at +most the modulus degree mass, so every coordinate's minimal shifted pivot +degree is within this window. -/ +def pivotWindowCap [Zero F] (equation : ModularEquation F) : Nat := + modulusDegreeMass equation.moduli + +/-- Coefficient-degree bound for the full-window fallback problem. Any row of +the lifted module whose shifted degree does not exceed the shifted degree of an +in-window solution has plain coefficient degrees at most +`pivotWindowCap + maxShiftDegree shift`: comparing two principal coordinates +costs at most the shift spread, and `pivotWindowCap` bounds every minimal +pivot degree. -/ +def fullWindowDegreeBound [Zero F] (equation : ModularEquation F) + (shift : Array Nat) : Nat := + pivotWindowCap equation + maxShiftDegree shift + +/-- Exact-nullspace lift with the relation entries reduced columnwise by the +moduli, so every principal entry of column `b` has degree below `deg M_b` and +the quotient coefficients of exact solutions stay below the solution degree. -/ +def reducedExactNullspaceLift (modCtx : CPolynomial.ModContext F) + (equation : ModularEquation F) : PolynomialMatrix F := + ofFn equation.solutionWidth equation.modularWidth + (fun i j ↦ + modByMonicWith modCtx (rowGet (equation.matrix.getD i #[]) j) + (equation.moduli.getD j 0)) ++ + negativeDiagonalRows equation.moduli + +/-- Unchunked exact-nullspace problem with orders certifying exactness across +the whole pivot window: a lifted row with principal coefficient degrees at most +`bound` and quotient coefficient degrees at most `bound + 1` produces column-`b` +products of degree below `deg M_b + bound + 2`, so vanishing to that X-adic +order forces the product to vanish exactly. Unlike the chunked relaxation, +this problem admits no uncertified rows below the in-window solution degrees, +at the cost of an order mass larger by a factor of the module width. -/ +def fullWindowExactNullspaceProblem (modCtx : CPolynomial.ModContext F) + (equation : ModularEquation F) (bound : Nat) : XAdicProblem F := + { orders := equation.moduli.map fun modulus ↦ modulus.natDegree + bound + 2 + matrix := reducedExactNullspaceLift modCtx equation } + +/-- Certified full-window solve: compute a minimal basis of the unchunked +exact-nullspace problem whose orders cover the entire pivot window, and keep +the principal columns. Every returned row that pivots at or below an in-window +solution degree is an exact modular solution, so this entry point cannot lose +the solution basis to uncertified low-degree rows. It is used as the fallback +when the chunked adaptive solver discovers nothing. -/ +def fullWindowSolutionBasisViaPMBasis (modCtx : CPolynomial.ModContext F) + (pmCtx : PMBasisContext F) (equation : ModularEquation F) + (shift : Array Nat) : PolynomialMatrix F := + let bound := fullWindowDegreeBound equation shift + let problem := fullWindowExactNullspaceProblem modCtx equation bound + let expandedShift := exactNullspaceShift shift equation.modularWidth bound + compactNonzeroRows + (principalSolutionRows equation.solutionWidth + (pmCtx.basis problem expandedShift)) + +/-- Certified verification window sized by the best exact degree already in +hand. Solutions at or above the best adaptive degree are dominated by the +adaptive result itself, so the certified window only has to cover the degrees +strictly below it; when the adaptive loop found nothing it falls back to the +full pivot window. -/ +def verificationWindowBound [Zero F] (equation : ModularEquation F) + (shift : Array Nat) (bestDegree? : Option Nat) : Nat := + match bestDegree? with + | none => fullWindowDegreeBound equation shift + | some best => min best (pivotWindowCap equation) + maxShiftDegree shift + +/-- Certified verification solve over the best-degree-shrunk window: an +unchunked exact-nullspace problem whose orders certify exactness for every +row dominated by an in-window solution, with the window sized by +`verificationWindowBound`. Its order mass is `sigma + s * bound` instead of +the full window's `(s + 1) * sigma`, so when the adaptive loop already found a +near-minimal row this costs about one extra cheap round. -/ +def verificationSolutionBasisViaPMBasis (modCtx : CPolynomial.ModContext F) + (pmCtx : PMBasisContext F) (equation : ModularEquation F) + (shift : Array Nat) (bestDegree? : Option Nat) : PolynomialMatrix F := + let bound := verificationWindowBound equation shift bestDegree? + let problem := fullWindowExactNullspaceProblem modCtx equation bound + let expandedShift := exactNullspaceShift shift equation.modularWidth bound + compactNonzeroRows + (principalSolutionRows equation.solutionWidth + (pmCtx.basis problem expandedShift)) + +/-- Pivot-degree assignment for one adaptive round: discovered coordinates use +their observed pivot degrees, undiscovered coordinates use the current +escalation window above their shift entry. -/ +def adaptiveProfileDegrees (shift : Array Nat) (profile : PivotDegreeProfile) + (budgets : Array Nat) (solutionWidth : Nat) : Array (Option Nat) := + (List.range solutionWidth).map + (fun j ↦ + match profile.degrees.getD j none with + | some degree => some degree + | none => some (shift.getD j 0 + budgets.getD j 0)) |>.toArray + +/-- Least shifted degree among accumulated solution rows. -/ +def leastSolutionRowDegree? (rows : PolynomialMatrix F) (shift : Array Nat) : + Option Nat := + (leastShiftedDegreeChoice? rows shift).map fun choice ↦ choice.degree + +/-- A coordinate needs no wider search window once its pivot degree is +discovered, its window has reached the cap, or its window already covers every +degree below the best solution row found so far. The last rule is what keeps +the loop from growing windows for coordinates that cannot improve the answer: +a row pivoting at `j` with shifted degree below the current best would lie +inside the already-searched window. Improving on `best` needs a row of degree +at most `best - 1`, so the window `shift[j] + budget[j]` suffices once +`best <= shift[j] + budget[j] + 1`. -/ +def coordinateSettled (profile : PivotDegreeProfile) (budgets : Array Nat) + (cap : Nat) (bestDegree? : Option Nat) (shift : Array Nat) (j : Nat) : Bool := + (profile.degrees.getD j none).isSome || + cap ≤ budgets.getD j 0 || + (match bestDegree? with + | some best => best ≤ shift.getD j 0 + budgets.getD j 0 + 1 + | none => false) + +/-- Whether every principal coordinate is settled for the current windows. -/ +def allCoordinatesSettled (profile : PivotDegreeProfile) (budgets : Array Nat) + (cap : Nat) (bestDegree? : Option Nat) (shift : Array Nat) + (solutionWidth : Nat) : Bool := + (List.range solutionWidth).all fun j ↦ + coordinateSettled profile budgets cap bestDegree? shift j + +/-- Double the escalation windows of coordinates that are not settled, clamped +at the window cap. Settled coordinates keep their window so the total window +mass stays within a constant factor of the useful pivot-degree mass. -/ +def escalateUnsettledBudgets (profile : PivotDegreeProfile) + (budgets : Array Nat) (cap : Nat) (bestDegree? : Option Nat) + (shift : Array Nat) : Array Nat := + (List.range budgets.size).map + (fun j ↦ + let budget := budgets.getD j 0 + if coordinateSettled profile budgets cap bestDegree? shift j then + budget + else + min cap (2 * max 1 budget)) |>.toArray + +/-- State carried between adaptive solution-basis rounds. `filtered` +accumulates the exact solution rows found across all rounds. -/ +structure AdaptiveSolveState (F : Type*) [Zero F] where + profile : PivotDegreeProfile + budgets : Array Nat + filtered : PolynomialMatrix F + raw : PolynomialMatrix F + +/-- One adaptive round: solve the chunked exact-nullspace problem for the +current window assignment, keep the rows that satisfy the original diagonal +congruences, and merge the observed shifted pivot degrees into the profile. -/ +def adaptiveSolutionRound + (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) + (pmCtx : PMBasisContext F) (equation : ModularEquation F) + (shift : Array Nat) (state : AdaptiveSolveState F) : AdaptiveSolveState F := + let degrees := adaptiveProfileDegrees shift state.profile state.budgets + equation.solutionWidth + let plan := partialLinearizationPlanFromPivotDegrees equation.solutionWidth + equation.modularWidth equation.moduli shift degrees + let rows := solutionBasisWithPlanViaPMBasis modCtx pmCtx equation shift plan + let filtered := filterModularSolutionRows mulCtx modCtx equation rows + { profile := pivotDegreeProfileMergeRows state.profile equation.solutionWidth + filtered shift + budgets := state.budgets + filtered := state.filtered ++ filtered + raw := rows } + +/-- Fuel-bounded adaptive window-escalation loop. Rounds stop as soon as every +principal coordinate is settled: discovered, saturated, or unable to beat the +best solution row already in hand. -/ +def adaptiveSolutionLoop + (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) + (pmCtx : PMBasisContext F) (equation : ModularEquation F) + (shift : Array Nat) (cap : Nat) : + Nat → AdaptiveSolveState F → AdaptiveSolveState F + | 0, state => state + | fuel + 1, state => + let next := adaptiveSolutionRound mulCtx modCtx pmCtx equation shift state + let bestDegree? := leastSolutionRowDegree? next.filtered shift + if next.filtered.size == 0 && !next.profile.discoveredAny then + -- Zero discovery means the chunked relaxation is dominated by + -- uncertified rows below the solution degrees; growing the windows + -- multiplies the round cost without producing new pivot information, + -- so stop here and let the caller run the certified full-window + -- fallback instead. + next + else if allCoordinatesSettled next.profile next.budgets cap bestDegree? shift + equation.solutionWidth then + next + else + let escalated := escalateUnsettledBudgets next.profile next.budgets cap + bestDegree? shift + if escalated == next.budgets then + next + else + adaptiveSolutionLoop mulCtx modCtx pmCtx equation shift cap fuel + { next with budgets := escalated } + +/-- Run the adaptive degree-first solver: discover the shifted pivot-degree +profile with geometrically growing per-coordinate windows, where the final +round doubles as the known-degree reconstruction for all discovered +coordinates. The initial window is one chunk per coordinate. -/ +def adaptiveSolutionBasis + (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) + (pmCtx : PMBasisContext F) (equation : ModularEquation F) + (shift : Array Nat) : AdaptiveSolveState F := + let delta := chunkDelta equation.solutionWidth equation.moduli + let cap := max delta (pivotWindowCap equation) + let fuel := Nat.log2 (max 1 cap) + 2 + adaptiveSolutionLoop mulCtx modCtx pmCtx equation shift cap fuel + { profile := emptyPivotDegreeProfile equation.solutionWidth + budgets := Array.replicate equation.solutionWidth (max 1 (delta - 1)) + filtered := #[] + raw := #[] } + +/-- Discover the shifted pivot-degree profile through the adaptive solver. -/ +def discoverPivotDegreeProfileViaPMBasis + (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) + (pmCtx : PMBasisContext F) (equation : ModularEquation F) + (shift : Array Nat) : PivotDegreeProfile := + (adaptiveSolutionBasis mulCtx modCtx pmCtx equation shift).profile + +/-- One residual reconstruction pass. If compressed rows `B` are not themselves +exact modular solutions, solve for polynomial combinations `C` such that +`C * (B * F mod M) = 0 mod M`, then return `C * B`. The residual equation is +solved with the same adaptive solver, without a further repair recursion. + +The candidate rows are first reduced to one representative per shifted leading +position. The reduction steps are unimodular, so the generated row module is +unchanged, while the residual equation's solution width stays bounded by the +principal width instead of the raw candidate count; without this bound the +repair solve can be quadratically wider than the original equation. -/ +def repairSolutionRowsViaPMBasis + (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) + (pmCtx : PMBasisContext F) (equation : ModularEquation F) + (shift : Array Nat) (rows : PolynomialMatrix F) : PolynomialMatrix F := + let reduced := compactNonzeroRows (reduceKernelLeafRowsByPivots rows shift) + let residualEquation : ModularEquation F := + { moduli := equation.moduli + matrix := modularResidualRows mulCtx modCtx equation reduced } + let repairState := adaptiveSolutionBasis mulCtx modCtx pmCtx residualEquation + (candidateRowShift reduced shift) + PolynomialMatrix.mulStrassenWith pmCtx.runtime.lowMulContext + pmCtx.runtime.leafCutoff repairState.filtered reduced + +/-- Debug helper for tiny problems that intentionally disables +principal-coordinate chunking. This keeps the old unchunked behavior available +for inspection without letting the production context bypass partial +linearization. -/ +def debugUnchunkedFilteredSolutionBasisViaPMBasis + (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) + (pmCtx : PMBasisContext F) (equation : ModularEquation F) + (shift : Array Nat) : PolynomialMatrix F := + filterModularSolutionRows mulCtx modCtx equation + (unchunkedSolutionBasisViaPMBasis modCtx pmCtx equation shift) + +/-- Known-degree reconstruction followed by the original diagonal-equation +guard. The reconstruction plan is built from discovered shifted pivot degrees, +solved as a chunked exact-nullspace PM-basis problem, and compressed back to the +principal solution coordinates. -/ +def knownDegreeFilteredSolutionBasisViaPMBasis + (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) + (pmCtx : PMBasisContext F) (equation : ModularEquation F) + (shift : Array Nat) (profile : PivotDegreeProfile) : PolynomialMatrix F := + let rows := knownDegreeSolutionBasisViaPMBasis modCtx pmCtx equation shift profile + let filtered := filterModularSolutionRows mulCtx modCtx equation rows + if filtered.size == 0 then + filterModularSolutionRows mulCtx modCtx equation + (repairSolutionRowsViaPMBasis mulCtx modCtx pmCtx equation shift rows) + else + filtered + +/-- Solver exposed through the modular-equation context: run the adaptive +degree-first window-escalation loop, whose final round is the known-degree +reconstruction for every discovered coordinate, then always run a certified +verification solve over the window shrunk to the best exact degree found (the +full pivot window when the chunked loop found nothing), and return the union +of both filtered candidate sets. Solutions at or above the adaptive best are +dominated by the adaptive rows; solutions strictly below it lie inside the +certified verification window, whose orders rule out uncertified relaxed +rows — so the union always contains an exact row of minimal shifted degree, +including in the partial-masking regime where uncertified chunked-kernel rows +crowd lower exact solutions out of every adaptive round. The adaptive rows +come first, so ties keep the adaptive choice. The residual repair pass +remains as a guard when both candidate sets are empty. The filter, +verification, and repair are semantic guards around the exact-nullspace +bridge; they do not call any alternate interpolation backend. -/ +def filteredSolutionBasisViaPMBasis + (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) + (pmCtx : PMBasisContext F) (equation : ModularEquation F) + (shift : Array Nat) : PolynomialMatrix F := + let final := adaptiveSolutionBasis mulCtx modCtx pmCtx equation shift + let verification := filterModularSolutionRows mulCtx modCtx equation + (verificationSolutionBasisViaPMBasis modCtx pmCtx equation shift + (leastSolutionRowDegree? final.filtered shift)) + let combined := final.filtered ++ verification + if combined.size == 0 then + filterModularSolutionRows mulCtx modCtx equation + (repairSolutionRowsViaPMBasis mulCtx modCtx pmCtx equation shift final.raw) + else + combined + +/-- Rows kept by the modular solution filter satisfy the modular predicate. -/ +theorem rowSatisfiesModularBool_of_mem_filterModularSolutionRows + {mulCtx : CPolynomial.MulContext F} {modCtx : CPolynomial.ModContext F} + {equation : ModularEquation F} {rows : PolynomialMatrix F} + {row : PolynomialRow F} + (hrow : row ∈ MatrixRows (filterModularSolutionRows mulCtx modCtx equation rows)) : + rowSatisfiesModularBool mulCtx modCtx row equation.matrix equation.moduli = true := by + rw [MatrixRows, filterModularSolutionRows] at hrow + have hmem : row ∈ rows ∧ rowIsZero row = false ∧ + rowSatisfiesModularBool mulCtx modCtx row equation.matrix equation.moduli = true := by + simpa using hrow + exact hmem.2.2 + +/-- One adaptive round preserves modular soundness of the accumulated filtered +rows. -/ +theorem adaptiveSolutionRound_filtered_sound + {mulCtx : CPolynomial.MulContext F} {modCtx : CPolynomial.ModContext F} + {pmCtx : PMBasisContext F} {equation : ModularEquation F} + {shift : Array Nat} {state : AdaptiveSolveState F} + (hstate : ∀ row ∈ MatrixRows state.filtered, + rowSatisfiesModularBool mulCtx modCtx row equation.matrix equation.moduli = true) : + ∀ row ∈ MatrixRows + (adaptiveSolutionRound mulCtx modCtx pmCtx equation shift state).filtered, + rowSatisfiesModularBool mulCtx modCtx row equation.matrix equation.moduli = true := by + intro row hrow + simp only [adaptiveSolutionRound, MatrixRows, Array.toList_append, + List.mem_append] at hrow + rcases hrow with hold | hnew + · exact hstate row hold + · exact rowSatisfiesModularBool_of_mem_filterModularSolutionRows hnew + +/-- The adaptive escalation loop preserves modular soundness of the accumulated +filtered rows. -/ +theorem adaptiveSolutionLoop_filtered_sound + {mulCtx : CPolynomial.MulContext F} {modCtx : CPolynomial.ModContext F} + {pmCtx : PMBasisContext F} {equation : ModularEquation F} + {shift : Array Nat} {cap : Nat} : + ∀ (fuel : Nat) (state : AdaptiveSolveState F), + (∀ row ∈ MatrixRows state.filtered, + rowSatisfiesModularBool mulCtx modCtx row equation.matrix equation.moduli = true) → + ∀ row ∈ MatrixRows + (adaptiveSolutionLoop mulCtx modCtx pmCtx equation shift cap fuel state).filtered, + rowSatisfiesModularBool mulCtx modCtx row equation.matrix equation.moduli = true := by + intro fuel + induction fuel with + | zero => + intro state hstate row hrow + exact hstate row hrow + | succ fuel ih => + intro state hstate row hrow + rw [adaptiveSolutionLoop] at hrow + have hnext := adaptiveSolutionRound_filtered_sound + (pmCtx := pmCtx) (shift := shift) (state := state) hstate + split at hrow + · exact hnext row hrow + · split at hrow + · exact hnext row hrow + · dsimp only [] at hrow + split at hrow + · exact hnext row hrow + · refine ih _ ?_ row hrow + intro r hr + exact hnext r hr + +/-- Rows accumulated by the adaptive solver satisfy the modular predicate. -/ +theorem adaptiveSolutionBasis_filtered_sound + {mulCtx : CPolynomial.MulContext F} {modCtx : CPolynomial.ModContext F} + {pmCtx : PMBasisContext F} {equation : ModularEquation F} + {shift : Array Nat} : + ∀ row ∈ MatrixRows + (adaptiveSolutionBasis mulCtx modCtx pmCtx equation shift).filtered, + rowSatisfiesModularBool mulCtx modCtx row equation.matrix equation.moduli = true := by + rw [adaptiveSolutionBasis] + exact adaptiveSolutionLoop_filtered_sound _ _ + (by intro r hr; simp [MatrixRows] at hr) + +/-- Every row returned by the filtered PM-basis modular solver satisfies the +original diagonal modular equation. -/ +theorem filteredSolutionBasisViaPMBasis_sound + {mulCtx : CPolynomial.MulContext F} {modCtx : CPolynomial.ModContext F} + {pmCtx : PMBasisContext F} {equation : ModularEquation F} + {shift : Array Nat} {row : PolynomialRow F} + (hrow : row ∈ MatrixRows + (filteredSolutionBasisViaPMBasis mulCtx modCtx pmCtx equation shift)) : + rowSatisfiesModularBool mulCtx modCtx row equation.matrix equation.moduli = true := by + simp only [filteredSolutionBasisViaPMBasis] at hrow + split at hrow + · exact rowSatisfiesModularBool_of_mem_filterModularSolutionRows hrow + · rw [MatrixRows, Array.toList_append, List.mem_append] at hrow + rcases hrow with hmem | hmem + · exact adaptiveSolutionBasis_filtered_sound row hmem + · exact rowSatisfiesModularBool_of_mem_filterModularSolutionRows hmem + +/-- Modular-equation solution-basis context with theorem fields. + +The completeness/minimality contract states that, for any nonzero in-width +solution row of the diagonal modular equation, the returned basis stays inside +the principal width and contains a row whose shifted degree does not exceed +the given solution's. The contract assumes monic moduli, a relation matrix +wide enough to expose every modular column to the executable row predicate, +and a shift aligned with the principal solution width. -/ +structure ModularSolutionBasisContext (F : Type*) [Field F] [BEq F] [LawfulBEq F] where + mulContext : CPolynomial.MulContext F + modContext : CPolynomial.ModContext F + solutionBasis : ModularEquation F → Array Nat → PolynomialMatrix F + sound : + ∀ equation shift row, + row ∈ MatrixRows (solutionBasis equation shift) → + rowSatisfiesModularBool mulContext modContext row equation.matrix equation.moduli = true + complete_minimal : + ∀ equation shift row, + (∀ b, b < equation.moduli.size → (equation.moduli.getD b 0).monic) → + equation.moduli.size ≤ MatrixWidth equation.matrix → + shift.size = equation.solutionWidth → + rowSatisfiesModularBool mulContext modContext row equation.matrix + equation.moduli = true → + rowIsZero row = false → + row.size ≤ equation.solutionWidth → + (∀ basisRow, + basisRow ∈ MatrixRows (solutionBasis equation shift) → + basisRow.size ≤ equation.solutionWidth) ∧ + ∃ basisRow degree, + basisRow ∈ MatrixRows (solutionBasis equation shift) ∧ + rowShiftedDegree? basisRow shift = some degree ∧ + ∀ rowDegree, rowShiftedDegree? row shift = some rowDegree → + degree ≤ rowDegree + +end Approximant + +end PolynomialMatrix + +end CompPoly diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation/Completeness.lean b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation/Completeness.lean new file mode 100644 index 00000000..64b637a0 --- /dev/null +++ b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation/Completeness.lean @@ -0,0 +1,1135 @@ +/- +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.LinearAlgebra.PolynomialMatrix.Approximant.ModularEquation.Basic +import CompPoly.Univariate.DivisionCorrectness + +/-! +# Filtered Modular Solver Completeness + +Completeness/minimality of the filtered PM-basis modular solver: every +nonzero in-width solution of a monic diagonal modular equation is dominated +by a returned row, via the certified verification window. +-/ + +namespace CompPoly + +namespace PolynomialMatrix + +namespace Approximant + +variable {F : Type*} [Field F] [BEq F] [LawfulBEq F] + +/-! ## Completeness/minimality of the filtered PM-basis modular solver + +The argument routes every modular solution through the certified verification +window. A nonzero in-width solution row of shifted degree `e ≤ bound` lifts +to an exact row of the reduced exact-nullspace problem whose expanded shifted +degree is at most `e + bound + 1`. The PM-basis minimality contract yields a +basis row of dominated expanded degree, the verification orders force its +column products to vanish exactly, and its principal truncation is therefore a +modular solution of shifted degree at most `e` surviving both filters. +Solutions above the verification window are handled either by the adaptive +rows themselves or by falling back to the always-available solution +`e_p * prod(moduli)`, which fits the saturated window. -/ + +section CompleteMinimal + +/-! ### Generic access and summation helpers -/ + +private theorem me_getD_list_range_map {α : Type*} (g : Nat → α) (n j : Nat) (d : α) : + (((List.range n).map g).toArray).getD j d = if j < n then g j else d := by + rw [Array.getD_eq_getD_getElem?, List.getElem?_toArray, List.getElem?_map] + by_cases hj : j < n + · rw [List.getElem?_range hj, Option.map_some, Option.getD_some, if_pos hj] + · rw [List.getElem?_eq_none (by simpa using Nat.le_of_not_lt hj), Option.map_none, + Option.getD_none, if_neg hj] + +private theorem me_getD_append_left {α : Type*} {A B : Array α} {i : Nat} (d : α) + (hi : i < A.size) : + (A ++ B).getD i d = A.getD i d := by + rw [Array.getD_eq_getD_getElem?, Array.getD_eq_getD_getElem?, + Array.getElem?_append_left hi] + +private theorem me_getD_append_right {α : Type*} {A B : Array α} {i : Nat} (d : α) + (hi : A.size ≤ i) : + (A ++ B).getD i d = B.getD (i - A.size) d := by + rw [Array.getD_eq_getD_getElem?, Array.getD_eq_getD_getElem?, + Array.getElem?_append_right hi] + +private theorem me_getD_replicate {α : Type*} {n : Nat} (a d : α) {i : Nat} + (hi : i < n) : + (Array.replicate n a).getD i d = a := by + rw [Array.getD_eq_getD_getElem?, Array.getElem?_replicate, if_pos hi, Option.getD_some] + +/-- In-bounds `getD` values are list members. -/ +theorem me_getD_mem_toList {α : Type*} {xs : Array α} {i : Nat} (d : α) + (hi : i < xs.size) : xs.getD i d ∈ xs.toList := by + rw [Array.getD_eq_getD_getElem?, Array.getElem?_eq_getElem hi, Option.getD_some] + exact Array.getElem_mem_toList hi + +omit [BEq F] [LawfulBEq F] in +private theorem me_matrixWidth_eq_getD (M : PolynomialMatrix F) : + MatrixWidth M = (M.getD 0 #[]).size := by + unfold MatrixWidth + rw [Array.getD_eq_getD_getElem?] + cases M[0]? <;> rfl + +private theorem me_sum_range_add {M : Type*} [AddCommMonoid M] (f : Nat → M) + (m n : Nat) : + ∑ k ∈ Finset.range (m + n), f k = + (∑ k ∈ Finset.range m, f k) + ∑ k ∈ Finset.range n, f (m + k) := by + induction n with + | zero => simp + | succ n ih => + rw [show m + (n + 1) = (m + n) + 1 from rfl, Finset.sum_range_succ, ih, + Finset.sum_range_succ, add_assoc] + +private theorem me_toPoly_sum (f : Nat → CPolynomial F) (n : Nat) : + (∑ k ∈ Finset.range n, f k).toPoly = ∑ k ∈ Finset.range n, (f k).toPoly := by + induction n with + | zero => simp [CPolynomial.toPoly_zero] + | succ n ih => + rw [Finset.sum_range_succ, Finset.sum_range_succ, CPolynomial.toPoly_add, ih] + +/-- Column entries of a row-by-matrix product as `toPoly` sums over any index +range covering the row. The matrix multiplication context is irrelevant to +the value. -/ +private theorem me_rowMul_toPoly (mulCtx : CPolynomial.MulContext F) + (row : PolynomialRow F) (M : PolynomialMatrix F) {j n : Nat} + (hj : j < MatrixWidth M) (hn : row.size ≤ n) : + (rowGet (rowMulMatrixWith mulCtx row M) j).toPoly = + ∑ k ∈ Finset.range n, + (rowGet row k).toPoly * (rowGet (M.getD k #[]) j).toPoly := by + rw [rowGet_rowMulMatrixWith mulCtx row M hj, me_toPoly_sum] + rw [show ∑ k ∈ Finset.range row.size, + (rowGet row k * rowGet (M.getD k #[]) j).toPoly = + ∑ k ∈ Finset.range row.size, + (rowGet row k).toPoly * (rowGet (M.getD k #[]) j).toPoly from + Finset.sum_congr rfl fun k _ ↦ CPolynomial.toPoly_mul _ _] + refine Finset.sum_subset + (fun x hx ↦ Finset.mem_range.mpr + (lt_of_lt_of_le (Finset.mem_range.mp hx) hn)) + fun k _hk hknot ↦ ?_ + have hk : row.size ≤ k := by simpa using hknot + rw [rowGet_of_size_le hk, CPolynomial.toPoly_zero, zero_mul] + +omit [BEq F] [LawfulBEq F] in +private theorem me_natDegree_sum_le {n : Nat} (f : Nat → Polynomial F) {D : Nat} + (h : ∀ k, k < n → (f k).natDegree ≤ D) : + (∑ k ∈ Finset.range n, f k).natDegree ≤ D := by + induction n with + | zero => simp + | succ n ih => + rw [Finset.sum_range_succ] + refine le_trans (Polynomial.natDegree_add_le _ _) ?_ + exact max_le (ih fun k hk ↦ h k (by omega)) (h n (by omega)) + +omit [BEq F] [LawfulBEq F] in +private theorem me_eq_zero_of_X_pow_dvd_of_natDegree_lt {p : Polynomial F} {n : Nat} + (hdvd : (Polynomial.X : Polynomial F) ^ n ∣ p) (hdeg : p.natDegree < n) : + p = 0 := by + by_contra hp + have hle := Polynomial.natDegree_le_of_dvd hdvd hp + rw [Polynomial.natDegree_X_pow] at hle + omega + +/-! ### Zero-row and shifted-degree helpers -/ + +private theorem me_rowIsZero_false_of_entry {row : PolynomialRow F} {j : Nat} + (hj : j < row.size) (hne : rowGet row j ≠ 0) : + rowIsZero row = false := by + cases hzero : rowIsZero row with + | false => rfl + | true => + exfalso + refine hne (rowIsZero_iff.mp hzero (rowGet row j) ?_) + rw [rowGet] + exact me_getD_mem_toList 0 hj + +private theorem me_rowGet_eq_zero_of_rowIsZero {row : PolynomialRow F} + (h : rowIsZero row = true) (k : Nat) : rowGet row k = 0 := by + rcases Nat.lt_or_ge k row.size with hk | hk + · refine rowIsZero_iff.mp h (rowGet row k) ?_ + rw [rowGet] + exact me_getD_mem_toList 0 hk + · exact rowGet_of_size_le hk + +omit [LawfulBEq F] in +private theorem me_rowIsZero_of_forall {row : PolynomialRow F} + (h : ∀ j, j < row.size → rowGet row j = 0) : + RowIsZero row := by + intro p hp + rcases List.getElem_of_mem hp with ⟨j, hj, hget⟩ + have hj' : j < row.size := by simpa using hj + have hzero := h j hj' + rw [rowGet, Array.getD_eq_getD_getElem?, Array.getElem?_eq_getElem hj', + Option.getD_some] at hzero + rw [← hget] + simpa [Array.getElem_toList] using hzero + +/-- Nonzero rows have a shifted degree. -/ +theorem me_rowShiftedDegree_isSome {row : PolynomialRow F} {shift : Array Nat} + (hnz : rowIsZero row = false) : + ∃ d, rowShiftedDegree? row shift = some d := by + cases hdeg : rowShiftedDegree? row shift with + | none => + rw [rowIsZero_iff.mpr (rowShiftedDegree?_eq_none_iff.mp hdeg)] at hnz + simp at hnz + | some d => exact ⟨d, rfl⟩ + +private theorem me_shiftedEntryDegree_eq {row : PolynomialRow F} {shift : Array Nat} + {j : Nat} (hne : rowGet row j ≠ 0) : + shiftedEntryDegree? row shift j = + some ((rowGet row j).natDegree + shift.getD j 0) := by + rw [shiftedEntryDegree?, if_neg (by simpa using hne)] + +private theorem me_shiftedEntryDegree_some {row : PolynomialRow F} {shift : Array Nat} + {j e : Nat} (h : shiftedEntryDegree? row shift j = some e) : + rowGet row j ≠ 0 ∧ e = (rowGet row j).natDegree + shift.getD j 0 := by + by_cases hne : rowGet row j = 0 + · rw [shiftedEntryDegree?] at h + simp [hne] at h + · rw [me_shiftedEntryDegree_eq hne] at h + exact ⟨hne, (Option.some.inj h).symm⟩ + +private theorem me_entry_le_of_rowShiftedDegree {row : PolynomialRow F} + {shift : Array Nat} {d j : Nat} + (hdeg : rowShiftedDegree? row shift = some d) (hne : rowGet row j ≠ 0) : + (rowGet row j).natDegree + shift.getD j 0 ≤ d := by + have hj : j < row.size := by + by_contra hj + exact hne (rowGet_of_size_le (Nat.le_of_not_lt hj)) + exact shiftedEntryDegree?_le_of_rowShiftedDegree?_eq_some hdeg hj + (me_shiftedEntryDegree_eq hne) + +private theorem me_rowShiftedDegree_attained {row : PolynomialRow F} + {shift : Array Nat} {d : Nat} (hdeg : rowShiftedDegree? row shift = some d) : + ∃ j, j < row.size ∧ rowGet row j ≠ 0 ∧ + d = (rowGet row j).natDegree + shift.getD j 0 := by + rcases exists_shiftedEntryDegree?_eq_of_rowShiftedDegree?_eq_some hdeg with + ⟨j, hj, hentry⟩ + rcases me_shiftedEntryDegree_some hentry with ⟨hne, he⟩ + exact ⟨j, hj, hne, he⟩ + +/-! ### `maxShiftDegree` and modulus-product bounds -/ + +private theorem me_foldl_max_init_le (g : Nat → Nat) : + ∀ (l : List Nat) (acc : Nat), acc ≤ l.foldl (fun a i ↦ max a (g i)) acc := by + intro l + induction l with + | nil => intro acc; exact Nat.le_refl _ + | cons x l ih => + intro acc + exact le_trans (Nat.le_max_left acc (g x)) (ih (max acc (g x))) + +private theorem me_le_foldl_max (g : Nat → Nat) : + ∀ (l : List Nat) (acc : Nat) (x : Nat), x ∈ l → + g x ≤ l.foldl (fun a i ↦ max a (g i)) acc := by + intro l + induction l with + | nil => intro acc x hx; cases hx + | cons y l ih => + intro acc x hx + rcases List.mem_cons.mp hx with rfl | hx + · exact le_trans (Nat.le_max_right acc (g x)) (me_foldl_max_init_le g l _) + · exact ih _ x hx + +private theorem me_shift_le_maxShiftDegree (shift : Array Nat) (j : Nat) : + shift.getD j 0 ≤ maxShiftDegree shift := by + rw [maxShiftDegree] + rcases Nat.lt_or_ge j shift.size with hj | hj + · exact me_le_foldl_max (fun i ↦ shift.getD i 0) (List.range shift.size) 0 j + (List.mem_range.mpr hj) + · rw [Array.getD_eq_getD_getElem?, Array.getElem?_eq_none hj] + exact Nat.zero_le _ + +/-- Product of all moduli, used as the universal fallback solution entry. -/ +private def me_moduliProduct (moduli : Array (CPolynomial F)) : CPolynomial F := + moduli.foldl (fun acc m ↦ acc * m) 1 + +private theorem me_foldl_mul_toPoly : + ∀ (l : List (CPolynomial F)) (acc : CPolynomial F), + (l.foldl (fun a m ↦ a * m) acc).toPoly = + acc.toPoly * (l.map CPolynomial.toPoly).prod := by + intro l + induction l with + | nil => intro acc; simp + | cons m l ih => + intro acc + rw [List.foldl_cons, ih, CPolynomial.toPoly_mul, List.map_cons, List.prod_cons] + ring + +private theorem me_moduliProduct_toPoly (moduli : Array (CPolynomial F)) : + (me_moduliProduct moduli).toPoly = (moduli.toList.map CPolynomial.toPoly).prod := by + rw [me_moduliProduct, ← Array.foldl_toList, me_foldl_mul_toPoly, + CPolynomial.toPoly_one, one_mul] + +private theorem me_moduliProduct_dvd {moduli : Array (CPolynomial F)} {b : Nat} + (hb : b < moduli.size) : + (moduli.getD b 0).toPoly ∣ (me_moduliProduct moduli).toPoly := by + rw [me_moduliProduct_toPoly] + exact List.dvd_prod (List.mem_map.mpr ⟨moduli.getD b 0, me_getD_mem_toList 0 hb, rfl⟩) + +omit [BEq F] [LawfulBEq F] in +private theorem me_list_prod_ne_zero : + ∀ (l : List (Polynomial F)), (∀ p ∈ l, p ≠ 0) → l.prod ≠ 0 + | [], _ => by simp + | p :: l, h => by + rw [List.prod_cons] + exact mul_ne_zero (h p (by simp)) + (me_list_prod_ne_zero l fun q hq ↦ h q (by simp [hq])) + +private theorem me_moduliProduct_ne_zero {moduli : Array (CPolynomial F)} + (hmonic : ∀ b, b < moduli.size → (moduli.getD b 0).monic) : + me_moduliProduct moduli ≠ 0 := by + intro hzero + have htoPoly : (me_moduliProduct moduli).toPoly = 0 := by + rw [hzero, CPolynomial.toPoly_zero] + rw [me_moduliProduct_toPoly] at htoPoly + refine me_list_prod_ne_zero (moduli.toList.map CPolynomial.toPoly) ?_ htoPoly + intro p hp + rcases List.mem_map.mp hp with ⟨q, hq, rfl⟩ + rcases List.getElem_of_mem hq with ⟨b, hb, hget⟩ + have hb' : b < moduli.size := by simpa using hb + have hqd : moduli.getD b 0 = q := by + rw [Array.getD_eq_getD_getElem?, Array.getElem?_eq_getElem hb', Option.getD_some] + simpa [Array.getElem_toList] using hget + have hmq := hmonic b hb' + rw [hqd] at hmq + exact Polynomial.Monic.ne_zero ((CPolynomial.monic_toPoly_iff q).mp hmq) + +omit [BEq F] [LawfulBEq F] in +private theorem me_foldl_add_mono : + ∀ (l : List (CPolynomial F)) (a b : Nat), a ≤ b → + l.foldl (fun acc m ↦ acc + m.natDegree) a ≤ + l.foldl (fun acc m ↦ acc + m.natDegree) b := by + intro l + induction l with + | nil => intro a b h; exact h + | cons m l ih => intro a b h; exact ih _ _ (Nat.add_le_add_right h _) + +private theorem me_foldl_mul_natDegree_le : + ∀ (l : List (CPolynomial F)) (acc : CPolynomial F), + (l.foldl (fun a m ↦ a * m) acc).toPoly.natDegree ≤ + l.foldl (fun a m ↦ a + m.natDegree) acc.toPoly.natDegree := by + intro l + induction l with + | nil => intro acc; exact Nat.le_refl _ + | cons m l ih => + intro acc + rw [List.foldl_cons, List.foldl_cons] + refine le_trans (ih (acc * m)) (me_foldl_add_mono l _ _ ?_) + rw [CPolynomial.toPoly_mul] + refine le_trans Polynomial.natDegree_mul_le ?_ + rw [CPolynomial.natDegree_toPoly] + +private theorem me_moduliProduct_natDegree_le (moduli : Array (CPolynomial F)) : + (me_moduliProduct moduli).toPoly.natDegree ≤ modulusDegreeMass moduli := by + rw [me_moduliProduct, modulusDegreeMass, ← Array.foldl_toList, ← Array.foldl_toList] + have h := me_foldl_mul_natDegree_le moduli.toList 1 + simpa [CPolynomial.toPoly_one] using h + +/-! ### Modular-reduction semantics (local copies of the GS bridge lemmas) -/ + +private theorem me_modByMonicWith_toPoly (modCtx : CPolynomial.ModContext F) + {p M : CPolynomial F} (hM : Polynomial.Monic M.toPoly) : + (modByMonicWith modCtx p M).toPoly = p.toPoly %ₘ M.toPoly := by + have hMne : M ≠ 0 := by + intro hzero + have : M.toPoly = 0 := by rw [hzero, CPolynomial.toPoly_zero] + exact hM.ne_zero this + rw [modByMonicWith, if_neg (by simpa using hMne), modCtx.modByMonic_eq_modByMonic] + exact CPolynomial.modByMonic_toPoly_eq_modByMonic p M + ((CPolynomial.monic_toPoly_iff M).mpr hM) + +private theorem me_dvd_modByMonicWith_sub (modCtx : CPolynomial.ModContext F) + {p M : CPolynomial F} (hM : Polynomial.Monic M.toPoly) : + M.toPoly ∣ (modByMonicWith modCtx p M).toPoly - p.toPoly := by + rw [me_modByMonicWith_toPoly modCtx hM] + refine ⟨-(p.toPoly /ₘ M.toPoly), ?_⟩ + calc p.toPoly %ₘ M.toPoly - p.toPoly + = p.toPoly %ₘ M.toPoly - + (p.toPoly %ₘ M.toPoly + M.toPoly * (p.toPoly /ₘ M.toPoly)) := by + rw [Polynomial.modByMonic_add_div p.toPoly M.toPoly] + _ = M.toPoly * -(p.toPoly /ₘ M.toPoly) := by ring + +private theorem me_modByMonicWith_eq_zero_iff_dvd (modCtx : CPolynomial.ModContext F) + {p M : CPolynomial F} (hM : Polynomial.Monic M.toPoly) : + modByMonicWith modCtx p M = 0 ↔ M.toPoly ∣ p.toPoly := by + rw [← Polynomial.modByMonic_eq_zero_iff_dvd hM, ← me_modByMonicWith_toPoly modCtx hM] + constructor + · intro h + rw [h, CPolynomial.toPoly_zero] + · intro h + exact (CPolynomial.toPoly_eq_zero_iff _).mp h + +private theorem me_divByMonic_mul_eq {p M : CPolynomial F} (hM : M.monic) + (hdvd : M.toPoly ∣ p.toPoly) : + (p.divByMonic M).toPoly * M.toPoly = p.toPoly := by + have hMonic : Polynomial.Monic M.toPoly := (CPolynomial.monic_toPoly_iff M).mp hM + have hmod : p.toPoly %ₘ M.toPoly = 0 := + (Polynomial.modByMonic_eq_zero_iff_dvd hMonic).mpr hdvd + have hdecomp := Polynomial.modByMonic_add_div p.toPoly M.toPoly + rw [hmod, zero_add] at hdecomp + rw [CPolynomial.divByMonic_toPoly_eq_divByMonic p M hM, mul_comm] + exact hdecomp + +/-- The executable modular row predicate, columnwise. -/ +private theorem me_rowSatisfies_iff (mulCtx : CPolynomial.MulContext F) + (modCtx : CPolynomial.ModContext F) (row : PolynomialRow F) + (M : PolynomialMatrix F) (moduli : Array (CPolynomial F)) : + rowSatisfiesModularBool mulCtx modCtx row M moduli = true ↔ + ∀ b, b < moduli.size → + modByMonicWith modCtx (rowGet (rowMulMatrixWith mulCtx row M) b) + (moduli.getD b 0) = 0 := by + rw [rowSatisfiesModularBool, rowMulMatrixModDiagonalWith, rowModDiagonalWith, + Array.all_eq_true] + constructor + · intro h b hb + have hb' : b < (((List.range moduli.size).map fun j ↦ + modByMonicWith modCtx (rowGet (rowMulMatrixWith mulCtx row M) j) + (moduli.getD j 0)).toArray).size := by + simpa using hb + have := h b hb' + simpa using this + · intro h b hb + have hb' : b < moduli.size := by simpa using hb + simpa using h b hb' + +/-! ### Membership plumbing for the pipeline filters -/ + +/-- Filtered modular-solution rows come from the input rows. -/ +theorem me_filterModularSolutionRows_subset + {mulCtx : CPolynomial.MulContext F} {modCtx : CPolynomial.ModContext F} + {equation : ModularEquation F} {rows : PolynomialMatrix F} + {row : PolynomialRow F} + (hrow : row ∈ MatrixRows (filterModularSolutionRows mulCtx modCtx equation rows)) : + row ∈ MatrixRows rows := by + rw [MatrixRows, filterModularSolutionRows] at hrow + rw [MatrixRows] + have hmem : row ∈ rows ∧ rowIsZero row = false ∧ + rowSatisfiesModularBool mulCtx modCtx row equation.matrix equation.moduli = true := by + simpa using hrow + simpa using hmem.1 + +private theorem me_mem_filterModularSolutionRows + {mulCtx : CPolynomial.MulContext F} {modCtx : CPolynomial.ModContext F} + {equation : ModularEquation F} {rows : PolynomialMatrix F} + {row : PolynomialRow F} + (hmem : row ∈ MatrixRows rows) (hnz : rowIsZero row = false) + (hsat : rowSatisfiesModularBool mulCtx modCtx row equation.matrix + equation.moduli = true) : + row ∈ MatrixRows (filterModularSolutionRows mulCtx modCtx equation rows) := by + rw [MatrixRows] at hmem + rw [MatrixRows, filterModularSolutionRows] + have h : row ∈ rows ∧ rowIsZero row = false ∧ + rowSatisfiesModularBool mulCtx modCtx row equation.matrix equation.moduli = true := + ⟨by simpa using hmem, hnz, hsat⟩ + simpa using h + +omit [LawfulBEq F] in +private theorem me_mem_compactNonzeroRows {rows : PolynomialMatrix F} + {row : PolynomialRow F} (hmem : row ∈ MatrixRows rows) + (hnz : rowIsZero row = false) : + row ∈ MatrixRows (compactNonzeroRows rows) := by + rw [MatrixRows] at hmem + rw [MatrixRows, compactNonzeroRows] + have h : row ∈ rows ∧ rowIsZero row = false := ⟨by simpa using hmem, hnz⟩ + simpa using h + +omit [BEq F] [LawfulBEq F] in +private theorem me_mem_principalSolutionRows {sW : Nat} {basis : PolynomialMatrix F} + {row : PolynomialRow F} (hmem : row ∈ MatrixRows basis) : + ((List.range sW).map fun j ↦ rowGet row j).toArray ∈ + MatrixRows (principalSolutionRows sW basis) := by + rw [MatrixRows, principalSolutionRows, Array.toList_map] + exact List.mem_map.mpr ⟨row, hmem, rfl⟩ + +omit [BEq F] [LawfulBEq F] in +/-- Principal solution rows keep the basis row width. -/ +theorem me_principalSolutionRows_width {sW : Nat} + {basis : PolynomialMatrix F} {row : PolynomialRow F} + (hrow : row ∈ MatrixRows (principalSolutionRows sW basis)) : + row.size = sW := by + rw [MatrixRows, principalSolutionRows, Array.toList_map] at hrow + rcases List.mem_map.mp hrow with ⟨r, _hr, rfl⟩ + simp + +/-! ### Width discipline of the adaptive pipeline -/ + +private theorem me_solutionBasisWithPlan_width + {modCtx : CPolynomial.ModContext F} {pmCtx : PMBasisContext F} + {equation : ModularEquation F} {shift : Array Nat} + {plan : PartialLinearizationPlan} {row : PolynomialRow F} + (hrow : row ∈ MatrixRows + (solutionBasisWithPlanViaPMBasis modCtx pmCtx equation shift plan)) : + row.size = plan.solutionWidth := by + simp only [solutionBasisWithPlanViaPMBasis] at hrow + have hsub := compactNonzeroRows_subset hrow + rw [MatrixRows, compressChunkedPrincipalRows, Array.toList_map] at hsub + rcases List.mem_map.mp hsub with ⟨r, _hr, rfl⟩ + simp [compressChunkedPrincipalRow] + +private theorem me_adaptiveRound_width + {mulCtx : CPolynomial.MulContext F} {modCtx : CPolynomial.ModContext F} + {pmCtx : PMBasisContext F} {equation : ModularEquation F} + {shift : Array Nat} {state : AdaptiveSolveState F} + (hstate : ∀ r ∈ MatrixRows state.filtered, r.size = equation.solutionWidth) : + ∀ r ∈ MatrixRows + (adaptiveSolutionRound mulCtx modCtx pmCtx equation shift state).filtered, + r.size = equation.solutionWidth := by + intro r hr + simp only [adaptiveSolutionRound, MatrixRows, Array.toList_append, + List.mem_append] at hr + rcases hr with hold | hnew + · exact hstate r hold + · have hsub := me_filterModularSolutionRows_subset hnew + have hsize := me_solutionBasisWithPlan_width hsub + rw [hsize] + simp [partialLinearizationPlanFromPivotDegrees] + +private theorem me_adaptiveLoop_width + {mulCtx : CPolynomial.MulContext F} {modCtx : CPolynomial.ModContext F} + {pmCtx : PMBasisContext F} {equation : ModularEquation F} + {shift : Array Nat} {cap : Nat} : + ∀ (fuel : Nat) (state : AdaptiveSolveState F), + (∀ r ∈ MatrixRows state.filtered, r.size = equation.solutionWidth) → + ∀ r ∈ MatrixRows + (adaptiveSolutionLoop mulCtx modCtx pmCtx equation shift cap fuel + state).filtered, + r.size = equation.solutionWidth := by + intro fuel + induction fuel with + | zero => + intro state hstate r hr + exact hstate r hr + | succ fuel ih => + intro state hstate r hr + rw [adaptiveSolutionLoop] at hr + have hnext := me_adaptiveRound_width (mulCtx := mulCtx) (modCtx := modCtx) + (pmCtx := pmCtx) (shift := shift) (state := state) hstate + split at hr + · exact hnext r hr + · split at hr + · exact hnext r hr + · dsimp only [] at hr + split at hr + · exact hnext r hr + · refine ih _ ?_ r hr + intro r' hr' + exact hnext r' hr' + +/-- Every adaptive solution-basis row has the linearized width. -/ +theorem me_adaptiveBasis_width + {mulCtx : CPolynomial.MulContext F} {modCtx : CPolynomial.ModContext F} + {pmCtx : PMBasisContext F} {equation : ModularEquation F} + {shift : Array Nat} : + ∀ r ∈ MatrixRows + (adaptiveSolutionBasis mulCtx modCtx pmCtx equation shift).filtered, + r.size = equation.solutionWidth := by + rw [adaptiveSolutionBasis] + exact me_adaptiveLoop_width _ _ (by intro r hr; simp [MatrixRows] at hr) + +/-! ### The fallback solution `e_p * prod(moduli)` -/ + +/-- Structure of the fallback row `e_p * prod(moduli)`. -/ +theorem me_prodRow_facts (mulCtx : CPolynomial.MulContext F) + (modCtx : CPolynomial.ModContext F) (equation : ModularEquation F) + (shift : Array Nat) {p : Nat} + (hmonic : ∀ b, b < equation.moduli.size → (equation.moduli.getD b 0).monic) + (hcols : equation.moduli.size ≤ MatrixWidth equation.matrix) + (hp : p < equation.solutionWidth) : + ∃ (prow : PolynomialRow F) (e : Nat), + rowSatisfiesModularBool mulCtx modCtx prow equation.matrix + equation.moduli = true ∧ + rowIsZero prow = false ∧ + prow.size = equation.solutionWidth ∧ + rowShiftedDegree? prow shift = some e ∧ + e ≤ pivotWindowCap equation + maxShiftDegree shift := by + classical + set sW := equation.solutionWidth with hsW + set prod := me_moduliProduct equation.moduli with hprodDef + set prow : PolynomialRow F := + ((List.range sW).map fun j ↦ if j = p then prod else 0).toArray with hprow + have hsize : prow.size = sW := by simp [hprow] + have hget : ∀ j, rowGet prow j = + if j < sW then (if j = p then prod else 0) else 0 := by + intro j + rw [hprow, rowGet, me_getD_list_range_map] + have hprodne : prod ≠ 0 := me_moduliProduct_ne_zero hmonic + have hnz : rowIsZero prow = false := by + refine me_rowIsZero_false_of_entry (j := p) (by omega) ?_ + rw [hget, if_pos hp, if_pos rfl] + exact hprodne + have hsat : rowSatisfiesModularBool mulCtx modCtx prow equation.matrix + equation.moduli = true := by + rw [me_rowSatisfies_iff] + intro b hb + rw [me_modByMonicWith_eq_zero_iff_dvd modCtx + ((CPolynomial.monic_toPoly_iff _).mp (hmonic b hb))] + rw [me_rowMul_toPoly mulCtx prow equation.matrix (lt_of_lt_of_le hb hcols) + (le_of_eq hsize)] + refine Finset.dvd_sum fun k hk ↦ ?_ + rw [hget, if_pos (Finset.mem_range.mp hk)] + by_cases hkp : k = p + · rw [if_pos hkp] + exact Dvd.dvd.mul_right (me_moduliProduct_dvd hb) _ + · rw [if_neg hkp, CPolynomial.toPoly_zero, zero_mul] + exact dvd_zero _ + obtain ⟨e, he⟩ := me_rowShiftedDegree_isSome (shift := shift) hnz + refine ⟨prow, e, hsat, hnz, hsize, he, ?_⟩ + obtain ⟨j, hjsize, hjne, hjeq⟩ := me_rowShiftedDegree_attained he + rw [hsize] at hjsize + have hjp : j = p := by + by_contra hne + refine hjne ?_ + rw [hget, if_pos hjsize, if_neg hne] + rw [hget, if_pos hjsize, if_pos hjp] at hjeq + have hdeg : prod.natDegree ≤ pivotWindowCap equation := by + rw [CPolynomial.natDegree_toPoly] + exact me_moduliProduct_natDegree_le equation.moduli + have hshiftle := me_shift_le_maxShiftDegree shift j + omega + +/-! ### The certified verification window -/ + +/-- Certified-window domination: any nonzero in-width modular solution row +whose shifted degree fits inside the verification window `bound` is +degree-dominated by a row of the filtered verification basis. -/ +theorem me_verification_dominates + (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) + (pmCtx : PMBasisContext F) (equation : ModularEquation F) + (shift : Array Nat) (bound : Nat) {rowStar : PolynomialRow F} {e : Nat} + (hmonic : ∀ b, b < equation.moduli.size → (equation.moduli.getD b 0).monic) + (hcols : equation.moduli.size ≤ MatrixWidth equation.matrix) + (hshift : shift.size = equation.solutionWidth) + (hpos : 0 < equation.solutionWidth) + (hsat : rowSatisfiesModularBool mulCtx modCtx rowStar equation.matrix + equation.moduli = true) + (hnz : rowIsZero rowStar = false) + (hwidth : rowStar.size ≤ equation.solutionWidth) + (hdeg : rowShiftedDegree? rowStar shift = some e) + (hebound : e ≤ bound) : + ∃ basisRow degree, + basisRow ∈ MatrixRows (filterModularSolutionRows mulCtx modCtx equation + (compactNonzeroRows (principalSolutionRows equation.solutionWidth + (pmCtx.basis (fullWindowExactNullspaceProblem modCtx equation bound) + (exactNullspaceShift shift equation.modularWidth bound))))) ∧ + rowShiftedDegree? basisRow shift = some degree ∧ degree ≤ e := by + classical + rw [show equation.modularWidth = equation.moduli.size from rfl] + set sW := equation.solutionWidth with hsWdef + set mW := equation.moduli.size with hmWdef + -- The reduced principal block and the lift matrix. + set Fred : PolynomialMatrix F := ofFn sW mW + (fun i j ↦ modByMonicWith modCtx (rowGet (equation.matrix.getD i #[]) j) + (equation.moduli.getD j 0)) with hFred + set liftM : PolynomialMatrix F := Fred ++ negativeDiagonalRows equation.moduli + with hliftM + have hFredSize : Fred.size = sW := ofFn_size _ _ _ + have hnegSize : (negativeDiagonalRows equation.moduli).size = mW := ofFn_size _ _ _ + have hliftSize : liftM.size = sW + mW := by + rw [hliftM, Array.size_append, hFredSize, hnegSize] + have hFredWidth : MatrixWidth Fred = mW := by + rw [hFred, MatrixWidth_ofFn, if_neg (by omega)] + have hFredEntry : ∀ {k b : Nat}, k < sW → b < mW → + rowGet (Fred.getD k #[]) b = + modByMonicWith modCtx (rowGet (equation.matrix.getD k #[]) b) + (equation.moduli.getD b 0) := by + intro k b hk hb + rw [hFred, rowGet_ofFn, if_pos ⟨hk, hb⟩] + have hliftWidth : MatrixWidth liftM = mW := by + rw [me_matrixWidth_eq_getD, hliftM, me_getD_append_left _ (by omega), hFred, + getD_ofFn, if_pos (by omega)] + simp + have hliftRows : ∀ r ∈ MatrixRows liftM, r.size = mW := by + intro r hr + rw [MatrixRows, hliftM, Array.toList_append, List.mem_append] at hr + rcases hr with hr | hr + · rcases List.getElem_of_mem hr with ⟨i, hi, hget⟩ + have hi' : i < Fred.size := by simpa using hi + have hgetD : Fred.getD i #[] = r := by + rw [Array.getD_eq_getD_getElem?, Array.getElem?_eq_getElem hi', Option.getD_some] + simpa [Array.getElem_toList] using hget + rw [← hgetD, hFred, getD_ofFn, if_pos (by omega)] + simp + · rcases List.getElem_of_mem hr with ⟨i, hi, hget⟩ + have hi' : i < (negativeDiagonalRows equation.moduli).size := by simpa using hi + have hgetD : (negativeDiagonalRows equation.moduli).getD i #[] = r := by + rw [Array.getD_eq_getD_getElem?, Array.getElem?_eq_getElem hi', Option.getD_some] + simpa [Array.getElem_toList] using hget + rw [← hgetD, negativeDiagonalRows, getD_ofFn, if_pos (by omega)] + simp only [List.size_toArray, List.length_map, List.length_range] + omega + have hwf : WellFormed liftM := by + intro r hr + rw [hliftWidth] + exact hliftRows r hr + have hliftL : ∀ {k b : Nat}, k < sW → b < mW → + rowGet (liftM.getD k #[]) b = + modByMonicWith modCtx (rowGet (equation.matrix.getD k #[]) b) + (equation.moduli.getD b 0) := by + intro k b hk hb + rw [hliftM, me_getD_append_left _ (by omega)] + exact hFredEntry hk hb + have hliftR : ∀ {c b : Nat}, c < mW → b < mW → + rowGet (liftM.getD (sW + c) #[]) b = + (if c == b then -(equation.moduli.getD c 0) else 0) := by + intro c b hc hb + rw [hliftM, me_getD_append_right _ (by omega)] + rw [show sW + c - Fred.size = c from by omega] + rw [negativeDiagonalRows, rowGet_ofFn, if_pos ⟨by omega, by omega⟩] + -- Monicity facts. + have hMon : ∀ {b : Nat}, b < mW → + Polynomial.Monic ((equation.moduli.getD b 0).toPoly) := + fun {b} hb ↦ (CPolynomial.monic_toPoly_iff _).mp (hmonic b hb) + have hMne : ∀ {b : Nat}, b < mW → ((equation.moduli.getD b 0).toPoly) ≠ 0 := + fun {b} hb ↦ (hMon hb).ne_zero + -- The verification problem and shift. + set P := fullWindowExactNullspaceProblem modCtx equation bound with hP + have hPmatrix : P.matrix = liftM := rfl + have hPordersSize : P.orders.size = mW := by + rw [hP, fullWindowExactNullspaceProblem] + simp only [Array.size_map] + omega + have hPorders : ∀ {b : Nat}, b < mW → + P.orders.getD b 0 = (equation.moduli.getD b 0).natDegree + bound + 2 := by + intro b hb + show (equation.moduli.map fun m ↦ m.natDegree + bound + 2).getD b 0 = _ + rw [Array.getD_eq_getD_getElem?, Array.getElem?_map, + Array.getElem?_eq_getElem (by omega), Option.map_some, Option.getD_some, + Array.getD_eq_getD_getElem?, Array.getElem?_eq_getElem (by omega), + Option.getD_some] + set eShift := exactNullspaceShift shift equation.moduli.size bound with heShift + have heShiftL : ∀ {k : Nat}, k < sW → + eShift.getD k 0 = shift.getD k 0 + bound + 1 := by + intro k hk + rw [heShift, exactNullspaceShift, liftedPrincipalShift, principalShiftOffset, + me_getD_append_left _ (by simp [hshift]; omega)] + rw [Array.getD_eq_getD_getElem?, Array.getElem?_map, + Array.getElem?_eq_getElem (by omega : k < shift.size), Option.map_some, + Option.getD_some, Array.getD_eq_getD_getElem?, + Array.getElem?_eq_getElem (by omega : k < shift.size), Option.getD_some] + omega + have heShiftR : ∀ {c : Nat}, c < mW → + eShift.getD (sW + c) 0 = bound := by + intro c hc + rw [heShift, exactNullspaceShift, quotientShift, + me_getD_append_right _ (by simp [liftedPrincipalShift, hshift])] + rw [show sW + c - (liftedPrincipalShift shift bound).size = c from by + simp [liftedPrincipalShift, hshift]] + exact me_getD_replicate _ _ hc + -- Divisibility of the original products. + have hdvdF : ∀ {b : Nat}, b < mW → + (equation.moduli.getD b 0).toPoly ∣ + ∑ k ∈ Finset.range sW, (rowGet rowStar k).toPoly * + (rowGet (equation.matrix.getD k #[]) b).toPoly := by + intro b hb + have hsat' := (me_rowSatisfies_iff mulCtx modCtx rowStar equation.matrix + equation.moduli).mp hsat b hb + have hdvd := (me_modByMonicWith_eq_zero_iff_dvd modCtx (hMon hb)).mp hsat' + rwa [me_rowMul_toPoly mulCtx rowStar equation.matrix + (lt_of_lt_of_le hb hcols) hwidth] at hdvd + -- The reduced products and their exact quotients. + set prodE := fun b ↦ rowGet (rowMulMatrixWith mulCtx rowStar Fred) b with hprodE + have hprodE_toPoly : ∀ {b : Nat}, b < mW → + (prodE b).toPoly = ∑ k ∈ Finset.range sW, (rowGet rowStar k).toPoly * + (rowGet (Fred.getD k #[]) b).toPoly := by + intro b hb + rw [hprodE] + exact me_rowMul_toPoly mulCtx rowStar Fred (by omega) hwidth + have hdvdRed : ∀ {b : Nat}, b < mW → + (equation.moduli.getD b 0).toPoly ∣ (prodE b).toPoly := by + intro b hb + have hdiff : (equation.moduli.getD b 0).toPoly ∣ + (prodE b).toPoly - + ∑ k ∈ Finset.range sW, (rowGet rowStar k).toPoly * + (rowGet (equation.matrix.getD k #[]) b).toPoly := by + rw [hprodE_toPoly hb, ← Finset.sum_sub_distrib] + refine Finset.dvd_sum fun k hk ↦ ?_ + have hk' : k < sW := Finset.mem_range.mp hk + rw [hFredEntry hk' hb] + have hsub := me_dvd_modByMonicWith_sub modCtx + (p := rowGet (equation.matrix.getD k #[]) b) (hMon hb) + have hfac : (rowGet rowStar k).toPoly * + (modByMonicWith modCtx (rowGet (equation.matrix.getD k #[]) b) + (equation.moduli.getD b 0)).toPoly - + (rowGet rowStar k).toPoly * + (rowGet (equation.matrix.getD k #[]) b).toPoly = + (rowGet rowStar k).toPoly * + ((modByMonicWith modCtx (rowGet (equation.matrix.getD k #[]) b) + (equation.moduli.getD b 0)).toPoly - + (rowGet (equation.matrix.getD k #[]) b).toPoly) := by + ring + rw [hfac] + exact Dvd.dvd.mul_left hsub _ + have hsum := dvd_add hdiff (hdvdF hb) + simpa using hsum + obtain ⟨quot, hquot⟩ : ∃ q : Nat → CPolynomial F, + ∀ b, q b = (prodE b).divByMonic (equation.moduli.getD b 0) := + ⟨fun b ↦ (prodE b).divByMonic (equation.moduli.getD b 0), fun _ ↦ rfl⟩ + have hquotExact : ∀ {b : Nat}, b < mW → + (quot b).toPoly * (equation.moduli.getD b 0).toPoly = (prodE b).toPoly := by + intro b hb + rw [hquot b] + exact me_divByMonic_mul_eq (hmonic b hb) (hdvdRed hb) + -- Degree bounds on the witness entries and quotients. + have hrowStarEntry : ∀ {k : Nat}, rowGet rowStar k ≠ 0 → + (rowGet rowStar k).toPoly.natDegree + shift.getD k 0 ≤ e := by + intro k hk + have h := me_entry_le_of_rowShiftedDegree hdeg hk + rwa [CPolynomial.natDegree_toPoly] at h + have hFredZero : ∀ {k b : Nat}, k < sW → b < mW → + (equation.moduli.getD b 0).natDegree = 0 → + (rowGet (Fred.getD k #[]) b).toPoly = 0 := by + intro k b hk hb hMdeg + rw [hFredEntry hk hb, me_modByMonicWith_toPoly modCtx (hMon hb)] + have hMone : (equation.moduli.getD b 0).toPoly = 1 := by + refine (Polynomial.Monic.natDegree_eq_zero (hMon hb)).mp ?_ + rwa [CPolynomial.natDegree_toPoly] at hMdeg + rw [hMone, Polynomial.modByMonic_one] + have hFredDegLt : ∀ {k b : Nat}, k < sW → b < mW → + (rowGet (Fred.getD k #[]) b).toPoly ≠ 0 → + (rowGet (Fred.getD k #[]) b).toPoly.natDegree < + (equation.moduli.getD b 0).natDegree := by + intro k b hk hb hne + rw [hFredEntry hk hb, me_modByMonicWith_toPoly modCtx (hMon hb)] at hne ⊢ + rw [CPolynomial.natDegree_toPoly] + exact Polynomial.natDegree_lt_natDegree hne + (Polynomial.degree_modByMonic_lt _ (hMon hb)) + have hquotDeg : ∀ {b : Nat}, b < mW → quot b ≠ 0 → + (quot b).toPoly.natDegree ≤ e := by + intro b hb hqz + have hqpoly : (quot b).toPoly ≠ 0 := + fun h ↦ hqz ((CPolynomial.toPoly_eq_zero_iff _).mp h) + by_cases hMdeg : (equation.moduli.getD b 0).natDegree = 0 + · exfalso + have hzero : (prodE b).toPoly = 0 := by + rw [hprodE_toPoly hb] + refine Finset.sum_eq_zero fun k hk ↦ ?_ + rw [hFredZero (Finset.mem_range.mp hk) hb hMdeg, mul_zero] + have hmul := hquotExact hb + rw [hzero] at hmul + rcases mul_eq_zero.mp hmul with h | h + · exact hqpoly h + · exact hMne hb h + · have h2 : (prodE b).toPoly.natDegree = + (quot b).toPoly.natDegree + (equation.moduli.getD b 0).natDegree := by + rw [← hquotExact hb, CPolynomial.natDegree_toPoly (equation.moduli.getD b 0)] + exact Polynomial.natDegree_mul hqpoly (hMne hb) + have h1 : (prodE b).toPoly.natDegree ≤ + e + (equation.moduli.getD b 0).natDegree - 1 := by + rw [hprodE_toPoly hb] + refine me_natDegree_sum_le _ fun k hk ↦ ?_ + by_cases hz : (rowGet rowStar k).toPoly = 0 + · rw [hz, zero_mul, Polynomial.natDegree_zero] + exact Nat.zero_le _ + by_cases hzF : (rowGet (Fred.getD k #[]) b).toPoly = 0 + · rw [hzF, mul_zero, Polynomial.natDegree_zero] + exact Nat.zero_le _ + · have hne : rowGet rowStar k ≠ 0 := + fun h ↦ hz (by rw [h, CPolynomial.toPoly_zero]) + have ha := hrowStarEntry hne + have hbnd := hFredDegLt hk hb hzF + refine le_trans Polynomial.natDegree_mul_le ?_ + omega + omega + -- The lifted exact-nullspace row. + set lifted : PolynomialRow F := ((List.range (sW + mW)).map + (fun k ↦ if k < sW then rowGet rowStar k else quot (k - sW))).toArray + with hlifted + have hliftedSize : lifted.size = sW + mW := by simp [hlifted] + have hliftedGet : ∀ k, rowGet lifted k = + if k < sW + mW then (if k < sW then rowGet rowStar k else quot (k - sW)) + else 0 := by + intro k + rw [hlifted, rowGet, me_getD_list_range_map] + have hliftedL : ∀ {k : Nat}, k < sW → rowGet lifted k = rowGet rowStar k := by + intro k hk + rw [hliftedGet, if_pos (by omega), if_pos hk] + have hliftedR : ∀ {c : Nat}, c < mW → rowGet lifted (sW + c) = quot c := by + intro c hc + rw [hliftedGet, if_pos (by omega), if_neg (by omega)] + rw [show sW + c - sW = c from by omega] + obtain ⟨j0, hj0, hj0ne⟩ := exists_nonzero_entry_of_rowIsZero_false hnz + have hj0sW : j0 < sW := by omega + have hnzL : rowIsZero lifted = false := by + refine me_rowIsZero_false_of_entry (j := j0) (by omega) ?_ + rw [hliftedL hj0sW] + exact hj0ne + -- The lifted row is an exact solution of the verification problem. + have happroxL : ∀ j, j < P.orders.size → + truncateX (P.orders.getD j 0) + (rowGet (rowMulMatrixWith pmCtx.runtime.mulContext lifted P.matrix) j) = 0 := by + intro j hj + have hjm : j < mW := by omega + have hzero : rowGet (rowMulMatrixWith pmCtx.runtime.mulContext lifted + P.matrix) j = 0 := by + refine (CPolynomial.toPoly_eq_zero_iff _).mp ?_ + rw [hPmatrix] + rw [me_rowMul_toPoly pmCtx.runtime.mulContext lifted liftM (by omega) + (le_of_eq hliftedSize)] + rw [me_sum_range_add (fun k ↦ (rowGet lifted k).toPoly * + (rowGet (liftM.getD k #[]) j).toPoly) sW mW] + have hfirst : ∑ k ∈ Finset.range sW, (rowGet lifted k).toPoly * + (rowGet (liftM.getD k #[]) j).toPoly = (prodE j).toPoly := by + rw [hprodE_toPoly hjm] + refine Finset.sum_congr rfl fun k hk ↦ ?_ + have hk' : k < sW := Finset.mem_range.mp hk + rw [hliftedL hk', hliftM, me_getD_append_left _ (by omega)] + have hsecond : ∑ k ∈ Finset.range mW, (rowGet lifted (sW + k)).toPoly * + (rowGet (liftM.getD (sW + k) #[]) j).toPoly = + -((quot j).toPoly * (equation.moduli.getD j 0).toPoly) := by + rw [Finset.sum_eq_single_of_mem j (Finset.mem_range.mpr hjm) ?_] + · rw [hliftedR hjm, hliftR hjm hjm, if_pos (by simp), + CPolynomial.toPoly_neg] + ring + · intro c hc hcj + rw [hliftR (Finset.mem_range.mp hc) hjm, if_neg (by simpa using hcj), + CPolynomial.toPoly_zero, mul_zero] + rw [hfirst, hsecond, hquotExact hjm] + ring + rw [hzero] + exact truncateX_zero _ + -- Apply the PM-basis minimality contract to the lifted row. + obtain ⟨bRow, Db, hbMem, hbSize, hbDeg, hbDom⟩ := + pmCtx.complete_minimal P eShift lifted (by rw [hPmatrix]; omega) + (by rw [hPmatrix]; exact hwf) happroxL hnzL + (by rw [hPmatrix]; omega) + have hbSizeLe : bRow.size ≤ sW + mW := by + rw [hPmatrix, hliftSize] at hbSize + exact hbSize + -- The lifted row stays inside the expanded window. + obtain ⟨Dl, hDl⟩ := me_rowShiftedDegree_isSome (shift := eShift) hnzL + have hDlle : Dl ≤ e + bound + 1 := by + obtain ⟨j, hjsize, hjne, hjeq⟩ := me_rowShiftedDegree_attained hDl + rw [hliftedSize] at hjsize + rcases Nat.lt_or_ge j sW with hjL | hjR + · rw [heShiftL hjL, hliftedL hjL] at hjeq + have hne' : rowGet rowStar j ≠ 0 := by rwa [hliftedL hjL] at hjne + have h := me_entry_le_of_rowShiftedDegree hdeg hne' + rw [CPolynomial.natDegree_toPoly] at h + rw [CPolynomial.natDegree_toPoly] at hjeq + omega + · have hcm : j - sW < mW := by omega + rw [show j = sW + (j - sW) from by omega] at hjne hjeq + rw [hliftedR hcm] at hjne hjeq + rw [heShiftR hcm] at hjeq + have hqd := hquotDeg hcm hjne + rw [CPolynomial.natDegree_toPoly] at hjeq + omega + have hDb : Db ≤ e + bound + 1 := le_trans (hbDom Dl hDl) hDlle + -- Entry bounds on the dominated basis row. + have hbEntryL : ∀ {k : Nat}, k < sW → rowGet bRow k ≠ 0 → + (rowGet bRow k).toPoly.natDegree + shift.getD k 0 ≤ e := by + intro k hk hne + have h := me_entry_le_of_rowShiftedDegree hbDeg hne + rw [heShiftL hk] at h + rw [CPolynomial.natDegree_toPoly] at h + omega + have hbEntryR : ∀ {c : Nat}, c < mW → rowGet bRow (sW + c) ≠ 0 → + (rowGet bRow (sW + c)).toPoly.natDegree ≤ e + 1 := by + intro c hc hne + have h := me_entry_le_of_rowShiftedDegree hbDeg hne + rw [heShiftR hc] at h + rw [CPolynomial.natDegree_toPoly] at h + omega + -- The verification orders force exact column products for the basis row. + have hsound := pmCtx.sound P eShift bRow hbMem + have hTzero : ∀ {j : Nat}, j < mW → + ∑ k ∈ Finset.range (sW + mW), (rowGet bRow k).toPoly * + (rowGet (liftM.getD k #[]) j).toPoly = 0 := by + intro j hjm + have hX := hsound j (by omega) + rw [truncateX_eq_zero_iff_X_pow_dvd] at hX + rw [hPmatrix] at hX + rw [me_rowMul_toPoly pmCtx.runtime.mulContext bRow liftM (by omega) + hbSizeLe] at hX + rw [hPorders hjm] at hX + refine me_eq_zero_of_X_pow_dvd_of_natDegree_lt hX ?_ + have hdegT : (∑ k ∈ Finset.range (sW + mW), (rowGet bRow k).toPoly * + (rowGet (liftM.getD k #[]) j).toPoly).natDegree ≤ + (equation.moduli.getD j 0).natDegree + bound + 1 := by + refine me_natDegree_sum_le _ fun k hk ↦ ?_ + by_cases hz : (rowGet bRow k).toPoly = 0 + · rw [hz, zero_mul, Polynomial.natDegree_zero] + exact Nat.zero_le _ + have hne : rowGet bRow k ≠ 0 := + fun h ↦ hz (by rw [h, CPolynomial.toPoly_zero]) + rcases Nat.lt_or_ge k sW with hkL | hkR + · by_cases hzF : (rowGet (liftM.getD k #[]) j).toPoly = 0 + · rw [hzF, mul_zero, Polynomial.natDegree_zero] + exact Nat.zero_le _ + · have hzF' : (rowGet (Fred.getD k #[]) j).toPoly ≠ 0 := by + rwa [hliftM, me_getD_append_left _ (by omega)] at hzF + have hMdeg1 : 1 ≤ (equation.moduli.getD j 0).natDegree := by + by_contra hcon + exact hzF' (hFredZero hkL hjm (by omega)) + have hFle := hFredDegLt hkL hjm hzF' + have ha := hbEntryL hkL hne + have hentry : rowGet (liftM.getD k #[]) j = rowGet (Fred.getD k #[]) j := by + rw [hliftM, me_getD_append_left _ (by omega)] + rw [hentry] + refine le_trans Polynomial.natDegree_mul_le ?_ + omega + · have hcm : k - sW < mW := by omega + rw [show k = sW + (k - sW) from by omega, hliftR hcm hjm] + by_cases hcj : k - sW = j + · rw [if_pos (by simpa using hcj)] + have hb1 : (rowGet bRow (sW + j)).toPoly.natDegree ≤ e + 1 := by + refine hbEntryR hjm ?_ + rwa [show sW + j = k from by omega] + refine le_trans Polynomial.natDegree_mul_le ?_ + rw [hcj, CPolynomial.toPoly_neg, Polynomial.natDegree_neg, + ← CPolynomial.natDegree_toPoly (equation.moduli.getD j 0)] + omega + · rw [if_neg (by simpa using hcj), CPolynomial.toPoly_zero, mul_zero, + Polynomial.natDegree_zero] + exact Nat.zero_le _ + omega + have hbProd : ∀ {j : Nat}, j < mW → + ∑ k ∈ Finset.range sW, (rowGet bRow k).toPoly * + (rowGet (Fred.getD k #[]) j).toPoly = + (rowGet bRow (sW + j)).toPoly * (equation.moduli.getD j 0).toPoly := by + intro j hjm + have h0 := hTzero hjm + rw [me_sum_range_add (fun k ↦ (rowGet bRow k).toPoly * + (rowGet (liftM.getD k #[]) j).toPoly) sW mW] at h0 + have hfirst : ∑ k ∈ Finset.range sW, (rowGet bRow k).toPoly * + (rowGet (liftM.getD k #[]) j).toPoly = + ∑ k ∈ Finset.range sW, (rowGet bRow k).toPoly * + (rowGet (Fred.getD k #[]) j).toPoly := by + refine Finset.sum_congr rfl fun k hk ↦ ?_ + have hk' : k < sW := Finset.mem_range.mp hk + rw [hliftM, me_getD_append_left _ (by omega)] + have hsecond : ∑ k ∈ Finset.range mW, (rowGet bRow (sW + k)).toPoly * + (rowGet (liftM.getD (sW + k) #[]) j).toPoly = + -((rowGet bRow (sW + j)).toPoly * (equation.moduli.getD j 0).toPoly) := by + rw [Finset.sum_eq_single_of_mem j (Finset.mem_range.mpr hjm) ?_] + · rw [hliftR hjm hjm, if_pos (by simp), CPolynomial.toPoly_neg] + ring + · intro c hc hcj + rw [hliftR (Finset.mem_range.mp hc) hjm, if_neg (by simpa using hcj), + CPolynomial.toPoly_zero, mul_zero] + rw [hfirst, hsecond] at h0 + have h0' : ∑ k ∈ Finset.range sW, (rowGet bRow k).toPoly * + (rowGet (Fred.getD k #[]) j).toPoly - + (rowGet bRow (sW + j)).toPoly * (equation.moduli.getD j 0).toPoly = 0 := by + rw [sub_eq_add_neg] + exact h0 + exact sub_eq_zero.mp h0' + -- The principal truncation of the basis row. + set bPrin : PolynomialRow F := + ((List.range sW).map fun j ↦ rowGet bRow j).toArray with hbPrin + have hbPrinSize : bPrin.size = sW := by simp [hbPrin] + have hbPrinGet : ∀ k, rowGet bPrin k = if k < sW then rowGet bRow k else 0 := by + intro k + rw [hbPrin, rowGet, me_getD_list_range_map] + have hbDvdF : ∀ {j : Nat}, j < mW → (equation.moduli.getD j 0).toPoly ∣ + ∑ k ∈ Finset.range sW, (rowGet bRow k).toPoly * + (rowGet (equation.matrix.getD k #[]) j).toPoly := by + intro j hjm + have hsumdvd : (equation.moduli.getD j 0).toPoly ∣ + ∑ k ∈ Finset.range sW, (rowGet bRow k).toPoly * + (rowGet (Fred.getD k #[]) j).toPoly := by + rw [hbProd hjm] + exact dvd_mul_left _ _ + have hdiff : (equation.moduli.getD j 0).toPoly ∣ + (∑ k ∈ Finset.range sW, (rowGet bRow k).toPoly * + (rowGet (Fred.getD k #[]) j).toPoly) - + ∑ k ∈ Finset.range sW, (rowGet bRow k).toPoly * + (rowGet (equation.matrix.getD k #[]) j).toPoly := by + rw [← Finset.sum_sub_distrib] + refine Finset.dvd_sum fun k hk ↦ ?_ + rw [hFredEntry (Finset.mem_range.mp hk) hjm] + have hsub := me_dvd_modByMonicWith_sub modCtx + (p := rowGet (equation.matrix.getD k #[]) j) (hMon hjm) + have hfac : (rowGet bRow k).toPoly * + (modByMonicWith modCtx (rowGet (equation.matrix.getD k #[]) j) + (equation.moduli.getD j 0)).toPoly - + (rowGet bRow k).toPoly * + (rowGet (equation.matrix.getD k #[]) j).toPoly = + (rowGet bRow k).toPoly * + ((modByMonicWith modCtx (rowGet (equation.matrix.getD k #[]) j) + (equation.moduli.getD j 0)).toPoly - + (rowGet (equation.matrix.getD k #[]) j).toPoly) := by + ring + rw [hfac] + exact Dvd.dvd.mul_left hsub _ + have h := dvd_sub hsumdvd hdiff + simpa using h + have hbPrinSat : rowSatisfiesModularBool mulCtx modCtx bPrin equation.matrix + equation.moduli = true := by + rw [me_rowSatisfies_iff] + intro b hb + rw [me_modByMonicWith_eq_zero_iff_dvd modCtx (hMon hb)] + rw [me_rowMul_toPoly mulCtx bPrin equation.matrix (lt_of_lt_of_le hb hcols) + (le_of_eq hbPrinSize)] + have hcong : ∑ k ∈ Finset.range sW, (rowGet bPrin k).toPoly * + (rowGet (equation.matrix.getD k #[]) b).toPoly = + ∑ k ∈ Finset.range sW, (rowGet bRow k).toPoly * + (rowGet (equation.matrix.getD k #[]) b).toPoly := by + refine Finset.sum_congr rfl fun k hk ↦ ?_ + rw [hbPrinGet, if_pos (Finset.mem_range.mp hk)] + rw [hcong] + exact hbDvdF hb + have hbPrinNz : rowIsZero bPrin = false := by + cases hzero : rowIsZero bPrin with + | false => rfl + | true => + exfalso + have hallP : ∀ k, k < sW → rowGet bRow k = 0 := by + intro k hk + have h := me_rowGet_eq_zero_of_rowIsZero hzero k + rwa [hbPrinGet, if_pos hk] at h + have hallQ : ∀ c, c < mW → rowGet bRow (sW + c) = 0 := by + intro c hc + have hprod := hbProd hc + have hzeroSum : ∑ k ∈ Finset.range sW, (rowGet bRow k).toPoly * + (rowGet (Fred.getD k #[]) c).toPoly = 0 := + Finset.sum_eq_zero fun k hk ↦ by + rw [hallP k (Finset.mem_range.mp hk), CPolynomial.toPoly_zero, + zero_mul] + rw [hzeroSum] at hprod + rcases mul_eq_zero.mp hprod.symm with h | h + · exact (CPolynomial.toPoly_eq_zero_iff _).mp h + · exact absurd h (hMne hc) + have hzRow : RowIsZero bRow := by + refine me_rowIsZero_of_forall fun k hk ↦ ?_ + rcases Nat.lt_or_ge k sW with h1 | h1 + · exact hallP k h1 + · have hcm : k - sW < mW := by omega + rw [show k = sW + (k - sW) from by omega] + exact hallQ _ hcm + have hnone := (rowShiftedDegree?_eq_none_iff + (row := bRow) (shift := eShift)).mpr hzRow + rw [hbDeg] at hnone + cases hnone + have hbPrinMemF : bPrin ∈ MatrixRows (filterModularSolutionRows mulCtx modCtx + equation (compactNonzeroRows (principalSolutionRows sW + (pmCtx.basis P eShift)))) := by + refine me_mem_filterModularSolutionRows ?_ hbPrinNz hbPrinSat + refine me_mem_compactNonzeroRows ?_ hbPrinNz + exact me_mem_principalSolutionRows hbMem + obtain ⟨degB, hdegB⟩ := me_rowShiftedDegree_isSome (shift := shift) hbPrinNz + have hdegBle : degB ≤ e := by + obtain ⟨j, hjsize, hjne, hjeq⟩ := me_rowShiftedDegree_attained hdegB + rw [hbPrinSize] at hjsize + have hjne' : rowGet bRow j ≠ 0 := by rwa [hbPrinGet, if_pos hjsize] at hjne + have h := hbEntryL hjsize hjne' + rw [hbPrinGet, if_pos hjsize] at hjeq + rw [CPolynomial.natDegree_toPoly] at hjeq + omega + exact ⟨bPrin, degB, hbPrinMemF, hdegB, hdegBle⟩ + +end CompleteMinimal + +end Approximant + +end PolynomialMatrix + +end CompPoly diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis.lean b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis.lean index 0bfc9c74..a6cf1c84 100644 --- a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis.lean +++ b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis.lean @@ -4,16 +4,14 @@ Released under Apache 2.0 license as described in the file LICENSE. Authors: Valerii Huhnin -/ -import CompPoly.LinearAlgebra.Dense -import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant.Basic +import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant.PMBasis.Correctness /-! -# Recursive PM-Basis Skeleton +# Recursive PM-Basis -An executable divide-and-conquer PM-basis driver parameterized by an explicit -leaf solver. The leaf is the only classical hook; production contexts choose -the cutoff and leaf implementation, while the recursive residual/composition -structure is shared. +Umbrella module for the divide-and-conquer PM-basis: executable definitions, +correctness development, and the production `PMBasisContext` instances backed +by the recursive driver with scalar dense-kernel leaves. -/ namespace CompPoly @@ -24,495 +22,6 @@ namespace Approximant variable {F : Type*} [Field F] [BEq F] [LawfulBEq F] -/-- Coefficient degree cap used by the classical scalar-kernel PM-basis leaf. -/ -def leafDegreeCap (problem : XAdicProblem F) : Nat := - max 1 (maxOrder problem) - -/-- Coefficient-equation indices `(column, coefficientDegree)`. -/ -def coefficientEquationIndices (orders : Array Nat) : Array (Nat × Nat) := Id.run do - let mut out := #[] - for j in [0:orders.size] do - for t in [0:orders.getD j 0] do - out := out.push (j, t) - pure out - -/-- Dense scalar coefficient matrix for the bounded leaf problem. -/ -def coefficientMatrix (problem : XAdicProblem F) : DenseMatrix F := - let degreeCap := leafDegreeCap problem - let equations := coefficientEquationIndices problem.orders - DenseMatrix.ofFn equations.size (problem.matrix.size * degreeCap) fun row col ↦ - let equation := equations.getD row (0, 0) - let matrixCol := equation.1 - let coeffDegree := equation.2 - let coord := col / degreeCap - let coordDegree := col % degreeCap - if coordDegree ≤ coeffDegree then - CPolynomial.coeff - (PolynomialMatrix.rowGet (problem.matrix.getD coord #[]) matrixCol) - (coeffDegree - coordDegree) - else - 0 - -/-- One scalar coefficient row for the bounded leaf problem. -/ -def coefficientMatrixRow (problem : XAdicProblem F) (degreeCap : Nat) - (equation : Nat × Nat) : Array F := - (List.range (problem.matrix.size * degreeCap)).map - (fun col ↦ - let matrixCol := equation.1 - let coeffDegree := equation.2 - let coord := col / degreeCap - let coordDegree := col % degreeCap - if coordDegree ≤ coeffDegree then - CPolynomial.coeff - (PolynomialMatrix.rowGet (problem.matrix.getD coord #[]) matrixCol) - (coeffDegree - coordDegree) - else - 0) |>.toArray - -/-- Scalar coefficient rows for the bounded leaf problem. This is the same -matrix as `coefficientMatrix`, represented directly as row arrays for the tiny -leaf RREF routine. -/ -def coefficientMatrixRows (problem : XAdicProblem F) : Array (Array F) := - let degreeCap := leafDegreeCap problem - (coefficientEquationIndices problem.orders).map - (coefficientMatrixRow problem degreeCap) - -/-- Swap two scalar rows in a row-array matrix. -/ -def swapScalarRows (rows : Array (Array F)) (rowA rowB : Nat) : - Array (Array F) := - let a := rows.getD rowA #[] - let b := rows.getD rowB #[] - (rows.setIfInBounds rowA b).setIfInBounds rowB a - -/-- Find a nonzero pivot row at or below `startRow` in column `col`. -/ -def findScalarPivotRow (rows : Array (Array F)) (startRow col : Nat) : - Option Nat := - (List.range' startRow (rows.size - startRow)).find? fun row ↦ - (rows.getD row #[]).getD col 0 != 0 - -/-- Scale a scalar row so that column `pivotCol` becomes one. -/ -def normalizeScalarRow (row : Array F) (pivotCol : Nat) : Array F := - let pivot := row.getD pivotCol 0 - if pivot == 0 then - row - else - row.map fun x ↦ x / pivot - -/-- Add `factor * source` to `target`, using zero defaults for ragged rows. -/ -def addScaledScalarRow (target source : Array F) (factor : F) : Array F := - (List.range (max target.size source.size)).map - (fun col ↦ target.getD col 0 + factor * source.getD col 0) |>.toArray - -/-- Normalize one pivot row and clear the pivot column in all other rows. -/ -def normalizeAndEliminateScalarRows (rows : Array (Array F)) - (pivotRow pivotCol : Nat) : Array (Array F) := - let pivot := (rows.getD pivotRow #[]).getD pivotCol 0 - if pivot == 0 then - rows - else - let pivotVector := normalizeScalarRow (rows.getD pivotRow #[]) pivotCol - let rows := rows.setIfInBounds pivotRow pivotVector - (List.range rows.size).foldl - (fun rows row ↦ - if row == pivotRow then - rows - else - let factor := -((rows.getD row #[]).getD pivotCol 0) - if factor == 0 then - rows - else - rows.setIfInBounds row - (addScaledScalarRow (rows.getD row #[]) pivotVector factor)) - rows - -/-- RREF result for a scalar row-array matrix. -/ -structure ScalarRrefResult where - rows : Array (Array F) - pivots : Array Nat - -/-- Fuel-bounded row-array RREF for tiny scalar coefficient matrices. -/ -def scalarRrefRowsLoop (cols : Nat) : - Nat → Nat → Nat → Array (Array F) → Array Nat → ScalarRrefResult (F := F) - | 0, _col, _row, rows, pivots => { rows := rows, pivots := pivots } - | fuel + 1, col, row, rows, pivots => - if col >= cols || row >= rows.size then - { rows := rows, pivots := pivots } - else - match findScalarPivotRow rows row col with - | none => scalarRrefRowsLoop cols fuel (col + 1) row rows pivots - | some pivotRow => - let swapped := swapScalarRows rows pivotRow row - let reduced := normalizeAndEliminateScalarRows swapped row col - scalarRrefRowsLoop cols fuel (col + 1) (row + 1) reduced - (pivots.push col) - -/-- Row-array RREF for tiny scalar coefficient matrices. -/ -def scalarRrefRows (rows : Array (Array F)) (cols : Nat) : - ScalarRrefResult (F := F) := - scalarRrefRowsLoop cols (cols + 1) 0 0 rows #[] - -/-- Kernel basis vector for one free column of a row-array RREF matrix. -/ -def basisVectorForFreeColumnRows (rows : Array (Array F)) - (pivots : Array Nat) (cols free : Nat) : Array F := - Array.ofFn fun i : Fin cols ↦ - if i.val == free then - 1 - else - match DenseMatrix.pivotRowOfColumn? pivots i.val with - | none => 0 - | some row => -((rows.getD row #[]).getD free 0) - -/-- Homogeneous scalar-kernel basis for a row-array matrix. -/ -def homogeneousKernelBasisRows (rows : Array (Array F)) (cols : Nat) : - Array (Array F) := - let R := scalarRrefRows rows cols - (DenseMatrix.freeColumns cols R.pivots).map - (basisVectorForFreeColumnRows R.rows R.pivots cols) - -/-- Convert one scalar kernel vector back into a polynomial row. -/ -def vectorToPolynomialRow (degreeCap solutionWidth : Nat) (v : Array F) : - PolynomialRow F := - (List.range solutionWidth).map - (fun coord ↦ - CPolynomial.ofArray - ((List.range degreeCap).map - (fun degree ↦ v.getD (coord * degreeCap + degree) 0) |>.toArray)) |>.toArray - -/-- Polynomial `c * X^d`, built without the `CPolynomial.monomial` -`DecidableEq` assumption. -/ -def coeffXPower (c : F) (d : Nat) : CPolynomial F := - CPolynomial.ofArray ((Array.replicate d 0).push c) - -/-- Multiply a row by `c * X^d`, using the coefficient-array monomial builder. -/ -def polynomialScaleCoeffX (c : F) (d : Nat) (p : CPolynomial F) : - CPolynomial F := - if c == 0 then - 0 - else if p == 0 then - 0 - else - CPolynomial.ofArray - ((List.replicate d 0 ++ p.val.toList.map (fun a ↦ c * a)).toArray) - -/-- Multiply a row by `c * X^d`, using coefficient shifting instead of generic -polynomial multiplication by a monomial. -/ -def rowScaleCoeffX (c : F) (d : Nat) (row : PolynomialRow F) : - PolynomialRow F := - row.map fun p ↦ polynomialScaleCoeffX c d p - -/-- Monomial row `X^d * e_i`, used to complete bounded-kernel leaves to a full -approximant basis. -/ -def monomialUnitRow (width i d : Nat) : PolynomialRow F := - (List.range width).map - (fun j ↦ if i == j then coeffXPower 1 d else 0) |>.toArray - -/-- Trivial high-degree approximants present in every X-adic problem. These -rows are essential when the bounded scalar kernel has fewer rows than the module -rank. -/ -def kernelLeafCompletionRows (problem : XAdicProblem F) : - PolynomialMatrix F := - let degreeCap := leafDegreeCap problem - (List.range problem.matrix.size).map - (fun i ↦ monomialUnitRow problem.matrix.size i degreeCap) |>.toArray - -/-- Whether a row set already contains a row with a given shifted leading -position. -/ -def rowsContainLeadingPosition (rows : PolynomialMatrix F) - (shift : Array Nat) (position : Nat) : Bool := - rows.any fun row ↦ - match rowShiftedLeadingPosition? row shift with - | some p => p == position - | none => false - -/-- High monomial rows for shifted leading positions not represented by `rows`. -These rows are always valid approximants and keep recursive residual problems -from losing coordinates after compact row reduction. -/ -def missingCompletionRows (problem : XAdicProblem F) - (shift : Array Nat) (rows : PolynomialMatrix F) : - PolynomialMatrix F := - let degreeCap := leafDegreeCap problem - (List.range problem.matrix.size).filterMap - (fun i ↦ - if rowsContainLeadingPosition rows shift i then - none - else - some (monomialUnitRow problem.matrix.size i degreeCap)) |>.toArray - -/-- Add high monomial approximants for missing pivot positions. -/ -def completeMissingPivotRows (problem : XAdicProblem F) - (shift : Array Nat) (rows : PolynomialMatrix F) : - PolynomialMatrix F := - rows ++ missingCompletionRows problem shift rows - -/-- Cancel the shifted leading term of `target` by `reducer`, when their shifted -leading positions agree. This is the small-leaf analogue of polynomial-matrix -row reduction; it is used only after the bounded scalar kernel has already been -computed. -/ -def cancelKernelLeafLeadingTerm - (target reducer : PolynomialRow F) (shift : Array Nat) : PolynomialRow F := - match rowShiftedLeadingTerm? target shift, rowShiftedLeadingTerm? reducer shift with - | some t, some r => - if t.position == r.position then - if r.coeff == 0 then - target - else - rowSub target (rowScaleCoeffX (t.coeff / r.coeff) (t.degree - r.degree) reducer) - else - target - | _, _ => target - -/-- One inner-loop update for finding a leading-position conflict in a bounded -kernel leaf. -/ -def kernelLeafConflictInRowStep? (rows : PolynomialMatrix F) (shift : Array Nat) - (i : Nat) (found : Option (Nat × Nat)) (j : Nat) : Option (Nat × Nat) := - match found with - | some _ => found - | none => - match rowShiftedLeadingPosition? (rows.getD i #[]) shift, - rowShiftedLeadingPosition? (rows.getD j #[]) shift with - | some pi, some pj => if pi == pj then some (i, j) else none - | _, _ => none - -/-- Scan one row for a shifted-leading-position conflict in a bounded kernel -leaf. -/ -def kernelLeafConflictInRow? (rows : PolynomialMatrix F) (shift : Array Nat) - (i : Nat) (found : Option (Nat × Nat)) : Option (Nat × Nat) := - (List.range' (i + 1) (rows.size - (i + 1))).foldl - (kernelLeafConflictInRowStep? rows shift i) found - -/-- Scan all row pairs for the first shifted-leading-position conflict. -/ -def kernelLeafConflictFrom? (rows : PolynomialMatrix F) (shift : Array Nat) - (found : Option (Nat × Nat)) : Option (Nat × Nat) := - (List.range rows.size).foldl - (fun acc i ↦ kernelLeafConflictInRow? rows shift i acc) found - -/-- First pair of nonzero bounded-kernel rows with the same shifted leading -position. -/ -def kernelLeafConflict? (rows : PolynomialMatrix F) (shift : Array Nat) : - Option (Nat × Nat) := - kernelLeafConflictFrom? rows shift none - -/-- One shifted-reduction step for bounded scalar-kernel rows. -/ -def reduceKernelLeafStep (rows : PolynomialMatrix F) (shift : Array Nat) - (i j : Nat) : PolynomialMatrix F := - let rowI := rows.getD i #[] - let rowJ := rows.getD j #[] - match rowShiftedDegree? rowI shift, rowShiftedDegree? rowJ shift with - | some degI, some degJ => - if degI ≤ degJ then - replaceRow rows j (cancelKernelLeafLeadingTerm rowJ rowI shift) - else - replaceRow rows i (cancelKernelLeafLeadingTerm rowI rowJ shift) - | _, _ => rows - -/-- Fuel for bounded-kernel shifted reduction. The scalar leaf is already a -small base case, so this conservative degree-width bound is acceptable here. -/ -def reduceKernelLeafFuel (rows : PolynomialMatrix F) (shift : Array Nat) : Nat := - let maxDegree := (List.range rows.size).foldl - (fun acc i ↦ - match rowShiftedDegree? (rows.getD i #[]) shift with - | none => acc - | some degree => max acc degree) - 0 - (rows.size + 1) * (MatrixWidth rows + 1) * (maxDegree + 1) - -/-- Extract the nonempty pivot rows from a leading-position table. -/ -def pivotRows (pivots : Array (Option (PolynomialRow F))) : - PolynomialMatrix F := - pivots.toList.filterMap id |>.toArray - -/-- Insert one row into a shifted weak-Popov pivot table. Conflicts are resolved -only at the current leading position, avoiding the repeated global pair scans -used by the simple reference reducer. -/ -def insertKernelLeafPivotRowWithFuel : - Nat → Array (Option (PolynomialRow F)) → Array Nat → PolynomialRow F → - Array (Option (PolynomialRow F)) - | 0, pivots, _shift, _row => pivots - | fuel + 1, pivots, shift, row => - match rowShiftedLeadingTerm? row shift with - | none => pivots - | some target => - match pivots.getD target.position none with - | none => pivots.setIfInBounds target.position (some row) - | some pivot => - match rowShiftedLeadingTerm? pivot shift with - | none => pivots.setIfInBounds target.position (some row) - | some reducer => - if target.shiftedDegree < reducer.shiftedDegree then - let reducedPivot := cancelKernelLeafLeadingTerm pivot row shift - let pivots := pivots.setIfInBounds target.position (some row) - insertKernelLeafPivotRowWithFuel fuel pivots shift reducedPivot - else - let reducedRow := cancelKernelLeafLeadingTerm row pivot shift - insertKernelLeafPivotRowWithFuel fuel pivots shift reducedRow - -/-- Pivot-table shifted reduction for bounded scalar-kernel rows. -/ -def reduceKernelLeafRowsByPivots (rows : PolynomialMatrix F) (shift : Array Nat) : - PolynomialMatrix F := - let fuel := reduceKernelLeafFuel rows shift - let pivots := rows.foldl - (fun pivots row ↦ insertKernelLeafPivotRowWithFuel fuel pivots shift row) - (Array.replicate (MatrixWidth rows) none) - pivotRows pivots - -/-- Shift-reduce the bounded scalar-kernel rows before compacting them. This -keeps one low representative per shifted leading position instead of selecting -arbitrary low-degree kernel vectors. -/ -def reduceKernelLeafWithFuel : - Nat → PolynomialMatrix F → Array Nat → PolynomialMatrix F - | 0, rows, _shift => rows - | fuel + 1, rows, shift => - match kernelLeafConflict? rows shift with - | none => rows - | some (i, j) => - reduceKernelLeafWithFuel fuel (reduceKernelLeafStep rows shift i j) shift - -/-- Shift-reduced bounded scalar-kernel rows for the PM-basis leaf. -/ -def reduceKernelLeafRows (rows : PolynomialMatrix F) (shift : Array Nat) : - PolynomialMatrix F := - reduceKernelLeafRowsByPivots rows shift - -/-- Insert one bounded-kernel row into a small shifted-reduced leaf basis. This -keeps the live reduction matrix near the module width instead of reducing the -entire scalar kernel at once. -/ -def insertKernelLeafRowIncremental (basis : PolynomialMatrix F) - (shift : Array Nat) (row : PolynomialRow F) : PolynomialMatrix F := - (reduceKernelLeafRows (basis.push row) shift).filter fun row ↦ !rowIsZero row - -/-- Shift-reduce all bounded scalar-kernel rows incrementally. The dense scalar -kernel can have many rows, but after every insertion the weak-Popov conflict loop -works on the current reduced basis plus one candidate row. -/ -def reduceKernelLeafRowsIncremental (rows : PolynomialMatrix F) - (shift : Array Nat) : PolynomialMatrix F := - rows.foldl (fun basis row ↦ insertKernelLeafRowIncremental basis shift row) #[] - -/-- Classical scalar-kernel leaf for small X-adic approximant problems. -/ -def kernelLeafBasis (problem : XAdicProblem F) (shift : Array Nat) : - PolynomialMatrix F := - let degreeCap := leafDegreeCap problem - let scalarRows := (homogeneousKernelBasisRows (coefficientMatrixRows problem) - (problem.matrix.size * degreeCap)).map - (vectorToPolynomialRow degreeCap problem.matrix.size) - completeMissingPivotRows problem shift - (reduceKernelLeafRowsIncremental - (scalarRows ++ kernelLeafCompletionRows problem) shift) - -/-- Remove zero rows before a recursively computed approximant basis is used as -the coordinate system for the next residual problem. -/ -def compactNonzeroRows (rows : PolynomialMatrix F) : PolynomialMatrix F := - rows.filter fun row ↦ !rowIsZero row - -/-- Runtime data for recursive PM-basis computation. -/ -structure PMBasisRuntime (F : Type*) [Field F] [BEq F] [LawfulBEq F] where - mulContext : CPolynomial.MulContext F - lowMulContext : PolynomialMatrix.MulLowContext F - composeBasis : PolynomialMatrix F → PolynomialMatrix F → PolynomialMatrix F - residualProduct : - Array Nat → PolynomialMatrix F → PolynomialMatrix F → PolynomialMatrix F - leafCutoff : Nat - leafBasis : XAdicProblem F → Array Nat → PolynomialMatrix F - -/-- Recursive PM-basis runtime using the scalar dense-kernel routine as its -small-leaf solver and an independently tuned basis-composition cutoff. -/ -def kernelLeafRuntimeWithLowAndCompose (mulCtx : CPolynomial.MulContext F) - (lowCtx : PolynomialMatrix.MulLowContext F) - (leafCutoff composeLeafCutoff : Nat) : - PMBasisRuntime F where - mulContext := mulCtx - lowMulContext := lowCtx - composeBasis := PolynomialMatrix.mulStrassenWith lowCtx composeLeafCutoff - residualProduct := PolynomialMatrix.mulTruncColumnStrassenWith lowCtx - composeLeafCutoff - leafCutoff := leafCutoff - leafBasis := kernelLeafBasis - -/-- Recursive PM-basis runtime using the scalar dense-kernel routine as its -small-leaf solver. -/ -def kernelLeafRuntimeWithLow (mulCtx : CPolynomial.MulContext F) - (lowCtx : PolynomialMatrix.MulLowContext F) (leafCutoff : Nat) : - PMBasisRuntime F := - kernelLeafRuntimeWithLowAndCompose mulCtx lowCtx leafCutoff leafCutoff - -/-- Compatibility runtime whose low products are obtained by truncating full -products. -/ -def kernelLeafRuntime (mulCtx : CPolynomial.MulContext F) (leafCutoff : Nat) : - PMBasisRuntime F := - kernelLeafRuntimeWithLow mulCtx (PolynomialMatrix.MulLowContext.fromMulContext mulCtx) - leafCutoff - -/-- Fuel-bounded recursive PM-basis driver, compacting zero rows after each -leaf and composition step. - -Internal nodes return the composed product `P₂ * P₁` without re-reduction: -composition of minimal half-bases under the updated shift is itself a basis of -the full-order approximant module, so re-reducing at every node would only add -work that is quadratic in the row degrees and outside the PM-basis cost model. -A single weak-Popov normalization pass runs once at the root entry points. -/ -def pmBasisWithFuelCore (runtime : PMBasisRuntime F) : - Nat → XAdicProblem F → Array Nat → PolynomialMatrix F - | 0, problem, shift => compactNonzeroRows (runtime.leafBasis problem shift) - | fuel + 1, problem, shift => - let order := maxOrder problem - if order ≤ runtime.leafCutoff || order ≤ 1 then - compactNonzeroRows (runtime.leafBasis problem shift) - else - let d₁ := order / 2 - let lower : XAdicProblem F := - { orders := lowerOrders problem d₁, matrix := problem.matrix } - let P₁ := pmBasisWithFuelCore runtime fuel lower shift - let residualOrders := residualOrders problem d₁ - let residual : XAdicProblem F := - { orders := residualOrders - matrix := residualMatrixWithProduct runtime.residualProduct P₁ - problem.matrix d₁ residualOrders } - let shifted := updateShiftByRows P₁ shift - let P₂ := pmBasisWithFuelCore runtime fuel residual shifted - compactNonzeroRows (runtime.composeBasis P₂ P₁) - -/-- Root normalization for a recursively composed approximant basis: one -weak-Popov reduction pass plus completion rows for any leading position that -lost its representative. When the recursion preserved minimality this pass -performs no cascading cancellations; it is a semantic guard, not part of the -recursive cost model. -/ -def pmBasisNormalizeRoot (problem : XAdicProblem F) (shift : Array Nat) - (basis : PolynomialMatrix F) : PolynomialMatrix F := - completeMissingPivotRows problem shift - (compactNonzeroRows (reduceKernelLeafRows basis shift)) - -/-- Fuel-bounded recursive PM-basis driver with root normalization. -/ -def pmBasisWithFuel (runtime : PMBasisRuntime F) - (fuel : Nat) (problem : XAdicProblem F) (shift : Array Nat) : - PolynomialMatrix F := - pmBasisNormalizeRoot problem shift (pmBasisWithFuelCore runtime fuel problem shift) - -/-- Default fuel choice, large enough to split each positive order down to the -leaf cutoff. -/ -def pmBasisFuel [Zero F] (problem : XAdicProblem F) : Nat := - maxOrder problem + 1 - -/-- Recursive PM-basis entry point. -/ -def pmBasis (runtime : PMBasisRuntime F) - (problem : XAdicProblem F) (shift : Array Nat) : PolynomialMatrix F := - pmBasisWithFuel runtime (pmBasisFuel problem) problem shift - -/-- Context packaging the executable PM-basis operation with theorem fields. -/ -structure PMBasisContext (F : Type*) [Field F] [BEq F] [LawfulBEq F] where - runtime : PMBasisRuntime F - basis : XAdicProblem F → Array Nat → PolynomialMatrix F := pmBasis runtime - sound : - ∀ problem shift row, - row ∈ MatrixRows (basis problem shift) → - ∀ j, j < problem.orders.size → - truncateX (problem.orders.getD j 0) - (rowGet (rowMulMatrixWith runtime.mulContext row problem.matrix) j) = 0 - complete_minimal : - ∀ problem shift row, - (∀ j, j < problem.orders.size → - truncateX (problem.orders.getD j 0) - (rowGet (rowMulMatrixWith runtime.mulContext row problem.matrix) j) = 0) → - ∃ basisRow, - basisRow ∈ MatrixRows (basis problem shift) - /-- PM-basis context backed by the recursive driver, scalar dense-kernel leaves, and an independently tuned basis-composition cutoff. -/ def kernelLeafPMBasisContextWithLowAndCompose (mulCtx : CPolynomial.MulContext F) @@ -523,9 +32,17 @@ def kernelLeafPMBasisContextWithLowAndCompose (mulCtx : CPolynomial.MulContext F basis := pmBasis (kernelLeafRuntimeWithLowAndCompose mulCtx lowCtx leafCutoff composeLeafCutoff) sound := by - sorry + intro problem shift row hrow + exact pmBasis_kernelLeaf_approximates mulCtx lowCtx leafCutoff + composeLeafCutoff problem shift hrow complete_minimal := by - sorry + -- Shifted-minimality of the recursive PM-basis (the predictable-degree + -- property of minimal approximant bases): every nonzero X-adic solution + -- row is degree-dominated by some basis row. + letI : DecidableEq F := instDecidableEqOfLawfulBEq + intro problem shift row hpos hwf happrox hnz hwidth + exact pmBasis_kernelLeaf_complete_minimal mulCtx lowCtx leafCutoff + composeLeafCutoff problem shift row hpos hwf happrox hnz hwidth /-- PM-basis context backed by the recursive driver and scalar dense-kernel leaves. -/ diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/Correctness.lean b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/Correctness.lean new file mode 100644 index 00000000..5216d402 --- /dev/null +++ b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/Correctness.lean @@ -0,0 +1,1225 @@ +/- +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.LinearAlgebra.PolynomialMatrix.Approximant.PMBasis.Recursion +import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant.PMBasis.KernelLeafCompleteness +import CompPoly.LinearAlgebra.PolynomialMatrix.MuldersStorjohannCorrectness.WeakPopovMinimal + +/-! +# Recursive PM-Basis Correctness + +Soundness and shifted minimality of the kernel-leaf recursive PM-basis: +every produced row satisfies the X-adic conditions with the principal width, +the recursion generates the full solution module, and the root-normalized +basis is shifted weak Popov, so the predictable-degree property yields a +basis row dominating every nonzero solution. +-/ + +namespace CompPoly + +namespace PolynomialMatrix + +namespace Approximant + +variable {F : Type*} [Field F] [BEq F] [LawfulBEq F] + +/-- Every row of the fuel-bounded kernel-leaf PM-basis core approximates the +problem and has the principal row width. -/ +theorem pmBasisWithFuelCore_kernelLeaf_rows (mulCtx : CPolynomial.MulContext F) + (lowCtx : PolynomialMatrix.MulLowContext F) + (leafCutoff composeLeafCutoff : Nat) : + ∀ (fuel : Nat) (problem : XAdicProblem F) (shift : Array Nat), + ∀ row ∈ MatrixRows (pmBasisWithFuelCore + (kernelLeafRuntimeWithLowAndCompose mulCtx lowCtx leafCutoff + composeLeafCutoff) fuel problem shift), + RowApproximates mulCtx problem row ∧ row.size = problem.matrix.size := by + intro fuel + induction fuel with + | zero => + intro problem shift row hrow + rw [pmBasisWithFuelCore] at hrow + exact kernelLeafBasis_rows mulCtx problem shift row + (compactNonzeroRows_subset hrow) + | succ fuel ih => + intro problem shift row hrow + rw [pmBasisWithFuelCore] at hrow + split at hrow + · exact kernelLeafBasis_rows mulCtx problem shift row + (compactNonzeroRows_subset hrow) + · set d₁ := maxOrder problem / 2 with hd₁ + set lower : XAdicProblem F := + { orders := lowerOrders problem d₁, matrix := problem.matrix } with hlower + set P₁ := pmBasisWithFuelCore + (kernelLeafRuntimeWithLowAndCompose mulCtx lowCtx leafCutoff + composeLeafCutoff) fuel lower shift with hP₁def + set resOrders := residualOrders problem d₁ with hresOrders + set Rmat := residualMatrixWithProduct + (mulTruncColumnStrassenWith lowCtx composeLeafCutoff) P₁ + problem.matrix d₁ resOrders with hRmatdef + set residual : XAdicProblem F := + { orders := resOrders, matrix := Rmat } with hresidual + set shifted := updateShiftByRows P₁ shift with hshifted + set P₂ := pmBasisWithFuelCore + (kernelLeafRuntimeWithLowAndCompose mulCtx lowCtx leafCutoff + composeLeafCutoff) fuel residual shifted with hP₂def + have hrow2 : row ∈ MatrixRows (compactNonzeroRows + (mulStrassenWith lowCtx composeLeafCutoff P₂ P₁)) := hrow + have hmem := compactNonzeroRows_subset hrow2 + have hnz := compactNonzeroRows_nonzero hrow2 + rw [mulStrassenWith_eq_mulWith, matrixRows_mulWith] at hmem + rcases List.mem_map.mp hmem with ⟨p₂, hp₂mem, rfl⟩ + have hctx : rowMulMatrixWith lowCtx.mulContext p₂ P₁ = + rowMulMatrixWith mulCtx p₂ P₁ := rowMulMatrixWith_ctx _ _ _ _ + rw [hctx] at hnz ⊢ + have hIH₁ : ∀ r ∈ MatrixRows P₁, + RowApproximates mulCtx lower r ∧ r.size = problem.matrix.size := + fun r hr ↦ ih lower shift r hr + have hIH₂ := ih residual shifted p₂ hp₂mem + rcases Nat.eq_zero_or_pos P₁.size with hP₁0 | hP₁0 + · exfalso + have hP₁empty : P₁ = #[] := Array.eq_empty_of_size_eq_zero hP₁0 + have hsize0 : (rowMulMatrixWith mulCtx p₂ P₁).size = 0 := by + rw [rowMulMatrixWith_size, hP₁empty] + rfl + rw [Array.eq_empty_of_size_eq_zero hsize0] at hnz + simp [rowIsZero] at hnz + · -- Shape facts for the residual matrix. + have hprodsize : (mulTruncColumnStrassenWith lowCtx composeLeafCutoff + (resOrders.map fun order ↦ order + d₁) P₁ problem.matrix).size = + P₁.size := mulTruncColumnStrassenWith_size _ _ _ _ _ + have hRsize : Rmat.size = P₁.size := by + rw [hRmatdef] + simp only [residualMatrixWithProduct] + rw [ofFn_size, hprodsize] + have hprodwidth : 0 < P₁.size → + MatrixWidth (mulTruncColumnStrassenWith lowCtx composeLeafCutoff + (resOrders.map fun order ↦ order + d₁) P₁ problem.matrix) = + MatrixWidth problem.matrix := by + intro hpos + rw [mulTruncColumnStrassenWith_eq_truncateColumns] + refine matrixWidth_eq_of_first_row + (by rw [truncateColumns_size, mulWith_size]; omega) ?_ + intro r hr + rw [MatrixRows, truncateColumns, Array.toList_map] at hr + rcases List.mem_map.mp hr with ⟨r', hr', rfl⟩ + have hsize' : (rowTruncateColumns + (resOrders.map fun order ↦ order + d₁) r').size = r'.size := by + simp [rowTruncateColumns] + rw [hsize'] + have hr'' : r' ∈ (MatrixRows P₁).map fun r ↦ + rowMulMatrixWith lowCtx.mulContext r problem.matrix := by + rw [← matrixRows_mulWith] + exact hr' + rcases List.mem_map.mp hr'' with ⟨r'', _hr'', rfl⟩ + rw [rowMulMatrixWith_size] + have hRwidth : 0 < P₁.size → + MatrixWidth problem.matrix ≤ MatrixWidth Rmat := by + intro hpos + rw [hRmatdef] + simp only [residualMatrixWithProduct] + rw [MatrixWidth_ofFn, if_neg (by omega), hprodwidth hpos] + constructor + · refine rowApproximates_composed mulCtx hIH₁ hRsize hRwidth ?_ hIH₂.1 + intro l j hl hj hjw t ht + have hd₁lt : d₁ < problem.orders.getD j 0 := by omega + have hjres : j < resOrders.size := by + rw [hresOrders, residualOrders] + simpa using hj + have hRentry : rowGet (Rmat.getD l #[]) j = + divXTrunc d₁ (resOrders.getD j 0) + (rowGet ((mulTruncColumnStrassenWith lowCtx composeLeafCutoff + (resOrders.map fun order ↦ order + d₁) P₁ + problem.matrix).getD l #[]) j) := by + rw [hRmatdef] + simp only [residualMatrixWithProduct] + rw [rowGet_ofFn, if_pos ⟨by rw [hprodsize]; exact hl, + by rw [hprodwidth hP₁0]; exact hjw⟩] + rw [hRentry, divXTrunc_coeff, + if_pos (by rw [hresOrders, residualOrders, + natArray_map_getD _ _ hj]; omega)] + rw [mulTruncColumnStrassenWith_entry lowCtx composeLeafCutoff _ P₁ + problem.matrix (by omega) j] + rw [truncateX_coeff, if_pos (by + rw [natArray_map_getD _ _ hjres, hresOrders, residualOrders, + natArray_map_getD _ _ hj] + omega)] + rw [rowMulMatrixWith_ctx lowCtx.mulContext mulCtx] + · rw [rowMulMatrixWith_size, + matrixWidth_eq_of_first_row hP₁0 (fun r hr ↦ (hIH₁ r hr).2)] + +/-- Every row of the kernel-leaf recursive PM-basis satisfies the X-adic +approximant conditions. -/ +theorem pmBasis_kernelLeaf_approximates (mulCtx : CPolynomial.MulContext F) + (lowCtx : PolynomialMatrix.MulLowContext F) + (leafCutoff composeLeafCutoff : Nat) + (problem : XAdicProblem F) (shift : Array Nat) {row : PolynomialRow F} + (hrow : row ∈ MatrixRows (pmBasis + (kernelLeafRuntimeWithLowAndCompose mulCtx lowCtx leafCutoff + composeLeafCutoff) problem shift)) : + RowApproximates mulCtx problem row := by + rw [pmBasis, pmBasisWithFuel, pmBasisNormalizeRoot] at hrow + refine completeMissingPivotRows_approximates mulCtx problem shift ?_ hrow + intro r hr + have hr' := compactNonzeroRows_subset hr + rw [reduceKernelLeafRows] at hr' + refine reduceKernelLeafRowsByPivots_invariant (RowApproximates mulCtx problem) + (fun t r' s ht hr'' ↦ + rowApproximates_cancelKernelLeafLeadingTerm mulCtx problem s ht hr'') + shift ?_ hr' + intro y hy + exact (pmBasisWithFuelCore_kernelLeaf_rows mulCtx lowCtx leafCutoff + composeLeafCutoff _ problem shift y hy).1 + +/-! ## Generation completeness of the recursive PM-basis core + +Every nonzero solution row of an X-adic problem lies in the row module +generated by the recursive PM-basis core. Together with the shifted weak-Popov +shape of the root normalization this yields the predictable-degree minimality +of the final basis. -/ + +omit [LawfulBEq F] in +/-- Zero rows read as zero in every coordinate. -/ +private theorem pm_rowGet_eq_zero_of_rowIsZero {row : PolynomialRow F} + (h : RowIsZero row) (j : Nat) : rowGet row j = 0 := by + rcases Nat.lt_or_ge j row.size with hj | hj + · rw [rowGet, array_getD_of_lt' _ _ hj] + exact h row[j] (Array.getElem_mem_toList hj) + · rw [rowGet, array_getD_of_le' _ _ hj] + +omit [LawfulBEq F] in +/-- Rows reading as zero in every coordinate are zero rows. -/ +private theorem pm_rowIsZero_of_rowGet {row : PolynomialRow F} + (h : ∀ j, rowGet row j = 0) : RowIsZero row := by + intro p hp + rcases List.getElem_of_mem hp with ⟨j, hj, hget⟩ + have hj' : j < row.size := by simpa using hj + have hzero := h j + rw [rowGet, array_getD_of_lt' _ _ hj'] at hzero + rw [← hget, Array.getElem_toList] + exact hzero + +/-- Spans of all-zero row sets contain only zero rows. -/ +private theorem pm_rowIsZero_of_mem_rowSpan_all_zero {X : PolynomialMatrix F} + (hall : ∀ r ∈ MatrixRows X, RowIsZero r) {row : PolynomialRow F} + (hrow : row ∈ RowSpan X) : RowIsZero row := by + rcases hrow with ⟨coeffs, _hsize, rfl⟩ + refine pm_rowIsZero_of_rowGet fun j ↦ ?_ + rw [pm_rowGet_rowLinearCombination] + refine Finset.sum_eq_zero fun i hi ↦ ?_ + rw [pm_rowGet_eq_zero_of_rowIsZero + (hall _ (getD_mem_matrixRows (Finset.mem_range.mp hi))) j, mul_zero] + +/-- Span transfer for uniform-width matrices: nonzero span members move along +any row map that covers the nonzero source rows. -/ +private theorem pm_mem_rowSpan_of_nonzero_rows_mem [DecidableEq F] + {A B : PolynomialMatrix F} {n : Nat} (hn : 0 < n) + (hA : ∀ r ∈ MatrixRows A, r.size = n) + (hB : ∀ r ∈ MatrixRows B, r.size = n) + (hrows : ∀ r ∈ MatrixRows A, ¬ RowIsZero r → r ∈ RowSpan B) + {row : PolynomialRow F} (hrow : row ∈ RowSpan A) (hnz : ¬ RowIsZero row) : + row ∈ RowSpan B := by + by_cases hallzero : ∀ r ∈ MatrixRows A, RowIsZero r + · exact absurd (pm_rowIsZero_of_mem_rowSpan_all_zero hallzero hrow) hnz + · have hex : ∃ r ∈ MatrixRows A, ¬ RowIsZero r := by + by_contra hnot + refine hallzero fun r hr ↦ ?_ + by_contra hz + exact hnot ⟨r, hr, hz⟩ + rcases hex with ⟨r₀, hr₀mem, hr₀nz⟩ + have hr₀B := hrows r₀ hr₀mem hr₀nz + have hBpos : 0 < B.size := by + rcases Nat.eq_zero_or_pos B.size with h0 | hp + · exfalso + rw [Array.eq_empty_of_size_eq_zero h0] at hr₀B + have hr₀empty := eq_empty_of_mem_rowSpan_empty hr₀B + have := hA r₀ hr₀mem + rw [hr₀empty] at this + simp at this + omega + · exact hp + have hApos : 0 < A.size := size_pos_of_mem_matrixRows hr₀mem + have hwB : MatrixWidth B = n := matrixWidth_eq_of_first_row hBpos hB + refine rowSpan_subset_of_rows_mem (wellFormed_of_sizes hB) + ((matrixWidth_eq_of_first_row hApos hA).trans hwB.symm) ?_ hrow + intro r hr + by_cases hz : RowIsZero r + · have hreq : r = zeroRow n := by + rw [← hA r hr] + exact rowIsZero_eq_zeroRow hz + rw [hreq, ← hwB] + exact zeroRow_mem_rowSpan (wellFormed_of_sizes hB) + · exact hrows r hr hz + +/-- Nonzero rows survive zero-row compaction. -/ +private theorem pm_mem_matrixRows_compactNonzeroRows {X : PolynomialMatrix F} + {r : PolynomialRow F} (hr : r ∈ MatrixRows X) (hnz : ¬ RowIsZero r) : + r ∈ MatrixRows (compactNonzeroRows X) := by + rw [MatrixRows, ← Array.mem_def] at hr ⊢ + rw [compactNonzeroRows, Array.mem_filter] + refine ⟨hr, ?_⟩ + cases hb : rowIsZero r + · rfl + · exact absurd (rowIsZero_iff.1 hb) hnz + +/-- Zero-row compaction keeps every nonzero span member. -/ +private theorem pm_mem_rowSpan_compactNonzeroRows [DecidableEq F] + {X : PolynomialMatrix F} {n : Nat} (hn : 0 < n) + (hsizes : ∀ r ∈ MatrixRows X, r.size = n) + {row : PolynomialRow F} (hrow : row ∈ RowSpan X) (hnz : ¬ RowIsZero row) : + row ∈ RowSpan (compactNonzeroRows X) := by + refine pm_mem_rowSpan_of_nonzero_rows_mem hn hsizes + (fun r hr ↦ hsizes r (compactNonzeroRows_subset hr)) ?_ hrow hnz + intro r hr hrnz + exact matrix_row_mem_rowSpan + (wellFormed_of_sizes fun s hs ↦ hsizes s (compactNonzeroRows_subset hs)) + (pm_mem_matrixRows_compactNonzeroRows hr hrnz) + +/-- `toPoly` distinguishes canonical polynomials. -/ +private theorem pm_eq_of_toPoly_eq {a b : CPolynomial F} + (h : a.toPoly = b.toPoly) : a = b := by + apply CPolynomial.eq_iff_coeff.2 + intro t + rw [CPolynomial.coeff_toPoly, CPolynomial.coeff_toPoly, h] + +/-- Column entries of a combination-by-matrix product as coefficient-weighted +sums of the row-by-matrix products. -/ +private theorem pm_combination_mul_toPoly (mulCtx : CPolynomial.MulContext F) + {P₁ M : PolynomialMatrix F} (q : PolynomialRow F) {j : Nat} + (hj : j < MatrixWidth M) : + (rowGet (rowMulMatrixWith mulCtx (rowLinearCombination q P₁) M) j).toPoly = + ∑ l ∈ Finset.range P₁.size, (rowGet q l).toPoly * + (rowGet (rowMulMatrixWith mulCtx (P₁.getD l #[]) M) j).toPoly := by + rw [rowGet_rowMulMatrixWith_toPoly mulCtx _ M hj] + have hentry : ∀ k, (rowGet (rowLinearCombination q P₁) k).toPoly = + ∑ l ∈ Finset.range P₁.size, + (rowGet q l).toPoly * (rowGet (P₁.getD l #[]) k).toPoly := by + intro k + rw [pm_rowGet_rowLinearCombination, + pm_toPoly_finset_sum (fun l ↦ q.getD l 0 * rowGet (P₁.getD l #[]) k) + P₁.size] + refine Finset.sum_congr rfl fun l _hl ↦ ?_ + rw [CPolynomial.toPoly_mul] + rfl + calc ∑ k ∈ Finset.range M.size, + (rowGet (rowLinearCombination q P₁) k).toPoly * + (rowGet (M.getD k #[]) j).toPoly + = ∑ k ∈ Finset.range M.size, ∑ l ∈ Finset.range P₁.size, + (rowGet q l).toPoly * (rowGet (P₁.getD l #[]) k).toPoly * + (rowGet (M.getD k #[]) j).toPoly := by + refine Finset.sum_congr rfl fun k _hk ↦ ?_ + rw [hentry k, Finset.sum_mul] + _ = ∑ l ∈ Finset.range P₁.size, ∑ k ∈ Finset.range M.size, + (rowGet q l).toPoly * (rowGet (P₁.getD l #[]) k).toPoly * + (rowGet (M.getD k #[]) j).toPoly := Finset.sum_comm + _ = ∑ l ∈ Finset.range P₁.size, (rowGet q l).toPoly * + (rowGet (rowMulMatrixWith mulCtx (P₁.getD l #[]) M) j).toPoly := by + refine Finset.sum_congr rfl fun l _hl ↦ ?_ + rw [rowGet_rowMulMatrixWith_toPoly mulCtx _ M hj, Finset.mul_sum] + refine Finset.sum_congr rfl fun k _hk ↦ ?_ + ring + +/-- Converse of the composition soundness step: the coefficient row of a +full-problem solution against a lower basis solves the residual problem. -/ +private theorem pm_rowApproximates_residual_of_combination + (mulCtx : CPolynomial.MulContext F) + {problem : XAdicProblem F} {d₁ : Nat} {P₁ Rmat : PolynomialMatrix F} + (hP₁ : ∀ r ∈ MatrixRows P₁, + RowApproximates mulCtx + { orders := lowerOrders problem d₁, matrix := problem.matrix } r ∧ + r.size = problem.matrix.size) + (hRsize : Rmat.size = P₁.size) + (hRwidth : MatrixWidth Rmat ≤ MatrixWidth problem.matrix) + (hR : ∀ l j, l < P₁.size → j < problem.orders.size → + j < MatrixWidth problem.matrix → + ∀ t, t < problem.orders.getD j 0 - d₁ → + CPolynomial.coeff (rowGet (Rmat.getD l #[]) j) t = + CPolynomial.coeff + (rowGet (rowMulMatrixWith mulCtx (P₁.getD l #[]) problem.matrix) j) + (t + d₁)) + {q : PolynomialRow F} + (happrox : RowApproximates mulCtx problem (rowLinearCombination q P₁)) : + RowApproximates mulCtx + { orders := residualOrders problem d₁, matrix := Rmat } q := by + rw [rowApproximates_iff] + intro j hj hjw + have hj' : j < problem.orders.size := by + simpa [residualOrders] using hj + have hjwM : j < MatrixWidth problem.matrix := Nat.lt_of_lt_of_le hjw hRwidth + set o := problem.orders.getD j 0 with ho + have horder : (residualOrders problem d₁).getD j 0 = o - d₁ := by + rw [residualOrders, natArray_map_getD _ _ hj'] + rw [horder] + rcases Nat.le_total o d₁ with hod | hod + · rw [Nat.sub_eq_zero_of_le hod, pow_zero] + exact one_dvd _ + · rcases Nat.eq_zero_or_pos P₁.size with hP₁0 | _hP₁0 + · rw [hRsize, hP₁0, Finset.range_zero, Finset.sum_empty] + exact dvd_zero _ + · have hP₁dvd : ∀ l, l < P₁.size → + (Polynomial.X : Polynomial F) ^ d₁ ∣ + (rowGet (rowMulMatrixWith mulCtx (P₁.getD l #[]) + problem.matrix) j).toPoly := by + intro l hl + have hmem := getD_mem_matrixRows hl + have happroxl := (hP₁ _ hmem).1 j (by simpa [lowerOrders] using hj') + rw [truncateX_eq_zero_iff_X_pow_dvd] at happroxl + have horderl : (lowerOrders problem d₁).getD j 0 = min o d₁ := by + rw [lowerOrders, natArray_map_getD _ _ hj'] + rwa [horderl, Nat.min_eq_right hod] at happroxl + have hquot : ∀ l, ∃ sl : Polynomial F, l < P₁.size → + (rowGet (rowMulMatrixWith mulCtx (P₁.getD l #[]) + problem.matrix) j).toPoly = Polynomial.X ^ d₁ * sl := by + intro l + by_cases hl : l < P₁.size + · rcases hP₁dvd l hl with ⟨sl, hsl⟩ + exact ⟨sl, fun _ ↦ hsl⟩ + · exact ⟨0, fun h ↦ absurd h hl⟩ + choose s hs using hquot + have hrowdvd : (Polynomial.X : Polynomial F) ^ o ∣ + ∑ l ∈ Finset.range P₁.size, (rowGet q l).toPoly * + (rowGet (rowMulMatrixWith mulCtx (P₁.getD l #[]) + problem.matrix) j).toPoly := by + have h0 := (rowApproximates_iff mulCtx problem _).1 happrox j hj' hjwM + rwa [← rowGet_rowMulMatrixWith_toPoly mulCtx _ problem.matrix hjwM, + pm_combination_mul_toPoly mulCtx q hjwM] at h0 + have hsum : ∑ l ∈ Finset.range P₁.size, (rowGet q l).toPoly * + (rowGet (rowMulMatrixWith mulCtx (P₁.getD l #[]) + problem.matrix) j).toPoly = + Polynomial.X ^ d₁ * + ∑ l ∈ Finset.range P₁.size, (rowGet q l).toPoly * s l := by + rw [Finset.mul_sum] + refine Finset.sum_congr rfl fun l hl ↦ ?_ + rw [hs l (Finset.mem_range.mp hl)] + ring + rw [hsum, show o = d₁ + (o - d₁) from by omega, pow_add] at hrowdvd + have hquotsum : (Polynomial.X : Polynomial F) ^ (o - d₁) ∣ + ∑ l ∈ Finset.range P₁.size, (rowGet q l).toPoly * s l := + (mul_dvd_mul_iff_left (pow_ne_zero d₁ Polynomial.X_ne_zero)).1 hrowdvd + have hcong : ∀ l, l < P₁.size → + (Polynomial.X : Polynomial F) ^ (o - d₁) ∣ + (rowGet (Rmat.getD l #[]) j).toPoly - s l := by + intro l hl + rw [Polynomial.X_pow_dvd_iff] + intro t ht + rw [Polynomial.coeff_sub, ← CPolynomial.coeff_toPoly, + hR l j hl hj' hjwM t (by omega), CPolynomial.coeff_toPoly] + have hcoeff : ((rowGet (rowMulMatrixWith mulCtx (P₁.getD l #[]) + problem.matrix) j).toPoly).coeff (t + d₁) = + (Polynomial.X ^ d₁ * s l).coeff (t + d₁) := by + rw [hs l hl] + rw [Polynomial.coeff_X_pow_mul] at hcoeff + rw [hcoeff] + exact sub_self _ + have hfinal : ∑ k ∈ Finset.range Rmat.size, (rowGet q k).toPoly * + (rowGet (Rmat.getD k #[]) j).toPoly = + (∑ l ∈ Finset.range P₁.size, (rowGet q l).toPoly * s l) + + ∑ l ∈ Finset.range P₁.size, (rowGet q l).toPoly * + ((rowGet (Rmat.getD l #[]) j).toPoly - s l) := by + rw [hRsize, ← Finset.sum_add_distrib] + refine Finset.sum_congr rfl fun l _hl ↦ ?_ + ring + rw [hfinal] + exact dvd_add hquotsum + (Finset.dvd_sum fun l hl ↦ + Dvd.dvd.mul_left (hcong l (Finset.mem_range.mp hl)) _) + +/-- Combinations against a basis of combinations are combinations against the +composed product matrix. -/ +private theorem pm_rowLinearCombination_combination + (mulCtx : CPolynomial.MulContext F) + {P₁ P₂ : PolynomialMatrix F} {n : Nat} + (hP₁pos : 0 < P₁.size) (hP₂pos : 0 < P₂.size) + (hsizes : ∀ r ∈ MatrixRows P₁, r.size = n) + (w : Array (CPolynomial F)) : + rowLinearCombination (rowLinearCombination w P₂) P₁ = + rowLinearCombination w (mulWith mulCtx P₂ P₁) := by + have hw₁ : MatrixWidth P₁ = n := matrixWidth_eq_of_first_row hP₁pos hsizes + have hCsize : (mulWith mulCtx P₂ P₁).size = P₂.size := mulWith_size mulCtx P₂ P₁ + have hCsizes : ∀ r ∈ MatrixRows (mulWith mulCtx P₂ P₁), r.size = n := by + intro r hr + rw [matrixRows_mulWith] at hr + rcases List.mem_map.mp hr with ⟨p₂, _hp₂, rfl⟩ + rw [rowMulMatrixWith_size, hw₁] + have hCw : MatrixWidth (mulWith mulCtx P₂ P₁) = n := + matrixWidth_eq_of_first_row (by omega) hCsizes + refine pm_row_ext ?_ fun j ↦ ?_ + · rw [pm_rowLinearCombination_size hsizes hw₁, + pm_rowLinearCombination_size hCsizes hCw] + · rw [pm_rowGet_rowLinearCombination, pm_rowGet_rowLinearCombination, hCsize] + rcases Nat.lt_or_ge j n with hj | hj + · refine pm_eq_of_toPoly_eq ?_ + rw [pm_toPoly_finset_sum + (fun l ↦ (rowLinearCombination w P₂).getD l 0 * rowGet (P₁.getD l #[]) j) + P₁.size] + rw [pm_toPoly_finset_sum + (fun i ↦ w.getD i 0 * rowGet ((mulWith mulCtx P₂ P₁).getD i #[]) j) + P₂.size] + have hjw₁ : j < MatrixWidth P₁ := by omega + calc ∑ l ∈ Finset.range P₁.size, + ((rowLinearCombination w P₂).getD l 0 * + rowGet (P₁.getD l #[]) j).toPoly + = ∑ l ∈ Finset.range P₁.size, ∑ i ∈ Finset.range P₂.size, + (w.getD i 0).toPoly * (rowGet (P₂.getD i #[]) l).toPoly * + (rowGet (P₁.getD l #[]) j).toPoly := by + refine Finset.sum_congr rfl fun l _hl ↦ ?_ + rw [CPolynomial.toPoly_mul] + have hwl : ((rowLinearCombination w P₂).getD l 0).toPoly = + ∑ i ∈ Finset.range P₂.size, + (w.getD i 0).toPoly * (rowGet (P₂.getD i #[]) l).toPoly := by + rw [show (rowLinearCombination w P₂).getD l 0 = + rowGet (rowLinearCombination w P₂) l from rfl, + pm_rowGet_rowLinearCombination, + pm_toPoly_finset_sum + (fun i ↦ w.getD i 0 * rowGet (P₂.getD i #[]) l) P₂.size] + refine Finset.sum_congr rfl fun i _hi ↦ ?_ + rw [CPolynomial.toPoly_mul] + rw [hwl, Finset.sum_mul] + _ = ∑ i ∈ Finset.range P₂.size, ∑ l ∈ Finset.range P₁.size, + (w.getD i 0).toPoly * (rowGet (P₂.getD i #[]) l).toPoly * + (rowGet (P₁.getD l #[]) j).toPoly := Finset.sum_comm + _ = ∑ i ∈ Finset.range P₂.size, + (w.getD i 0 * rowGet ((mulWith mulCtx P₂ P₁).getD i #[]) j).toPoly := by + refine Finset.sum_congr rfl fun i hi ↦ ?_ + rw [CPolynomial.toPoly_mul, + mulWith_getD mulCtx P₂ P₁ (Finset.mem_range.mp hi), + rowGet_rowMulMatrixWith_toPoly mulCtx _ P₁ hjw₁, Finset.mul_sum] + refine Finset.sum_congr rfl fun l _hl ↦ ?_ + ring + · refine Eq.trans (Finset.sum_eq_zero fun l hl ↦ ?_) + (Finset.sum_eq_zero fun i hi ↦ ?_).symm + · have hzero : rowGet (P₁.getD l #[]) j = 0 := by + rw [rowGet, array_getD_of_le' _ _ (by + rw [hsizes _ (getD_mem_matrixRows (Finset.mem_range.mp hl))] + omega)] + rw [hzero, mul_zero] + · have hi' : i < (mulWith mulCtx P₂ P₁).size := by + rw [hCsize] + exact Finset.mem_range.mp hi + have hzero : rowGet ((mulWith mulCtx P₂ P₁).getD i #[]) j = 0 := by + rw [rowGet, array_getD_of_le' _ _ (by + rw [hCsizes _ (getD_mem_matrixRows hi')] + omega)] + rw [hzero, mul_zero] + +/-- **Generation completeness of the recursive PM-basis core.** Every nonzero +solution row of an X-adic problem lies in the row module generated by the +fuel-bounded recursive PM-basis core. -/ +theorem pmBasisWithFuelCore_kernelLeaf_rowSpan_complete [DecidableEq F] + (mulCtx : CPolynomial.MulContext F) + (lowCtx : PolynomialMatrix.MulLowContext F) + (leafCutoff composeLeafCutoff : Nat) : + ∀ (fuel : Nat) (problem : XAdicProblem F) (shift : Array Nat), + 0 < problem.matrix.size → WellFormed problem.matrix → + ∀ row : PolynomialRow F, RowApproximates mulCtx problem row → + row.size = problem.matrix.size → ¬ RowIsZero row → + row ∈ RowSpan (pmBasisWithFuelCore + (kernelLeafRuntimeWithLowAndCompose mulCtx lowCtx leafCutoff + composeLeafCutoff) fuel problem shift) := by + intro fuel + induction fuel with + | zero => + intro problem shift hpos hwf row happrox hsize hnz + rw [pmBasisWithFuelCore] + exact pm_mem_rowSpan_compactNonzeroRows hpos + (fun r hr ↦ (kernelLeafBasis_rows mulCtx problem shift r hr).2) + (kernelLeafBasis_rowSpan_complete mulCtx problem shift hpos hwf + happrox hsize hnz) + hnz + | succ fuel ih => + intro problem shift hpos hwf row happrox hsize hnz + rw [pmBasisWithFuelCore] + split + · exact pm_mem_rowSpan_compactNonzeroRows hpos + (fun r hr ↦ (kernelLeafBasis_rows mulCtx problem shift r hr).2) + (kernelLeafBasis_rowSpan_complete mulCtx problem shift hpos hwf + happrox hsize hnz) + hnz + · set d₁ := maxOrder problem / 2 with hd₁ + set lower : XAdicProblem F := + { orders := lowerOrders problem d₁, matrix := problem.matrix } + with hlower + set P₁ := pmBasisWithFuelCore + (kernelLeafRuntimeWithLowAndCompose mulCtx lowCtx leafCutoff + composeLeafCutoff) fuel lower shift with hP₁def + set resOrders := residualOrders problem d₁ with hresOrders + set Rmat := residualMatrixWithProduct + (mulTruncColumnStrassenWith lowCtx composeLeafCutoff) P₁ + problem.matrix d₁ resOrders with hRmatdef + set residual : XAdicProblem F := + { orders := resOrders, matrix := Rmat } with hresidual + set shifted := updateShiftByRows P₁ shift with hshifted + set P₂ := pmBasisWithFuelCore + (kernelLeafRuntimeWithLowAndCompose mulCtx lowCtx leafCutoff + composeLeafCutoff) fuel residual shifted with hP₂def + have hIH₁ : ∀ r ∈ MatrixRows P₁, + RowApproximates mulCtx lower r ∧ r.size = problem.matrix.size := + fun r hr ↦ pmBasisWithFuelCore_kernelLeaf_rows mulCtx lowCtx + leafCutoff composeLeafCutoff fuel lower shift r hr + -- The row solves the lower-order problem. + have hlowapprox : RowApproximates mulCtx lower row := by + rw [rowApproximates_iff] at happrox ⊢ + intro j hj hjw + have hj' : j < problem.orders.size := by + simpa [hlower, lowerOrders] using hj + refine dvd_trans (pow_dvd_pow _ ?_) (happrox j hj' hjw) + have horder : (lowerOrders problem d₁).getD j 0 = + min (problem.orders.getD j 0) d₁ := by + rw [lowerOrders, natArray_map_getD _ _ hj'] + rw [hlower] + simp only [horder] + omega + have hrow₁ : row ∈ RowSpan P₁ := + ih lower shift hpos hwf row hlowapprox hsize hnz + rcases Nat.eq_zero_or_pos P₁.size with hP₁0 | hP₁0 + · exfalso + rw [Array.eq_empty_of_size_eq_zero hP₁0] at hrow₁ + have hempty := eq_empty_of_mem_rowSpan_empty hrow₁ + rw [hempty] at hsize + simp at hsize + omega + · rcases hrow₁ with ⟨q, hqsize, hqeq⟩ + -- Shape facts for the residual matrix. + have hprodsize : (mulTruncColumnStrassenWith lowCtx composeLeafCutoff + (resOrders.map fun order ↦ order + d₁) P₁ problem.matrix).size = + P₁.size := mulTruncColumnStrassenWith_size _ _ _ _ _ + have hRsize : Rmat.size = P₁.size := by + rw [hRmatdef] + simp only [residualMatrixWithProduct] + rw [ofFn_size, hprodsize] + have hprodwidth : + MatrixWidth (mulTruncColumnStrassenWith lowCtx composeLeafCutoff + (resOrders.map fun order ↦ order + d₁) P₁ problem.matrix) = + MatrixWidth problem.matrix := by + rw [mulTruncColumnStrassenWith_eq_truncateColumns] + refine matrixWidth_eq_of_first_row + (by rw [truncateColumns_size, mulWith_size]; omega) ?_ + intro r hr + rw [MatrixRows, truncateColumns, Array.toList_map] at hr + rcases List.mem_map.mp hr with ⟨r', hr', rfl⟩ + have hsize' : (rowTruncateColumns + (resOrders.map fun order ↦ order + d₁) r').size = r'.size := by + simp [rowTruncateColumns] + rw [hsize'] + have hr'' : r' ∈ (MatrixRows P₁).map fun r ↦ + rowMulMatrixWith lowCtx.mulContext r problem.matrix := by + rw [← matrixRows_mulWith] + exact hr' + rcases List.mem_map.mp hr'' with ⟨r'', _hr'', rfl⟩ + rw [rowMulMatrixWith_size] + have hRwidth : MatrixWidth Rmat = MatrixWidth problem.matrix := by + rw [hRmatdef] + simp only [residualMatrixWithProduct] + rw [MatrixWidth_ofFn, if_neg (by omega), hprodwidth] + have hRcong : ∀ l j, l < P₁.size → j < problem.orders.size → + j < MatrixWidth problem.matrix → + ∀ t, t < problem.orders.getD j 0 - d₁ → + CPolynomial.coeff (rowGet (Rmat.getD l #[]) j) t = + CPolynomial.coeff + (rowGet (rowMulMatrixWith mulCtx (P₁.getD l #[]) + problem.matrix) j) (t + d₁) := by + intro l j hl hj hjw t ht + have hd₁lt : d₁ < problem.orders.getD j 0 := by omega + have hjres : j < resOrders.size := by + rw [hresOrders, residualOrders] + simpa using hj + have hRentry : rowGet (Rmat.getD l #[]) j = + divXTrunc d₁ (resOrders.getD j 0) + (rowGet ((mulTruncColumnStrassenWith lowCtx composeLeafCutoff + (resOrders.map fun order ↦ order + d₁) P₁ + problem.matrix).getD l #[]) j) := by + rw [hRmatdef] + simp only [residualMatrixWithProduct] + rw [rowGet_ofFn, if_pos ⟨by rw [hprodsize]; exact hl, + by rw [hprodwidth]; exact hjw⟩] + rw [hRentry, divXTrunc_coeff, + if_pos (by rw [hresOrders, residualOrders, + natArray_map_getD _ _ hj]; omega)] + rw [mulTruncColumnStrassenWith_entry lowCtx composeLeafCutoff _ P₁ + problem.matrix (by omega) j] + rw [truncateX_coeff, if_pos (by + rw [natArray_map_getD _ _ hjres, hresOrders, residualOrders, + natArray_map_getD _ _ hj] + omega)] + rw [rowMulMatrixWith_ctx lowCtx.mulContext mulCtx] + -- The combination coefficients are nonzero and solve the residual. + have hq_nz : ¬ RowIsZero q := by + intro hqz + apply hnz + rw [hqeq] + refine pm_rowIsZero_of_rowGet fun j ↦ ?_ + rw [pm_rowGet_rowLinearCombination] + refine Finset.sum_eq_zero fun i _hi ↦ ?_ + rw [show q.getD i 0 = rowGet q i from rfl, + pm_rowGet_eq_zero_of_rowIsZero hqz, zero_mul] + have hqapprox : RowApproximates mulCtx residual q := by + rw [hresidual, hresOrders] + refine pm_rowApproximates_residual_of_combination mulCtx hIH₁ + hRsize (le_of_eq hRwidth) hRcong ?_ + rw [← hqeq] + exact happrox + have hwf_R : WellFormed Rmat := by + refine wellFormed_of_sizes + (n := MatrixWidth (mulTruncColumnStrassenWith lowCtx + composeLeafCutoff (resOrders.map fun order ↦ order + d₁) P₁ + problem.matrix)) ?_ + intro r hr + rcases List.getElem_of_mem hr with ⟨i, hi, hget⟩ + have hi' : i < Rmat.size := by simpa [MatrixRows] using hi + have hr_eq : Rmat.getD i #[] = r := by + rw [array_getD_of_lt' _ _ hi', ← Array.getElem_toList] + exact hget + rw [← hr_eq, hRmatdef] + simp only [residualMatrixWithProduct] + rw [getD_ofFn, if_pos (by + rw [hRmatdef] at hi' + simpa [residualMatrixWithProduct, ofFn_size] using hi')] + simp + have hRpos : 0 < Rmat.size := by omega + have hq_size : q.size = Rmat.size := by omega + have hrow₂ : q ∈ RowSpan P₂ := + ih residual shifted hRpos hwf_R q hqapprox hq_size hq_nz + rcases hrow₂ with ⟨w, hwsize, hweq⟩ + have hP₂pos : 0 < P₂.size := by + rcases Nat.eq_zero_or_pos P₂.size with h0 | hp + · exfalso + rw [Array.eq_empty_of_size_eq_zero h0] at hweq + have hqempty : q = #[] := by + rw [hweq, rowLinearCombination] + rfl + rw [hqempty] at hq_nz + exact hq_nz rowIsZero_empty + · exact hp + -- Compose the two combinations through the product basis. + have hdistrib : row = + rowLinearCombination w (mulWith lowCtx.mulContext P₂ P₁) := by + rw [hqeq, hweq] + exact pm_rowLinearCombination_combination lowCtx.mulContext hP₁0 + hP₂pos (fun r hr ↦ (hIH₁ r hr).2) w + have hfinal : row ∈ RowSpan (compactNonzeroRows + (mulStrassenWith lowCtx composeLeafCutoff P₂ P₁)) := by + rw [mulStrassenWith_eq_mulWith] + refine pm_mem_rowSpan_compactNonzeroRows hpos ?_ ?_ hnz + · intro r hr + rw [matrixRows_mulWith] at hr + rcases List.mem_map.mp hr with ⟨p₂, _hp₂, rfl⟩ + rw [rowMulMatrixWith_size, + matrixWidth_eq_of_first_row hP₁0 fun r hr ↦ (hIH₁ r hr).2] + · exact ⟨w, by rw [mulWith_size]; omega, hdistrib⟩ + exact hfinal + +/-! ## Root normalization shape and shifted minimality -/ + +omit [BEq F] [LawfulBEq F] in +/-- Zero padding on the right does not change row reads. -/ +private theorem pm_rowGet_append_replicate (row : PolynomialRow F) + (m k : Nat) : + rowGet (row ++ Array.replicate m (0 : CPolynomial F)) k = rowGet row k := by + rcases Nat.lt_or_ge k row.size with hk | hk + · rw [rowGet, rowGet, pm_append_getD_left _ hk] + · rw [rowGet, rowGet, pm_append_getD_right _ hk, array_getD_of_le' _ _ hk] + rcases Nat.lt_or_ge (k - row.size) m with h | h + · rw [array_getD_of_lt' _ _ (by simpa using h), Array.getElem_replicate] + · rw [array_getD_of_le' _ _ (by simpa using h)] + +/-- Zero padding preserves the X-adic approximant conditions. -/ +private theorem pm_rowApproximates_append_replicate + (mulCtx : CPolynomial.MulContext F) (problem : XAdicProblem F) + {row : PolynomialRow F} (m : Nat) + (h : RowApproximates mulCtx problem row) : + RowApproximates mulCtx problem + (row ++ Array.replicate m (0 : CPolynomial F)) := by + rw [rowApproximates_iff] at h ⊢ + intro j hj hjw + have hsum : ∑ k ∈ Finset.range problem.matrix.size, + (rowGet (row ++ Array.replicate m (0 : CPolynomial F)) k).toPoly * + (rowGet (problem.matrix.getD k #[]) j).toPoly = + ∑ k ∈ Finset.range problem.matrix.size, + (rowGet row k).toPoly * (rowGet (problem.matrix.getD k #[]) j).toPoly := + Finset.sum_congr rfl fun k _hk ↦ by rw [pm_rowGet_append_replicate] + rw [hsum] + exact h j hj hjw + +/-- Coordinates reading zero contribute no shifted entry degree. -/ +private theorem pm_shiftedEntryDegree_eq_none_of_rowGet_eq_zero + {row : PolynomialRow F} {shift : Array Nat} {j : Nat} + (h : rowGet row j = 0) : shiftedEntryDegree? row shift j = none := by + simp [shiftedEntryDegree?, h] + +omit [LawfulBEq F] in +/-- Shifted entry degrees only depend on row reads. -/ +private theorem pm_shiftedEntryDegree_congr {row row' : PolynomialRow F} + {shift : Array Nat} {j : Nat} (h : rowGet row' j = rowGet row j) : + shiftedEntryDegree? row' shift j = shiftedEntryDegree? row shift j := by + simp only [shiftedEntryDegree?, h] + +/-- Zero padding preserves the shifted row degree. -/ +private theorem pm_rowShiftedDegree_append_replicate (row : PolynomialRow F) + (shift : Array Nat) (m : Nat) : + rowShiftedDegree? (row ++ Array.replicate m (0 : CPolynomial F)) shift = + rowShiftedDegree? row shift := by + set row' := row ++ Array.replicate m (0 : CPolynomial F) with hrow' + have hget : ∀ j, rowGet row' j = rowGet row j := + pm_rowGet_append_replicate row m + have hentry : ∀ j, + shiftedEntryDegree? row' shift j = shiftedEntryDegree? row shift j := + fun j ↦ pm_shiftedEntryDegree_congr (hget j) + have hzero_iff : RowIsZero row' ↔ RowIsZero row := by + constructor + · intro h + refine pm_rowIsZero_of_rowGet fun j ↦ ?_ + rw [← hget j] + exact pm_rowGet_eq_zero_of_rowIsZero h j + · intro h + refine pm_rowIsZero_of_rowGet fun j ↦ ?_ + rw [hget j] + exact pm_rowGet_eq_zero_of_rowIsZero h j + cases hd : rowShiftedDegree? row shift with + | none => + rw [rowShiftedDegree?_eq_none_iff] at hd ⊢ + exact hzero_iff.mpr hd + | some d => + cases hd' : rowShiftedDegree? row' shift with + | none => + exfalso + rw [rowShiftedDegree?_eq_none_iff] at hd' + have hnone : rowShiftedDegree? row shift = none := + rowShiftedDegree?_eq_none_iff.2 (hzero_iff.mp hd') + rw [hd] at hnone + cases hnone + | some d' => + rcases exists_shiftedEntryDegree?_eq_of_rowShiftedDegree?_eq_some hd + with ⟨j, hj, hej⟩ + rcases exists_shiftedEntryDegree?_eq_of_rowShiftedDegree?_eq_some hd' + with ⟨j', hj', hej'⟩ + have hj'row : j' < row.size := by + by_contra hge + have hzero : rowGet row j' = 0 := by + rw [rowGet, array_getD_of_le' _ _ (by omega)] + rw [hentry j', + pm_shiftedEntryDegree_eq_none_of_rowGet_eq_zero hzero] at hej' + cases hej' + have h1 : d' ≤ d := + shiftedEntryDegree?_le_of_rowShiftedDegree?_eq_some hd hj'row + (by rw [← hentry j']; exact hej') + have hsize' : row'.size = row.size + m := by + rw [hrow'] + simp + have h2 : d ≤ d' := + shiftedEntryDegree?_le_of_rowShiftedDegree?_eq_some (j := j) hd' + (by omega) (by rw [hentry j]; exact hej) + exact congrArg some (by omega) + +/-- High monomial coefficients are nonzero. -/ +private theorem pm_coeffXPower_one_ne_zero (d : Nat) : + coeffXPower (1 : F) d ≠ 0 := by + intro h + have hcoeff : CPolynomial.coeff (coeffXPower (1 : F) d) d = 1 := by + rw [CPolynomial.coeff_toPoly, coeffXPower_toPoly, Polynomial.coeff_C_mul, + Polynomial.coeff_X_pow, if_pos rfl, mul_one] + rw [h, CPolynomial.coeff_zero] at hcoeff + exact zero_ne_one hcoeff + +/-- Degree of a high monomial coefficient. -/ +private theorem pm_coeffXPower_one_natDegree (d : Nat) : + (coeffXPower (1 : F) d).natDegree = d := by + rw [CPolynomial.natDegree_toPoly, coeffXPower_toPoly, Polynomial.C_1, + one_mul, Polynomial.natDegree_X_pow] + +/-- Diagonal shifted entry degree of a monomial unit row. -/ +private theorem pm_shiftedEntryDegree_monomialUnitRow_self {n i : Nat} + (cap : Nat) (shift : Array Nat) (hi : i < n) : + shiftedEntryDegree? (monomialUnitRow (F := F) n i cap) shift i = + some (cap + shift.getD i 0) := by + have hget : rowGet (monomialUnitRow (F := F) n i cap) i = + coeffXPower 1 cap := by + rw [pm_rowGet_monomialUnitRow, if_pos ⟨hi, rfl⟩] + rw [shiftedEntryDegree?_eq_some_of_rowGet_ne_zero (by + rw [hget] + exact pm_coeffXPower_one_ne_zero cap), + hget, pm_coeffXPower_one_natDegree] + +/-- Off-diagonal shifted entry degrees of a monomial unit row are undefined. -/ +private theorem pm_shiftedEntryDegree_monomialUnitRow_ne {n i j : Nat} + (cap : Nat) (shift : Array Nat) (hij : i ≠ j) : + shiftedEntryDegree? (monomialUnitRow (F := F) n i cap) shift j = none := by + refine pm_shiftedEntryDegree_eq_none_of_rowGet_eq_zero ?_ + rw [pm_rowGet_monomialUnitRow, if_neg fun h ↦ hij h.2] + +/-- Shifted row degree of a monomial unit row. -/ +private theorem pm_rowShiftedDegree_monomialUnitRow {n i : Nat} (cap : Nat) + (shift : Array Nat) (hi : i < n) : + rowShiftedDegree? (monomialUnitRow (F := F) n i cap) shift = + some (cap + shift.getD i 0) := by + cases hd : rowShiftedDegree? (monomialUnitRow (F := F) n i cap) shift with + | none => + exfalso + have hz := rowShiftedDegree?_eq_none_iff.1 hd + have hzero := pm_rowGet_eq_zero_of_rowIsZero hz i + rw [pm_rowGet_monomialUnitRow, if_pos ⟨hi, rfl⟩] at hzero + exact pm_coeffXPower_one_ne_zero cap hzero + | some d => + rcases exists_shiftedEntryDegree?_eq_of_rowShiftedDegree?_eq_some hd + with ⟨j, hj, hej⟩ + rcases eq_or_ne i j with rfl | hij + · rw [pm_shiftedEntryDegree_monomialUnitRow_self cap shift hi] at hej + rw [← hej] + · rw [pm_shiftedEntryDegree_monomialUnitRow_ne cap shift hij] at hej + cases hej + +/-- Shifted leading position of a monomial unit row. -/ +private theorem pm_rowShiftedLeadingPosition_monomialUnitRow [DecidableEq F] + {n i : Nat} (cap : Nat) (shift : Array Nat) (hi : i < n) : + rowShiftedLeadingPosition? (monomialUnitRow (F := F) n i cap) shift = + some i := by + have hdeg := pm_rowShiftedDegree_monomialUnitRow (F := F) cap shift hi + rcases rowShiftedLeadingPosition?_some_of_degree hdeg with ⟨pos, hpos⟩ + have hentry := rowShiftedLeadingPosition?_entry_eq hdeg hpos + rcases eq_or_ne i pos with rfl | hne + · exact hpos + · rw [pm_shiftedEntryDegree_monomialUnitRow_ne cap shift hne] at hentry + cases hentry + +omit [LawfulBEq F] in +/-- Stored rows witness their shifted leading position in the row set. -/ +private theorem pm_rowsContainLeadingPosition_of_getD + {rows : PolynomialMatrix F} {shift : Array Nat} {i p : Nat} + (hi : i < rows.size) + (hpos : rowShiftedLeadingPosition? (rows.getD i #[]) shift = some p) : + rowsContainLeadingPosition rows shift p = true := by + rw [rowsContainLeadingPosition, Array.any_eq_true] + refine ⟨i, hi, ?_⟩ + rw [← array_getD_of_lt' rows #[] hi, hpos] + exact beq_self_eq_true p + +omit [LawfulBEq F] in +/-- Pivot tables whose slots store their own leading positions extract to +shifted weak-Popov row sets. -/ +private theorem pm_pivotRows_shiftedWeakPopov + {pivots : Array (Option (PolynomialRow F))} {shift : Array Nat} + (hinv : ∀ p r, pivots.getD p none = some r → + rowShiftedLeadingPosition? r shift = some p) : + ShiftedWeakPopov (pivotRows pivots) shift := by + have hpair : (pivots.toList.filterMap id).Pairwise + (fun a b ↦ rowShiftedLeadingPosition? a shift ≠ + rowShiftedLeadingPosition? b shift) := by + rw [List.pairwise_filterMap, List.pairwise_iff_getElem] + intro u v hu hv huv b hb b' hb' + have hu' : u < pivots.size := by simpa using hu + have hv' : v < pivots.size := by simpa using hv + have hbu : pivots.getD u none = some b := by + rw [array_getD_of_lt' _ _ hu', ← Array.getElem_toList] + exact hb + have hbv : pivots.getD v none = some b' := by + rw [array_getD_of_lt' _ _ hv', ← Array.getElem_toList] + exact hb' + rw [hinv u b hbu, hinv v b' hbv] + intro hcontra + cases hcontra + omega + rw [List.pairwise_iff_getElem] at hpair + have hsz : (pivotRows pivots).size = (pivots.toList.filterMap id).length := by + rw [pivotRows] + simp + have hget : ∀ k, k < (pivotRows pivots).size → + ∀ hk : k < (pivots.toList.filterMap id).length, + (pivotRows pivots).getD k #[] = (pivots.toList.filterMap id)[k] := by + intro k hk hk' + rw [pivotRows, array_getD_of_lt' _ _ (by simpa using hk'), + List.getElem_toArray] + intro i j hi hj hij hpi hpj + rcases Nat.lt_or_ge i j with h | h + · rw [hget i hi (by omega), hget j hj (by omega)] + exact hpair i j (by omega) (by omega) h + · have hji : j < i := by omega + rw [hget i hi (by omega), hget j hj (by omega)] + exact (hpair j i (by omega) (by omega) hji).symm + +/-- Per-row shape facts for the missing-pivot completion rows. -/ +private theorem pm_missingCompletionRows_facts [DecidableEq F] + (problem : XAdicProblem F) (shift : Array Nat) (rows : PolynomialMatrix F) + {k : Nat} (hk : k < (missingCompletionRows problem shift rows).size) : + ∃ i, i < problem.matrix.size ∧ + rowShiftedLeadingPosition? + ((missingCompletionRows problem shift rows).getD k #[]) shift = + some i ∧ + rowsContainLeadingPosition rows shift i = false := by + have hmem : (missingCompletionRows problem shift rows).getD k #[] ∈ + (List.range problem.matrix.size).filterMap (fun i ↦ + if rowsContainLeadingPosition rows shift i then none + else some (monomialUnitRow problem.matrix.size i + (leafDegreeCap problem))) := by + rw [missingCompletionRows] at hk ⊢ + rw [array_getD_of_lt' _ _ hk, List.getElem_toArray] + exact List.getElem_mem _ + rcases List.mem_filterMap.mp hmem with ⟨i, hi, hg⟩ + rw [List.mem_range] at hi + by_cases hc : rowsContainLeadingPosition rows shift i + · rw [if_pos hc] at hg + cases hg + · rw [if_neg hc] at hg + refine ⟨i, hi, ?_, by simpa using hc⟩ + rw [← Option.some.inj hg] + exact pm_rowShiftedLeadingPosition_monomialUnitRow + (leafDegreeCap problem) shift hi + +/-- Distinct missing-pivot completion rows have distinct shifted leading +positions. -/ +private theorem pm_missingCompletionRows_pairwise [DecidableEq F] + (problem : XAdicProblem F) (shift : Array Nat) (rows : PolynomialMatrix F) + {k k' : Nat} (hk : k < (missingCompletionRows problem shift rows).size) + (hk' : k' < (missingCompletionRows problem shift rows).size) + (hkk : k < k') : + rowShiftedLeadingPosition? + ((missingCompletionRows problem shift rows).getD k #[]) shift ≠ + rowShiftedLeadingPosition? + ((missingCompletionRows problem shift rows).getD k' #[]) shift := by + have hmlist : missingCompletionRows problem shift rows = + ((List.range problem.matrix.size).filterMap (fun i ↦ + if rowsContainLeadingPosition rows shift i then none + else some (monomialUnitRow problem.matrix.size i + (leafDegreeCap problem)))).toArray := by + simp only [missingCompletionRows] + have hpair : ((List.range problem.matrix.size).filterMap (fun i ↦ + if rowsContainLeadingPosition rows shift i then none + else some (monomialUnitRow (F := F) problem.matrix.size i + (leafDegreeCap problem)))).Pairwise + (fun a b ↦ rowShiftedLeadingPosition? a shift ≠ + rowShiftedLeadingPosition? b shift) := by + rw [List.pairwise_filterMap, List.pairwise_iff_getElem] + intro u v hu hv huv b hb b' hb' + have hu' : u < problem.matrix.size := by simpa using hu + have hv' : v < problem.matrix.size := by simpa using hv + simp only [List.getElem_range] at hb hb' + have hbpos : rowShiftedLeadingPosition? b shift = some u := by + by_cases hc : rowsContainLeadingPosition rows shift u + · rw [if_pos hc] at hb + cases hb + · rw [if_neg hc] at hb + rw [← Option.some.inj hb] + exact pm_rowShiftedLeadingPosition_monomialUnitRow + (leafDegreeCap problem) shift hu' + have hbpos' : rowShiftedLeadingPosition? b' shift = some v := by + by_cases hc : rowsContainLeadingPosition rows shift v + · rw [if_pos hc] at hb' + cases hb' + · rw [if_neg hc] at hb' + rw [← Option.some.inj hb'] + exact pm_rowShiftedLeadingPosition_monomialUnitRow + (leafDegreeCap problem) shift hv' + rw [hbpos, hbpos'] + intro hcontra + cases hcontra + omega + rw [List.pairwise_iff_getElem] at hpair + rw [hmlist] at hk hk' ⊢ + have hk_len : k < ((List.range problem.matrix.size).filterMap (fun i ↦ + if rowsContainLeadingPosition rows shift i then none + else some (monomialUnitRow (F := F) problem.matrix.size i + (leafDegreeCap problem)))).length := by + simpa using hk + have hk'_len : k' < ((List.range problem.matrix.size).filterMap (fun i ↦ + if rowsContainLeadingPosition rows shift i then none + else some (monomialUnitRow (F := F) problem.matrix.size i + (leafDegreeCap problem)))).length := by + simpa using hk' + rw [array_getD_of_lt' _ _ hk, array_getD_of_lt' _ _ hk', + List.getElem_toArray, List.getElem_toArray] + exact hpair k k' hk_len hk'_len hkk + +/-- Missing-pivot completion preserves the shifted weak-Popov property. -/ +private theorem pm_completeMissingPivotRows_shiftedWeakPopov [DecidableEq F] + (problem : XAdicProblem F) (shift : Array Nat) {rows : PolynomialMatrix F} + (hwp : ShiftedWeakPopov rows shift) : + ShiftedWeakPopov (completeMissingPivotRows problem shift rows) shift := by + intro i j hi hj hij hpi hpj + rw [completeMissingPivotRows, Array.size_append] at hi hj + rw [completeMissingPivotRows] at hpi hpj ⊢ + rcases Nat.lt_or_ge i rows.size with hi' | hi' <;> + rcases Nat.lt_or_ge j rows.size with hj' | hj' + · rw [pm_append_getD_left _ hi'] at hpi + rw [pm_append_getD_left _ hj'] at hpj + rw [pm_append_getD_left _ hi', pm_append_getD_left _ hj'] + exact hwp i j hi' hj' hij hpi hpj + · rw [pm_append_getD_left _ hi'] at hpi ⊢ + rw [pm_append_getD_right _ hj'] at hpj ⊢ + cases hp : rowShiftedLeadingPosition? (rows.getD i #[]) shift with + | none => exact absurd hp hpi + | some p => + rcases pm_missingCompletionRows_facts problem shift rows + (k := j - rows.size) (by omega) with ⟨im, _him, hposm, hcontm⟩ + rw [hposm] + intro heq + cases Option.some.inj heq + have htrue := pm_rowsContainLeadingPosition_of_getD hi' hp + rw [hcontm] at htrue + cases htrue + · rw [pm_append_getD_left _ hj'] at hpj ⊢ + rw [pm_append_getD_right _ hi'] at hpi ⊢ + cases hp : rowShiftedLeadingPosition? (rows.getD j #[]) shift with + | none => exact absurd hp hpj + | some p => + rcases pm_missingCompletionRows_facts problem shift rows + (k := i - rows.size) (by omega) with ⟨im, _him, hposm, hcontm⟩ + rw [hposm] + intro heq + cases Option.some.inj heq + have htrue := pm_rowsContainLeadingPosition_of_getD hj' hp + rw [hcontm] at htrue + cases htrue + · rw [pm_append_getD_right _ hi', pm_append_getD_right _ hj'] + have hki : i - rows.size < (missingCompletionRows problem shift rows).size := + by omega + have hkj : j - rows.size < (missingCompletionRows problem shift rows).size := + by omega + rcases Nat.lt_or_ge (i - rows.size) (j - rows.size) with h | h + · exact pm_missingCompletionRows_pairwise problem shift rows hki hkj h + · have hlt : j - rows.size < i - rows.size := by omega + exact (pm_missingCompletionRows_pairwise problem shift rows hkj hki + hlt).symm + +/-- **Shifted minimality of the recursive PM-basis.** Every nonzero X-adic +solution row is shifted-degree dominated by some row of the root-normalized +recursive PM-basis. -/ +theorem pmBasis_kernelLeaf_complete_minimal [DecidableEq F] + (mulCtx : CPolynomial.MulContext F) + (lowCtx : PolynomialMatrix.MulLowContext F) + (leafCutoff composeLeafCutoff : Nat) + (problem : XAdicProblem F) (shift : Array Nat) (row : PolynomialRow F) + (hpos : 0 < problem.matrix.size) (hwf : WellFormed problem.matrix) + (happrox : RowApproximates mulCtx problem row) + (hnz : rowIsZero row = false) (hwidth : row.size ≤ problem.matrix.size) : + ∃ basisRow degree, + basisRow ∈ MatrixRows (pmBasis + (kernelLeafRuntimeWithLowAndCompose mulCtx lowCtx leafCutoff + composeLeafCutoff) problem shift) ∧ + basisRow.size ≤ problem.matrix.size ∧ + rowShiftedDegree? basisRow shift = some degree ∧ + ∀ rowDegree, rowShiftedDegree? row shift = some rowDegree → + degree ≤ rowDegree := by + set n := problem.matrix.size with hn + set runtime := kernelLeafRuntimeWithLowAndCompose mulCtx lowCtx leafCutoff + composeLeafCutoff with hruntime + -- Pad the solution row to the principal width. + set row' := row ++ Array.replicate (n - row.size) (0 : CPolynomial F) + with hrow' + have hrow'size : row'.size = n := by + rw [hrow'] + simp + omega + have hrownz : ¬ RowIsZero row := by + intro h + rw [← rowIsZero_iff, hnz] at h + cases h + have hrow'nz : ¬ RowIsZero row' := by + intro h + refine hrownz fun p hp ↦ h p ?_ + rw [hrow', Array.toList_append] + exact List.mem_append.mpr (Or.inl hp) + have hrow'approx : RowApproximates mulCtx problem row' := + pm_rowApproximates_append_replicate mulCtx problem (n - row.size) happrox + have hrow'deg : rowShiftedDegree? row' shift = rowShiftedDegree? row shift := + pm_rowShiftedDegree_append_replicate row shift (n - row.size) + -- The padded row lies in the span of the recursive core. + set core := pmBasisWithFuelCore runtime (pmBasisFuel problem) problem shift + with hcore + have hcoresizes : ∀ r ∈ MatrixRows core, r.size = n := + fun r hr ↦ (pmBasisWithFuelCore_kernelLeaf_rows mulCtx lowCtx leafCutoff + composeLeafCutoff (pmBasisFuel problem) problem shift r hr).2 + have hrow'span : row' ∈ RowSpan core := + pmBasisWithFuelCore_kernelLeaf_rowSpan_complete mulCtx lowCtx leafCutoff + composeLeafCutoff (pmBasisFuel problem) problem shift hpos hwf row' + hrow'approx hrow'size hrow'nz + -- Lift the span through the root pivot reduction. + set red := reduceKernelLeafRowsByPivots core shift with hred + have hred_rows : ∀ r ∈ MatrixRows red, r.size = n ∧ rowIsZero r = false := + reduceKernelLeafRowsByPivots_rows hcoresizes + have hrow'red : row' ∈ RowSpan red := + pm_mem_rowSpan_of_nonzero_rows_mem hpos hcoresizes + (fun r hr ↦ (hred_rows r hr).1) + (fun r hr hrnz ↦ + reduceKernelLeafRowsByPivots_rowSpan_superset hcoresizes hr hrnz) + hrow'span hrow'nz + have hcmp_eq : compactNonzeroRows red = red := by + rw [compactNonzeroRows, Array.filter_eq_self] + intro r hr + rw [(hred_rows r (by rw [MatrixRows, ← Array.mem_def]; exact hr)).2] + rfl + -- The final basis is the completed reduction. + have hbasis_eq : pmBasis runtime problem shift = + completeMissingPivotRows problem shift red := by + rw [pmBasis, pmBasisWithFuel, pmBasisNormalizeRoot, reduceKernelLeafRows, + ← hcore, ← hred, hcmp_eq] + have hfinsizes : ∀ r ∈ MatrixRows (completeMissingPivotRows problem shift + red), r.size = n := by + intro r hr + rw [completeMissingPivotRows, MatrixRows, Array.toList_append] at hr + rcases List.mem_append.mp hr with hr | hr + · exact (hred_rows r hr).1 + · rw [missingCompletionRows, List.toList_toArray] at hr + rcases List.mem_filterMap.mp hr with ⟨i, _hi, hg⟩ + by_cases hc : rowsContainLeadingPosition red shift i + · rw [if_pos hc] at hg + cases hg + · rw [if_neg hc] at hg + rw [← Option.some.inj hg, hn] + simp [monomialUnitRow] + have hrow'fin : row' ∈ RowSpan (completeMissingPivotRows problem shift + red) := by + refine pm_mem_rowSpan_of_nonzero_rows_mem hpos + (fun r hr ↦ (hred_rows r hr).1) hfinsizes ?_ hrow'red hrow'nz + intro r hr _hrnz + refine matrix_row_mem_rowSpan (wellFormed_of_sizes hfinsizes) ?_ + rw [completeMissingPivotRows, MatrixRows, Array.toList_append] + exact List.mem_append.mpr (Or.inl hr) + -- The completed reduction is shifted weak-Popov. + have hwp_red : ShiftedWeakPopov red shift := by + have hred_eq : red = pivotRows (core.toList.foldl + (fun pivots row ↦ insertKernelLeafPivotRowWithFuel + (reduceKernelLeafFuel core shift) pivots shift row) + (Array.replicate (MatrixWidth core) none)) := by + rw [hred, reduceKernelLeafRowsByPivots, ← Array.foldl_toList] + rw [hred_eq] + refine pm_pivotRows_shiftedWeakPopov fun p r hget ↦ ?_ + have hinit : ∀ p r, + (Array.replicate (MatrixWidth core) + (none : Option (PolynomialRow F))).getD p none = some r → + r.size = n ∧ rowShiftedLeadingPosition? r shift = some p := by + intro p r hget + rcases Nat.lt_or_ge p + (Array.replicate (MatrixWidth core) + (none : Option (PolynomialRow F))).size with hp | hp + · rw [array_getD_of_lt' _ _ hp, Array.getElem_replicate] at hget + cases hget + · rw [array_getD_of_le' _ _ hp] at hget + cases hget + exact (insertKernelLeaf_foldl_pivotInv (n := n) core.toList + (reduceKernelLeafFuel core shift) + (Array.replicate (MatrixWidth core) none) shift + (fun r hr ↦ hcoresizes r hr) hinit p r hget).2 + have hwp_fin : ShiftedWeakPopov + (completeMissingPivotRows problem shift red) shift := + pm_completeMissingPivotRows_shiftedWeakPopov problem shift hwp_red + -- Apply the predictable-degree property of weak-Popov matrices. + have hdeg : rowShiftedDegree? row' shift ≠ none := by + rw [hrow'deg] + intro h + exact hrownz (rowShiftedDegree?_eq_none_iff.1 h) + obtain ⟨outRow, outDeg, rowDeg, houtmem, houtdeg, hrowdeg, hle⟩ := + shiftedWeakPopov_least_row_minimal + (completeMissingPivotRows problem shift red) shift row' + (wellFormed_of_sizes hfinsizes) hwp_fin hrow'fin hdeg + refine ⟨outRow, outDeg, by rw [hbasis_eq]; exact houtmem, + le_of_eq (hfinsizes outRow houtmem), houtdeg, ?_⟩ + intro rowDegree hrowDegree + rw [hrow'deg, hrowDegree] at hrowdeg + cases Option.some.inj hrowdeg + exact hle + +end Approximant + +end PolynomialMatrix + +end CompPoly diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/KernelLeaf.lean b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/KernelLeaf.lean new file mode 100644 index 00000000..4887ee12 --- /dev/null +++ b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/KernelLeaf.lean @@ -0,0 +1,410 @@ +/- +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.LinearAlgebra.Dense +import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant.Basic +import CompPoly.LinearAlgebra.PolynomialMatrix.MuldersStorjohannCorrectness.RowOps + +/-! +# Scalar Kernel-Leaf PM-Basis Definitions + +Executable definitions for the classical scalar-kernel PM-basis leaf: the +dense coefficient matrix and its row-array RREF kernel, reconstruction of +polynomial rows from kernel vectors, monomial completion rows, and the +shifted pivot-table reduction used to compact leaf bases. +-/ + +namespace CompPoly + +namespace PolynomialMatrix + +namespace Approximant + +variable {F : Type*} [Field F] [BEq F] [LawfulBEq F] + +/-- Coefficient degree cap used by the classical scalar-kernel PM-basis leaf. -/ +def leafDegreeCap (problem : XAdicProblem F) : Nat := + max 1 (maxOrder problem) + +/-- Coefficient-equation indices `(column, coefficientDegree)`. -/ +def coefficientEquationIndices (orders : Array Nat) : Array (Nat × Nat) := Id.run do + let mut out := #[] + for j in [0:orders.size] do + for t in [0:orders.getD j 0] do + out := out.push (j, t) + pure out + +/-- Dense scalar coefficient matrix for the bounded leaf problem. -/ +def coefficientMatrix (problem : XAdicProblem F) : DenseMatrix F := + let degreeCap := leafDegreeCap problem + let equations := coefficientEquationIndices problem.orders + DenseMatrix.ofFn equations.size (problem.matrix.size * degreeCap) fun row col ↦ + let equation := equations.getD row (0, 0) + let matrixCol := equation.1 + let coeffDegree := equation.2 + let coord := col / degreeCap + let coordDegree := col % degreeCap + if coordDegree ≤ coeffDegree then + CPolynomial.coeff + (PolynomialMatrix.rowGet (problem.matrix.getD coord #[]) matrixCol) + (coeffDegree - coordDegree) + else + 0 + +/-- One scalar coefficient row for the bounded leaf problem. -/ +def coefficientMatrixRow (problem : XAdicProblem F) (degreeCap : Nat) + (equation : Nat × Nat) : Array F := + (List.range (problem.matrix.size * degreeCap)).map + (fun col ↦ + let matrixCol := equation.1 + let coeffDegree := equation.2 + let coord := col / degreeCap + let coordDegree := col % degreeCap + if coordDegree ≤ coeffDegree then + CPolynomial.coeff + (PolynomialMatrix.rowGet (problem.matrix.getD coord #[]) matrixCol) + (coeffDegree - coordDegree) + else + 0) |>.toArray + +/-- Scalar coefficient rows for the bounded leaf problem. This is the same +matrix as `coefficientMatrix`, represented directly as row arrays for the tiny +leaf RREF routine. -/ +def coefficientMatrixRows (problem : XAdicProblem F) : Array (Array F) := + let degreeCap := leafDegreeCap problem + (coefficientEquationIndices problem.orders).map + (coefficientMatrixRow problem degreeCap) + +/-- Swap two scalar rows in a row-array matrix. -/ +def swapScalarRows (rows : Array (Array F)) (rowA rowB : Nat) : + Array (Array F) := + let a := rows.getD rowA #[] + let b := rows.getD rowB #[] + (rows.setIfInBounds rowA b).setIfInBounds rowB a + +/-- Find a nonzero pivot row at or below `startRow` in column `col`. -/ +def findScalarPivotRow (rows : Array (Array F)) (startRow col : Nat) : + Option Nat := + (List.range' startRow (rows.size - startRow)).find? fun row ↦ + (rows.getD row #[]).getD col 0 != 0 + +/-- Scale a scalar row so that column `pivotCol` becomes one. -/ +def normalizeScalarRow (row : Array F) (pivotCol : Nat) : Array F := + let pivot := row.getD pivotCol 0 + if pivot == 0 then + row + else + row.map fun x ↦ x / pivot + +/-- Add `factor * source` to `target`, using zero defaults for ragged rows. -/ +def addScaledScalarRow (target source : Array F) (factor : F) : Array F := + (List.range (max target.size source.size)).map + (fun col ↦ target.getD col 0 + factor * source.getD col 0) |>.toArray + +/-- Normalize one pivot row and clear the pivot column in all other rows. -/ +def normalizeAndEliminateScalarRows (rows : Array (Array F)) + (pivotRow pivotCol : Nat) : Array (Array F) := + let pivot := (rows.getD pivotRow #[]).getD pivotCol 0 + if pivot == 0 then + rows + else + let pivotVector := normalizeScalarRow (rows.getD pivotRow #[]) pivotCol + let rows := rows.setIfInBounds pivotRow pivotVector + (List.range rows.size).foldl + (fun rows row ↦ + if row == pivotRow then + rows + else + let factor := -((rows.getD row #[]).getD pivotCol 0) + if factor == 0 then + rows + else + rows.setIfInBounds row + (addScaledScalarRow (rows.getD row #[]) pivotVector factor)) + rows + +/-- RREF result for a scalar row-array matrix. -/ +structure ScalarRrefResult where + rows : Array (Array F) + pivots : Array Nat + +/-- Fuel-bounded row-array RREF for tiny scalar coefficient matrices. -/ +def scalarRrefRowsLoop (cols : Nat) : + Nat → Nat → Nat → Array (Array F) → Array Nat → ScalarRrefResult (F := F) + | 0, _col, _row, rows, pivots => { rows := rows, pivots := pivots } + | fuel + 1, col, row, rows, pivots => + if col >= cols || row >= rows.size then + { rows := rows, pivots := pivots } + else + match findScalarPivotRow rows row col with + | none => scalarRrefRowsLoop cols fuel (col + 1) row rows pivots + | some pivotRow => + let swapped := swapScalarRows rows pivotRow row + let reduced := normalizeAndEliminateScalarRows swapped row col + scalarRrefRowsLoop cols fuel (col + 1) (row + 1) reduced + (pivots.push col) + +/-- Row-array RREF for tiny scalar coefficient matrices. -/ +def scalarRrefRows (rows : Array (Array F)) (cols : Nat) : + ScalarRrefResult (F := F) := + scalarRrefRowsLoop cols (cols + 1) 0 0 rows #[] + +/-- Kernel basis vector for one free column of a row-array RREF matrix. -/ +def basisVectorForFreeColumnRows (rows : Array (Array F)) + (pivots : Array Nat) (cols free : Nat) : Array F := + Array.ofFn fun i : Fin cols ↦ + if i.val == free then + 1 + else + match DenseMatrix.pivotRowOfColumn? pivots i.val with + | none => 0 + | some row => -((rows.getD row #[]).getD free 0) + +/-- Homogeneous scalar-kernel basis for a row-array matrix. -/ +def homogeneousKernelBasisRows (rows : Array (Array F)) (cols : Nat) : + Array (Array F) := + let R := scalarRrefRows rows cols + (DenseMatrix.freeColumns cols R.pivots).map + (basisVectorForFreeColumnRows R.rows R.pivots cols) + +/-- Convert one scalar kernel vector back into a polynomial row. -/ +def vectorToPolynomialRow (degreeCap solutionWidth : Nat) (v : Array F) : + PolynomialRow F := + (List.range solutionWidth).map + (fun coord ↦ + CPolynomial.ofArray + ((List.range degreeCap).map + (fun degree ↦ v.getD (coord * degreeCap + degree) 0) |>.toArray)) |>.toArray + +/-- Polynomial `c * X^d`, built without the `CPolynomial.monomial` +`DecidableEq` assumption. -/ +def coeffXPower (c : F) (d : Nat) : CPolynomial F := + CPolynomial.ofArray ((Array.replicate d 0).push c) + +/-- Multiply a row by `c * X^d`, using the coefficient-array monomial builder. -/ +def polynomialScaleCoeffX (c : F) (d : Nat) (p : CPolynomial F) : + CPolynomial F := + if c == 0 then + 0 + else if p == 0 then + 0 + else + CPolynomial.ofArray + ((List.replicate d 0 ++ p.val.toList.map (fun a ↦ c * a)).toArray) + +/-- Multiply a row by `c * X^d`, using coefficient shifting instead of generic +polynomial multiplication by a monomial. -/ +def rowScaleCoeffX (c : F) (d : Nat) (row : PolynomialRow F) : + PolynomialRow F := + row.map fun p ↦ polynomialScaleCoeffX c d p + +/-- Monomial row `X^d * e_i`, used to complete bounded-kernel leaves to a full +approximant basis. -/ +def monomialUnitRow (width i d : Nat) : PolynomialRow F := + (List.range width).map + (fun j ↦ if i == j then coeffXPower 1 d else 0) |>.toArray + +/-- Trivial high-degree approximants present in every X-adic problem. These +rows are essential when the bounded scalar kernel has fewer rows than the module +rank. -/ +def kernelLeafCompletionRows (problem : XAdicProblem F) : + PolynomialMatrix F := + let degreeCap := leafDegreeCap problem + (List.range problem.matrix.size).map + (fun i ↦ monomialUnitRow problem.matrix.size i degreeCap) |>.toArray + +/-- Whether a row set already contains a row with a given shifted leading +position. -/ +def rowsContainLeadingPosition (rows : PolynomialMatrix F) + (shift : Array Nat) (position : Nat) : Bool := + rows.any fun row ↦ + match rowShiftedLeadingPosition? row shift with + | some p => p == position + | none => false + +/-- High monomial rows for shifted leading positions not represented by `rows`. +These rows are always valid approximants and keep recursive residual problems +from losing coordinates after compact row reduction. -/ +def missingCompletionRows (problem : XAdicProblem F) + (shift : Array Nat) (rows : PolynomialMatrix F) : + PolynomialMatrix F := + let degreeCap := leafDegreeCap problem + (List.range problem.matrix.size).filterMap + (fun i ↦ + if rowsContainLeadingPosition rows shift i then + none + else + some (monomialUnitRow problem.matrix.size i degreeCap)) |>.toArray + +/-- Add high monomial approximants for missing pivot positions. -/ +def completeMissingPivotRows (problem : XAdicProblem F) + (shift : Array Nat) (rows : PolynomialMatrix F) : + PolynomialMatrix F := + rows ++ missingCompletionRows problem shift rows + +/-- Cancel the shifted leading term of `target` by `reducer`, when their shifted +leading positions agree. This is the small-leaf analogue of polynomial-matrix +row reduction; it is used only after the bounded scalar kernel has already been +computed. -/ +def cancelKernelLeafLeadingTerm + (target reducer : PolynomialRow F) (shift : Array Nat) : PolynomialRow F := + match rowShiftedLeadingTerm? target shift, rowShiftedLeadingTerm? reducer shift with + | some t, some r => + if t.position == r.position then + if r.coeff == 0 then + target + else + rowSub target (rowScaleCoeffX (t.coeff / r.coeff) (t.degree - r.degree) reducer) + else + target + | _, _ => target + +/-- One inner-loop update for finding a leading-position conflict in a bounded +kernel leaf. -/ +def kernelLeafConflictInRowStep? (rows : PolynomialMatrix F) (shift : Array Nat) + (i : Nat) (found : Option (Nat × Nat)) (j : Nat) : Option (Nat × Nat) := + match found with + | some _ => found + | none => + match rowShiftedLeadingPosition? (rows.getD i #[]) shift, + rowShiftedLeadingPosition? (rows.getD j #[]) shift with + | some pi, some pj => if pi == pj then some (i, j) else none + | _, _ => none + +/-- Scan one row for a shifted-leading-position conflict in a bounded kernel +leaf. -/ +def kernelLeafConflictInRow? (rows : PolynomialMatrix F) (shift : Array Nat) + (i : Nat) (found : Option (Nat × Nat)) : Option (Nat × Nat) := + (List.range' (i + 1) (rows.size - (i + 1))).foldl + (kernelLeafConflictInRowStep? rows shift i) found + +/-- Scan all row pairs for the first shifted-leading-position conflict. -/ +def kernelLeafConflictFrom? (rows : PolynomialMatrix F) (shift : Array Nat) + (found : Option (Nat × Nat)) : Option (Nat × Nat) := + (List.range rows.size).foldl + (fun acc i ↦ kernelLeafConflictInRow? rows shift i acc) found + +/-- First pair of nonzero bounded-kernel rows with the same shifted leading +position. -/ +def kernelLeafConflict? (rows : PolynomialMatrix F) (shift : Array Nat) : + Option (Nat × Nat) := + kernelLeafConflictFrom? rows shift none + +/-- One shifted-reduction step for bounded scalar-kernel rows. -/ +def reduceKernelLeafStep (rows : PolynomialMatrix F) (shift : Array Nat) + (i j : Nat) : PolynomialMatrix F := + let rowI := rows.getD i #[] + let rowJ := rows.getD j #[] + match rowShiftedDegree? rowI shift, rowShiftedDegree? rowJ shift with + | some degI, some degJ => + if degI ≤ degJ then + replaceRow rows j (cancelKernelLeafLeadingTerm rowJ rowI shift) + else + replaceRow rows i (cancelKernelLeafLeadingTerm rowI rowJ shift) + | _, _ => rows + +/-- Fuel for bounded-kernel shifted reduction. The scalar leaf is already a +small base case, so this conservative degree-width bound is acceptable here. -/ +def reduceKernelLeafFuel (rows : PolynomialMatrix F) (shift : Array Nat) : Nat := + let maxDegree := (List.range rows.size).foldl + (fun acc i ↦ + match rowShiftedDegree? (rows.getD i #[]) shift with + | none => acc + | some degree => max acc degree) + 0 + (rows.size + 1) * (MatrixWidth rows + 1) * (maxDegree + 1) + +/-- Extract the nonempty pivot rows from a leading-position table. -/ +def pivotRows (pivots : Array (Option (PolynomialRow F))) : + PolynomialMatrix F := + pivots.toList.filterMap id |>.toArray + +/-- Insert one row into a shifted weak-Popov pivot table. Conflicts are resolved +only at the current leading position, avoiding the repeated global pair scans +used by the simple reference reducer. -/ +def insertKernelLeafPivotRowWithFuel : + Nat → Array (Option (PolynomialRow F)) → Array Nat → PolynomialRow F → + Array (Option (PolynomialRow F)) + | 0, pivots, _shift, _row => pivots + | fuel + 1, pivots, shift, row => + match rowShiftedLeadingTerm? row shift with + | none => pivots + | some target => + match pivots.getD target.position none with + | none => pivots.setIfInBounds target.position (some row) + | some pivot => + match rowShiftedLeadingTerm? pivot shift with + | none => pivots.setIfInBounds target.position (some row) + | some reducer => + if target.shiftedDegree < reducer.shiftedDegree then + let reducedPivot := cancelKernelLeafLeadingTerm pivot row shift + let pivots := pivots.setIfInBounds target.position (some row) + insertKernelLeafPivotRowWithFuel fuel pivots shift reducedPivot + else + let reducedRow := cancelKernelLeafLeadingTerm row pivot shift + insertKernelLeafPivotRowWithFuel fuel pivots shift reducedRow + +/-- Pivot-table shifted reduction for bounded scalar-kernel rows. -/ +def reduceKernelLeafRowsByPivots (rows : PolynomialMatrix F) (shift : Array Nat) : + PolynomialMatrix F := + let fuel := reduceKernelLeafFuel rows shift + let pivots := rows.foldl + (fun pivots row ↦ insertKernelLeafPivotRowWithFuel fuel pivots shift row) + (Array.replicate (MatrixWidth rows) none) + pivotRows pivots + +/-- Shift-reduce the bounded scalar-kernel rows before compacting them. This +keeps one low representative per shifted leading position instead of selecting +arbitrary low-degree kernel vectors. -/ +def reduceKernelLeafWithFuel : + Nat → PolynomialMatrix F → Array Nat → PolynomialMatrix F + | 0, rows, _shift => rows + | fuel + 1, rows, shift => + match kernelLeafConflict? rows shift with + | none => rows + | some (i, j) => + reduceKernelLeafWithFuel fuel (reduceKernelLeafStep rows shift i j) shift + +/-- Shift-reduced bounded scalar-kernel rows for the PM-basis leaf. -/ +def reduceKernelLeafRows (rows : PolynomialMatrix F) (shift : Array Nat) : + PolynomialMatrix F := + reduceKernelLeafRowsByPivots rows shift + +/-- Insert one bounded-kernel row into a small shifted-reduced leaf basis. This +keeps the live reduction matrix near the module width instead of reducing the +entire scalar kernel at once. -/ +def insertKernelLeafRowIncremental (basis : PolynomialMatrix F) + (shift : Array Nat) (row : PolynomialRow F) : PolynomialMatrix F := + (reduceKernelLeafRows (basis.push row) shift).filter fun row ↦ !rowIsZero row + +/-- Shift-reduce all bounded scalar-kernel rows incrementally. The dense scalar +kernel can have many rows, but after every insertion the weak-Popov conflict loop +works on the current reduced basis plus one candidate row. -/ +def reduceKernelLeafRowsIncremental (rows : PolynomialMatrix F) + (shift : Array Nat) : PolynomialMatrix F := + rows.foldl (fun basis row ↦ insertKernelLeafRowIncremental basis shift row) #[] + +/-- Classical scalar-kernel leaf for small X-adic approximant problems. -/ +def kernelLeafBasis (problem : XAdicProblem F) (shift : Array Nat) : + PolynomialMatrix F := + let degreeCap := leafDegreeCap problem + let scalarRows := (homogeneousKernelBasisRows (coefficientMatrixRows problem) + (problem.matrix.size * degreeCap)).map + (vectorToPolynomialRow degreeCap problem.matrix.size) + completeMissingPivotRows problem shift + (reduceKernelLeafRowsIncremental + (scalarRows ++ kernelLeafCompletionRows problem) shift) + +/-- Remove zero rows before a recursively computed approximant basis is used as +the coordinate system for the next residual problem. -/ +def compactNonzeroRows (rows : PolynomialMatrix F) : PolynomialMatrix F := + rows.filter fun row ↦ !rowIsZero row + +end Approximant + +end PolynomialMatrix + +end CompPoly diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/KernelLeafCompleteness.lean b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/KernelLeafCompleteness.lean new file mode 100644 index 00000000..047c58d6 --- /dev/null +++ b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/KernelLeafCompleteness.lean @@ -0,0 +1,597 @@ +/- +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.LinearAlgebra.PolynomialMatrix.Approximant.PMBasis.KernelLeafSoundness +import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant.PMBasis.KernelLeafSpan + +/-! +# Kernel-Leaf Basis Generation Completeness + +Every nonzero solution row of an X-adic problem lies in the row module +generated by the kernel-leaf basis: the high part is generated by the +monomial completion rows and the bounded remainder by the scalar kernel. +-/ + +namespace CompPoly + +namespace PolynomialMatrix + +namespace Approximant + +variable {F : Type*} [Field F] [BEq F] [LawfulBEq F] + +/-! ## Kernel-leaf basis generation completeness + +Every solution row of an X-adic problem splits into a low part, whose packed +coefficient vector lies in the bounded scalar kernel, plus a high part in which +every entry is divisible by `X^(leafDegreeCap problem)`. The low part is a +constant-coefficient combination of the reconstructed scalar-kernel rows by +`homogeneousKernelBasisRows_complete`, the high part is a polynomial +combination of the monomial completion rows, and the reduction and completion +steps only enlarge the generated row module. Together these facts show that +the kernel-leaf basis generates every solution row. -/ + +section GenerationCompleteness + +variable {α β : Type*} + +omit [Field F] [BEq F] [LawfulBEq F] in +/-- `getD` of an array append, left side. -/ +theorem pm_append_getD_left {A B : Array α} (d : α) {i : Nat} + (hi : i < A.size) : (A ++ B).getD i d = A.getD i d := by + rw [Array.getD_eq_getD_getElem?, Array.getD_eq_getD_getElem?, + Array.getElem?_append_left hi] + +omit [Field F] [BEq F] [LawfulBEq F] in +/-- `getD` of an array append, right side. -/ +theorem pm_append_getD_right {A B : Array α} (d : α) {i : Nat} + (hi : A.size ≤ i) : (A ++ B).getD i d = B.getD (i - A.size) d := by + rw [Array.getD_eq_getD_getElem?, Array.getD_eq_getD_getElem?, + Array.getElem?_append_right hi] + +omit [Field F] [BEq F] [LawfulBEq F] in +private theorem pm_map_getD (f : α → β) (xs : Array α) (d : α) (e : β) + {i : Nat} (hi : i < xs.size) : + (xs.map f).getD i e = f (xs.getD i d) := by + rw [array_getD_of_lt' _ _ (by simpa using hi), Array.getElem_map, + array_getD_of_lt' _ _ hi] + +end GenerationCompleteness + +/-- Constant polynomial factors act coefficientwise. -/ +private theorem pm_coeff_C_mul (c : F) (p : CPolynomial F) (t : Nat) : + CPolynomial.coeff (CPolynomial.C c * p) t = c * CPolynomial.coeff p t := by + rw [CPolynomial.coeff_toPoly, CPolynomial.toPoly_mul, CPolynomial.C_toPoly, + Polynomial.coeff_C_mul, ← CPolynomial.coeff_toPoly] + +/-- Coefficients of finite polynomial sums are coefficient sums. -/ +private theorem pm_coeff_finset_sum (f : Nat → CPolynomial F) (m t : Nat) : + CPolynomial.coeff (∑ i ∈ Finset.range m, f i) t = + ∑ i ∈ Finset.range m, CPolynomial.coeff (f i) t := by + rw [CPolynomial.coeff_toPoly, pm_toPoly_finset_sum, Polynomial.finset_sum_coeff] + exact Finset.sum_congr rfl fun i _hi ↦ (CPolynomial.coeff_toPoly (f i) t).symm + +/-- Coefficients of a product with the monomial `X^d`. -/ +private theorem pm_coeff_mul_coeffXPower (q : CPolynomial F) (d t : Nat) : + CPolynomial.coeff (q * coeffXPower 1 d) t = + if d ≤ t then CPolynomial.coeff q (t - d) else 0 := by + rw [CPolynomial.coeff_toPoly, CPolynomial.toPoly_mul, coeffXPower_toPoly, + Polynomial.C_1, one_mul, Polynomial.coeff_mul_X_pow', + ← CPolynomial.coeff_toPoly] + +/-- A polynomial with vanishing low coefficients truncates to zero. -/ +private theorem pm_truncateX_eq_zero_of_coeff (order : Nat) {p : CPolynomial F} + (h : ∀ t, t < order → CPolynomial.coeff p t = 0) : + truncateX order p = 0 := by + apply CPolynomial.eq_iff_coeff.2 + intro t + rw [truncateX_coeff, CPolynomial.coeff_zero] + rcases Nat.lt_or_ge t order with ht | ht + · rw [if_pos ht] + exact h t ht + · rw [if_neg (by omega)] + +/-- A polynomial with vanishing low coefficients factors through `X^cap` with +quotient `divXTrunc cap`. -/ +private theorem pm_high_factor {cap : Nat} {p : CPolynomial F} + (hlow : ∀ t, t < cap → CPolynomial.coeff p t = 0) : + divXTrunc cap p.val.size p * coeffXPower 1 cap = p := by + apply CPolynomial.eq_iff_coeff.2 + intro t + rw [pm_coeff_mul_coeffXPower] + rcases Nat.lt_or_ge t cap with ht | ht + · rw [if_neg (by omega), hlow t ht] + · rw [if_pos ht, divXTrunc_coeff] + rcases Nat.lt_or_ge (t - cap) p.val.size with hs | hs + · rw [if_pos hs, Nat.sub_add_cancel ht] + · rw [if_neg (by omega), CPolynomial.coeff_eq_zero_of_size_le p (by omega)] + +/-- Entries of a monomial unit row. -/ +theorem pm_rowGet_monomialUnitRow (n i d k : Nat) : + rowGet (monomialUnitRow (F := F) n i d) k = + if k < n ∧ i = k then coeffXPower 1 d else 0 := by + rcases Nat.lt_or_ge k n with hk | hk + · rw [monomialUnitRow, rowGet, Array.getD_eq_getD_getElem?, + List.getElem?_toArray, List.getElem?_map, List.getElem?_range hk, + Option.map_some, Option.getD_some] + by_cases hik : i = k + · rw [if_pos (beq_iff_eq.mpr hik), if_pos ⟨hk, hik⟩] + · rw [if_neg (by simpa using hik), if_neg fun h ↦ hik h.2] + · rw [monomialUnitRow, rowGet, Array.getD_eq_getD_getElem?, + List.getElem?_toArray, List.getElem?_eq_none (by simpa using hk), + Option.getD_none, if_neg fun h ↦ absurd h.1 (Nat.not_lt.mpr hk)] + +/-- The completion matrix has one row per module coordinate. -/ +private theorem pm_completionRows_size (problem : XAdicProblem F) : + (kernelLeafCompletionRows problem).size = problem.matrix.size := by + simp [kernelLeafCompletionRows] + +/-- In-range rows of the completion matrix are monomial unit rows. -/ +private theorem pm_completionRows_getD (problem : XAdicProblem F) {i : Nat} + (hi : i < problem.matrix.size) : + (kernelLeafCompletionRows problem).getD i #[] = + monomialUnitRow problem.matrix.size i (leafDegreeCap problem) := by + simp only [kernelLeafCompletionRows] + rw [Array.getD_eq_getD_getElem?, List.getElem?_toArray, List.getElem?_map, + List.getElem?_range hi, Option.map_some, Option.getD_some] + +/-- Entries of a row linear combination are coefficient-weighted entry sums. -/ +theorem pm_rowGet_rowLinearCombination (coeffs : Array (CPolynomial F)) + (M : PolynomialMatrix F) (j : Nat) : + rowGet (rowLinearCombination coeffs M) j = + ∑ i ∈ Finset.range M.size, coeffs.getD i 0 * rowGet (M.getD i #[]) j := by + rw [rowLinearCombination] + have hfold : ∀ m : Nat, + rowGet ((List.range m).foldl + (fun acc i ↦ rowAdd acc + (rowScalePolynomial (coeffs.getD i 0) (M.getD i #[]))) + (zeroRow (MatrixWidth M))) j = + ∑ i ∈ Finset.range m, coeffs.getD i 0 * rowGet (M.getD i #[]) j := by + intro m + induction m with + | zero => simp [rowGet_zeroRow] + | succ m ih => + rw [List.range_succ, List.foldl_append, List.foldl_cons, List.foldl_nil, + rowGet_rowAdd, ih, rowGet_rowScalePolynomial, Finset.sum_range_succ] + exact hfold M.size + +/-- Row linear combinations of uniform-width matrices keep that width. -/ +theorem pm_rowLinearCombination_size {M : PolynomialMatrix F} {n : Nat} + (hsizes : ∀ r ∈ MatrixRows M, r.size = n) (hw : MatrixWidth M = n) + (coeffs : Array (CPolynomial F)) : + (rowLinearCombination coeffs M).size = n := by + rw [rowLinearCombination] + have hfold : ∀ m : Nat, m ≤ M.size → + ((List.range m).foldl + (fun acc i ↦ rowAdd acc + (rowScalePolynomial (coeffs.getD i 0) (M.getD i #[]))) + (zeroRow (MatrixWidth M))).size = n := by + intro m + induction m with + | zero => + intro _ + simp [zeroRow, hw] + | succ m ih => + intro hm + rw [List.range_succ, List.foldl_append, List.foldl_cons, List.foldl_nil, + rowAdd_size, ih (by omega), rowScalePolynomial_size, + hsizes _ (getD_mem_matrixRows (by omega)), Nat.max_self] + exact hfold M.size (Nat.le_refl _) + +omit [BEq F] [LawfulBEq F] in +/-- Rows agreeing in size and on every `rowGet` entry are equal. -/ +theorem pm_row_ext {a b : PolynomialRow F} (hsize : a.size = b.size) + (h : ∀ j, rowGet a j = rowGet b j) : a = b := by + refine Array.ext hsize fun j hj hj' ↦ ?_ + have hget := h j + rw [rowGet, rowGet, array_getD_of_lt' _ _ hj, array_getD_of_lt' _ _ hj'] + at hget + exact hget + +/-- Rows whose entries are all divisible by `X^(leafDegreeCap problem)` satisfy +every X-adic condition. -/ +private theorem pm_rowApproximates_of_entry_dvd (mulCtx : CPolynomial.MulContext F) + (problem : XAdicProblem F) {row : PolynomialRow F} + (h : ∀ k, (Polynomial.X : Polynomial F) ^ leafDegreeCap problem ∣ + (rowGet row k).toPoly) : + RowApproximates mulCtx problem row := by + rw [rowApproximates_iff] + intro j hj _hjw + refine Finset.dvd_sum fun k _hk ↦ ?_ + refine Dvd.dvd.mul_right (dvd_trans (pow_dvd_pow _ ?_) (h k)) _ + exact le_trans (getD_le_maxOrder problem hj) (Nat.le_max_right 1 _) + +/-- All kernel-leaf input rows have the principal row width. -/ +private theorem pm_kernelLeafUnion_sizes (problem : XAdicProblem F) : + ∀ r ∈ MatrixRows + ((homogeneousKernelBasisRows (coefficientMatrixRows problem) + (problem.matrix.size * leafDegreeCap problem)).map + (vectorToPolynomialRow (leafDegreeCap problem) problem.matrix.size) ++ + kernelLeafCompletionRows problem), + r.size = problem.matrix.size := by + intro r hr + rw [MatrixRows, Array.toList_append] at hr + rcases List.mem_append.mp hr with hr | hr + · rw [Array.toList_map] at hr + rcases List.mem_map.mp hr with ⟨w, _hw, rfl⟩ + simp [vectorToPolynomialRow] + · simp only [kernelLeafCompletionRows] at hr + rcases List.mem_map.mp hr with ⟨i, _hi, rfl⟩ + simp [monomialUnitRow] + +/-- **Low part.** A bounded solution row, all of whose entry coefficients live +below the leaf degree cap, lies in the row span of the kernel-leaf input rows: +its packed coefficient vector is orthogonal to the scalar coefficient matrix, +so scalar-kernel completeness reconstructs it as a constant-coefficient +combination of the reconstructed kernel rows. -/ +private theorem pm_lowRow_mem_rowSpan_union (mulCtx : CPolynomial.MulContext F) + (problem : XAdicProblem F) (hpos : 0 < problem.matrix.size) + (hwf : WellFormed problem.matrix) {rem : PolynomialRow F} + (happrox : RowApproximates mulCtx problem rem) + (hsize : rem.size = problem.matrix.size) + (hdeg : ∀ k, k < rem.size → ∀ a, leafDegreeCap problem ≤ a → + CPolynomial.coeff (rowGet rem k) a = 0) : + rem ∈ RowSpan + ((homogeneousKernelBasisRows (coefficientMatrixRows problem) + (problem.matrix.size * leafDegreeCap problem)).map + (vectorToPolynomialRow (leafDegreeCap problem) problem.matrix.size) ++ + kernelLeafCompletionRows problem) := by + have hcap_pos : 0 < leafDegreeCap problem := le_max_left 1 _ + have hrt := vectorToPolynomialRow_rowToCoefficientVector problem hsize hdeg + have hdot := coefficientMatrixRows_dot_eq_zero_of_approximates mulCtx problem + happrox hwf + have hcomplete := homogeneousKernelBasisRows_complete + (coefficientMatrixRows problem) + (problem.matrix.size * leafDegreeCap problem) hdot + set cols := problem.matrix.size * leafDegreeCap problem with hcols + set kb := homogeneousKernelBasisRows (coefficientMatrixRows problem) cols + with hkbdef + set free := DenseMatrix.freeColumns cols + (scalarRrefRows (coefficientMatrixRows problem) cols).pivots with hfreedef + set v := rowToCoefficientVector problem rem with hvdef + set M := kb.map + (vectorToPolynomialRow (leafDegreeCap problem) problem.matrix.size) ++ + kernelLeafCompletionRows problem with hMdef + have hMsizes : ∀ r ∈ MatrixRows M, r.size = problem.matrix.size := by + rw [hMdef, hkbdef, hcols] + exact pm_kernelLeafUnion_sizes problem + have hMsize : M.size = kb.size + problem.matrix.size := by + rw [hMdef, Array.size_append, Array.size_map, pm_completionRows_size] + have hMpos : 0 < M.size := by omega + have hMwidth : MatrixWidth M = problem.matrix.size := + matrixWidth_eq_of_first_row hMpos hMsizes + set coeffs : Array (CPolynomial F) := Array.ofFn (fun i : Fin M.size ↦ + if i.val < kb.size then CPolynomial.C (v.getD (free.getD i.val 0) 0) + else 0) + with hcoeffsdef + have hcoeffs_getD : ∀ x, x < M.size → + coeffs.getD x 0 = + if x < kb.size then CPolynomial.C (v.getD (free.getD x 0) 0) + else 0 := by + intro x hx + rw [hcoeffsdef, array_getD_of_lt' _ _ (by simpa using hx)] + simp + refine ⟨coeffs, by rw [hcoeffsdef]; simp, ?_⟩ + refine pm_row_ext + (by rw [hsize, pm_rowLinearCombination_size hMsizes hMwidth]) ?_ + intro j + rw [pm_rowGet_rowLinearCombination] + apply CPolynomial.eq_iff_coeff.2 + intro a + rw [pm_coeff_finset_sum] + have hsub : Finset.range kb.size ⊆ Finset.range M.size := by + intro x hx + rw [Finset.mem_range] at hx ⊢ + omega + have hvan : ∀ x ∈ Finset.range M.size, x ∉ Finset.range kb.size → + CPolynomial.coeff (coeffs.getD x 0 * rowGet (M.getD x #[]) j) a = 0 := by + intro x hx hxn + have hxM : x < M.size := Finset.mem_range.mp hx + have hxkb : kb.size ≤ x := by + rcases Nat.lt_or_ge x kb.size with h | h + · exact absurd (Finset.mem_range.mpr h) hxn + · exact h + rw [hcoeffs_getD x hxM, if_neg (by omega), zero_mul, + CPolynomial.coeff_zero] + refine Eq.trans ?_ (Finset.sum_subset hsub hvan) + have hterm : ∀ i ∈ Finset.range kb.size, + CPolynomial.coeff (coeffs.getD i 0 * rowGet (M.getD i #[]) j) a = + v.getD (free.getD i 0) 0 * + (if j < problem.matrix.size ∧ a < leafDegreeCap problem then + (kb.getD i #[]).getD (j * leafDegreeCap problem + a) 0 + else 0) := by + intro i hi + have hikb : i < kb.size := Finset.mem_range.mp hi + have hiM : i < M.size := by omega + have hMget : M.getD i #[] = + vectorToPolynomialRow (leafDegreeCap problem) problem.matrix.size + (kb.getD i #[]) := by + rw [hMdef, pm_append_getD_left _ (by rw [Array.size_map]; exact hikb), + pm_map_getD _ _ #[] _ hikb] + rw [hcoeffs_getD i hiM, if_pos hikb, hMget, pm_coeff_C_mul, + rowGet_vectorToPolynomialRow_coeff] + have hlhs : CPolynomial.coeff (rowGet rem j) a = + if j < problem.matrix.size ∧ a < leafDegreeCap problem then + v.getD (j * leafDegreeCap problem + a) 0 + else 0 := by + rw [← hrt, rowGet_vectorToPolynomialRow_coeff] + rw [hlhs, Finset.sum_congr rfl hterm] + by_cases hcond : j < problem.matrix.size ∧ a < leafDegreeCap problem + · rw [if_pos hcond] + obtain ⟨hidx, -, -⟩ := pm_pack_index hcap_pos hcond.1 hcond.2 + refine Eq.trans (hcomplete _ (by rw [hcols]; exact hidx)) ?_ + exact Finset.sum_congr rfl fun i _hi ↦ by rw [if_pos hcond] + · rw [if_neg hcond] + exact (Finset.sum_eq_zero fun i _hi ↦ by rw [if_neg hcond, mul_zero]).symm + +/-- **High part.** A row whose entry coefficients all vanish below the leaf +degree cap lies in the row span of the kernel-leaf input rows: every entry +factors through `X^(leafDegreeCap problem)`, so the row is a polynomial +combination of the monomial completion rows. -/ +private theorem pm_highRow_mem_rowSpan_union (problem : XAdicProblem F) + (hpos : 0 < problem.matrix.size) {hi : PolynomialRow F} + (hsize : hi.size = problem.matrix.size) + (hlow : ∀ k a, a < leafDegreeCap problem → + CPolynomial.coeff (rowGet hi k) a = 0) : + hi ∈ RowSpan + ((homogeneousKernelBasisRows (coefficientMatrixRows problem) + (problem.matrix.size * leafDegreeCap problem)).map + (vectorToPolynomialRow (leafDegreeCap problem) problem.matrix.size) ++ + kernelLeafCompletionRows problem) := by + set A := (homogeneousKernelBasisRows (coefficientMatrixRows problem) + (problem.matrix.size * leafDegreeCap problem)).map + (vectorToPolynomialRow (leafDegreeCap problem) problem.matrix.size) + with hAdef + set M := A ++ kernelLeafCompletionRows problem with hMdef + have hMsizes : ∀ r ∈ MatrixRows M, r.size = problem.matrix.size := by + rw [hMdef, hAdef] + exact pm_kernelLeafUnion_sizes problem + have hMsize : M.size = A.size + problem.matrix.size := by + rw [hMdef, Array.size_append, pm_completionRows_size] + have hMpos : 0 < M.size := by omega + have hMwidth : MatrixWidth M = problem.matrix.size := + matrixWidth_eq_of_first_row hMpos hMsizes + have hMget_right : ∀ {b : Nat}, A.size ≤ b → b < M.size → + M.getD b #[] = + monomialUnitRow problem.matrix.size (b - A.size) + (leafDegreeCap problem) := by + intro b hb hbM + rw [hMdef, pm_append_getD_right _ hb, + pm_completionRows_getD problem (by omega)] + set coeffs : Array (CPolynomial F) := Array.ofFn (fun i : Fin M.size ↦ + if A.size ≤ i.val then + divXTrunc (leafDegreeCap problem) + ((rowGet hi (i.val - A.size)).val.size) (rowGet hi (i.val - A.size)) + else 0) + with hcoeffsdef + have hcoeffs_getD : ∀ x, x < M.size → + coeffs.getD x 0 = + if A.size ≤ x then + divXTrunc (leafDegreeCap problem) + ((rowGet hi (x - A.size)).val.size) (rowGet hi (x - A.size)) + else 0 := by + intro x hx + rw [hcoeffsdef, array_getD_of_lt' _ _ (by simpa using hx)] + simp + refine ⟨coeffs, by rw [hcoeffsdef]; simp, ?_⟩ + refine pm_row_ext + (by rw [hsize, pm_rowLinearCombination_size hMsizes hMwidth]) ?_ + intro j + rw [pm_rowGet_rowLinearCombination] + by_cases hj : j < problem.matrix.size + · have hother : ∀ b ∈ Finset.range M.size, b ≠ A.size + j → + coeffs.getD b 0 * rowGet (M.getD b #[]) j = 0 := by + intro b hb hbne + have hbM : b < M.size := Finset.mem_range.mp hb + rcases Nat.lt_or_ge b A.size with hbA | hbA + · rw [hcoeffs_getD b hbM, if_neg (by omega), zero_mul] + · rw [hMget_right hbA hbM, pm_rowGet_monomialUnitRow, + if_neg (by rintro ⟨-, h2⟩; exact hbne (by omega)), mul_zero] + refine (Eq.trans (Finset.sum_eq_single_of_mem (A.size + j) + (Finset.mem_range.mpr (by omega)) hother) ?_).symm + rw [hcoeffs_getD _ (by omega), if_pos (Nat.le_add_right _ _), + hMget_right (Nat.le_add_right _ _) (by omega), Nat.add_sub_cancel_left, + pm_rowGet_monomialUnitRow, if_pos ⟨hj, rfl⟩] + exact pm_high_factor fun t ht ↦ hlow j t ht + · have hzero : rowGet hi j = 0 := by + rw [rowGet, array_getD_of_le' _ _ (by omega)] + rw [hzero] + refine (Finset.sum_eq_zero fun b hb ↦ ?_).symm + have hbM : b < M.size := Finset.mem_range.mp hb + rcases Nat.lt_or_ge b A.size with hbA | hbA + · rw [hcoeffs_getD b hbM, if_neg (by omega), zero_mul] + · rw [hMget_right hbA hbM, pm_rowGet_monomialUnitRow, + if_neg fun h ↦ hj h.1, mul_zero] + +/-- **Decomposition.** Every solution row splits as a truncated low part plus +an `X^(leafDegreeCap)`-divisible high part, both of which stay in the row span +of the kernel-leaf input rows. -/ +private theorem pm_solution_mem_rowSpan_union (mulCtx : CPolynomial.MulContext F) + (problem : XAdicProblem F) (hpos : 0 < problem.matrix.size) + (hwf : WellFormed problem.matrix) {row : PolynomialRow F} + (happrox : RowApproximates mulCtx problem row) + (hsize : row.size = problem.matrix.size) : + row ∈ RowSpan + ((homogeneousKernelBasisRows (coefficientMatrixRows problem) + (problem.matrix.size * leafDegreeCap problem)).map + (vectorToPolynomialRow (leafDegreeCap problem) problem.matrix.size) ++ + kernelLeafCompletionRows problem) := by + set hi : PolynomialRow F := Array.ofFn (fun k : Fin row.size ↦ + rowGet row k.val - + truncateX (leafDegreeCap problem) (rowGet row k.val)) + with hhidef + have hhisize : hi.size = row.size := by + rw [hhidef] + simp + have hhiget : ∀ k, rowGet hi k = + rowGet row k - truncateX (leafDegreeCap problem) (rowGet row k) := by + intro k + rcases Nat.lt_or_ge k row.size with hk | hk + · rw [rowGet, array_getD_of_lt' _ _ (by omega)] + simp only [hhidef, Array.getElem_ofFn] + · rw [rowGet, array_getD_of_le' _ _ (by omega), rowGet, + array_getD_of_le' _ _ hk, truncateX_zero_eq_zero, sub_zero] + have hhilow : ∀ k a, a < leafDegreeCap problem → + CPolynomial.coeff (rowGet hi k) a = 0 := by + intro k a ha + rw [hhiget k, CPolynomial.coeff_sub, truncateX_coeff, if_pos ha, sub_self] + have hhiapprox : RowApproximates mulCtx problem hi := by + refine pm_rowApproximates_of_entry_dvd mulCtx problem fun k ↦ ?_ + rw [← truncateX_eq_zero_iff_X_pow_dvd] + exact pm_truncateX_eq_zero_of_coeff _ fun t ht ↦ hhilow k t ht + have hremget : ∀ k, rowGet (rowSub row hi) k = + truncateX (leafDegreeCap problem) (rowGet row k) := by + intro k + rw [rowGet_rowSub, hhiget k, sub_sub_cancel] + have hremsize : (rowSub row hi).size = problem.matrix.size := by + rw [rowSub_size, hhisize, Nat.max_self, hsize] + have hremdeg : ∀ k, k < (rowSub row hi).size → ∀ a, + leafDegreeCap problem ≤ a → + CPolynomial.coeff (rowGet (rowSub row hi) k) a = 0 := by + intro k _hk a ha + rw [hremget k, truncateX_coeff, if_neg (by omega)] + have hremapprox : RowApproximates mulCtx problem (rowSub row hi) := + rowApproximates_rowSub mulCtx problem happrox hhiapprox + have hsplit : rowAdd (rowSub row hi) hi = row := rowSub_add_cancel hhisize + rw [← hsplit] + exact rowAdd_mem_rowSpan + (pm_lowRow_mem_rowSpan_union mulCtx problem hpos hwf hremapprox hremsize + hremdeg) + (pm_highRow_mem_rowSpan_union problem hpos (hhisize.trans hsize) hhilow) + +/-- The incremental kernel-leaf reduction keeps a uniform input row width. -/ +private theorem pm_reduceIncremental_sizes {n : Nat} + {rows : PolynomialMatrix F} (shift : Array Nat) + (hsizes : ∀ r ∈ MatrixRows rows, r.size = n) : + ∀ r ∈ MatrixRows (reduceKernelLeafRowsIncremental rows shift), + r.size = n := by + intro r hr + refine reduceKernelLeafRowsIncremental_invariant (fun row ↦ row.size = n) + ?_ shift hsizes hr + intro target reducer shift' ht hrd + rw [cancelKernelLeafLeadingTerm] + split + · split + · split + · exact ht + · rw [rowSub_size, rowScaleCoeffX_size] + omega + · exact ht + · exact ht + +/-- **Generation completeness of the kernel-leaf basis.** Every nonzero +solution row of an X-adic problem lies in the row module generated by the +kernel-leaf basis. -/ +theorem kernelLeafBasis_rowSpan_complete [DecidableEq F] + (mulCtx : CPolynomial.MulContext F) (problem : XAdicProblem F) + (shift : Array Nat) (hpos : 0 < problem.matrix.size) + (hwf : WellFormed problem.matrix) {row : PolynomialRow F} + (happrox : RowApproximates mulCtx problem row) + (hsize : row.size = problem.matrix.size) + (_hnz : ¬ RowIsZero row) : + row ∈ RowSpan (kernelLeafBasis problem shift) := by + have hrow_union := pm_solution_mem_rowSpan_union mulCtx problem hpos hwf + happrox hsize + set union := (homogeneousKernelBasisRows (coefficientMatrixRows problem) + (problem.matrix.size * leafDegreeCap problem)).map + (vectorToPolynomialRow (leafDegreeCap problem) problem.matrix.size) ++ + kernelLeafCompletionRows problem with hunion + set reduced := reduceKernelLeafRowsIncremental union shift with hreduced + have hbasis_eq : kernelLeafBasis problem shift = + reduced ++ missingCompletionRows problem shift reduced := by + rw [hreduced, hunion] + simp only [kernelLeafBasis, completeMissingPivotRows] + have hunionsizes : ∀ r ∈ MatrixRows union, r.size = problem.matrix.size := by + rw [hunion] + exact pm_kernelLeafUnion_sizes problem + have hredsizes : ∀ r ∈ MatrixRows reduced, r.size = problem.matrix.size := by + rw [hreduced] + exact pm_reduceIncremental_sizes shift hunionsizes + have hwit_mem : monomialUnitRow (F := F) problem.matrix.size 0 + (leafDegreeCap problem) ∈ MatrixRows union := by + rw [hunion, MatrixRows, Array.toList_append] + refine List.mem_append.mpr (Or.inr ?_) + simp only [kernelLeafCompletionRows] + exact List.mem_map.mpr ⟨0, List.mem_range.mpr hpos, rfl⟩ + have hwit_size : (monomialUnitRow (F := F) problem.matrix.size 0 + (leafDegreeCap problem)).size = problem.matrix.size := by + simp [monomialUnitRow] + have hwit_nz : ¬ RowIsZero (monomialUnitRow (F := F) problem.matrix.size 0 + (leafDegreeCap problem)) := by + intro hz + have hget : rowGet (monomialUnitRow (F := F) problem.matrix.size 0 + (leafDegreeCap problem)) 0 = coeffXPower 1 (leafDegreeCap problem) := by + rw [pm_rowGet_monomialUnitRow, if_pos ⟨hpos, rfl⟩] + have hmem0 : (monomialUnitRow (F := F) problem.matrix.size 0 + (leafDegreeCap problem))[0]'(by omega) ∈ + (monomialUnitRow (F := F) problem.matrix.size 0 + (leafDegreeCap problem)).toList := + Array.getElem_mem_toList (by omega) + have hzero := hz _ hmem0 + rw [rowGet, array_getD_of_lt' _ _ (by omega), hzero] at hget + have hone : CPolynomial.coeff (coeffXPower (1 : F) (leafDegreeCap problem)) + (leafDegreeCap problem) = 1 := by + rw [CPolynomial.coeff_toPoly, coeffXPower_toPoly, Polynomial.coeff_C_mul, + Polynomial.coeff_X_pow, if_pos rfl, mul_one] + rw [← hget, CPolynomial.coeff_zero] at hone + exact zero_ne_one hone + have hwit_span : monomialUnitRow (F := F) problem.matrix.size 0 + (leafDegreeCap problem) ∈ RowSpan reduced := by + rw [hreduced] + exact reduceKernelLeafRowsIncremental_rowSpan_superset hunionsizes + hwit_mem hwit_nz + have hredpos : 0 < reduced.size := by + rcases Nat.eq_zero_or_pos reduced.size with h0 | hp + · exfalso + rw [Array.eq_empty_of_size_eq_zero h0] at hwit_span + have hweq := eq_empty_of_mem_rowSpan_empty hwit_span + rw [hweq] at hwit_size + simp at hwit_size + omega + · exact hp + have hunionpos : 0 < union.size := by + rw [hunion, Array.size_append, pm_completionRows_size] + omega + have hunionwidth : MatrixWidth union = problem.matrix.size := + matrixWidth_eq_of_first_row hunionpos hunionsizes + have hredwidth : MatrixWidth reduced = problem.matrix.size := + matrixWidth_eq_of_first_row hredpos hredsizes + have hsub1 : RowSpan union ⊆ RowSpan reduced := by + refine rowSpan_subset_of_rows_mem (wellFormed_of_sizes hredsizes) + (hunionwidth.trans hredwidth.symm) ?_ + intro r hr + by_cases hz : RowIsZero r + · have hreq : r = zeroRow problem.matrix.size := by + have hzr := rowIsZero_eq_zeroRow hz + rwa [hunionsizes r hr] at hzr + rw [hreq] + have hzero := zeroRow_mem_rowSpan (wellFormed_of_sizes hredsizes) + rwa [hredwidth] at hzero + · rw [hreduced] + exact reduceKernelLeafRowsIncremental_rowSpan_superset hunionsizes hr hz + have hbasissizes : ∀ r ∈ MatrixRows (kernelLeafBasis problem shift), + r.size = problem.matrix.size := + fun r hr ↦ (kernelLeafBasis_rows mulCtx problem shift r hr).2 + have hbasispos : 0 < (kernelLeafBasis problem shift).size := by + rw [hbasis_eq, Array.size_append] + omega + have hbasiswidth : MatrixWidth (kernelLeafBasis problem shift) = + problem.matrix.size := + matrixWidth_eq_of_first_row hbasispos hbasissizes + have hsub2 : RowSpan reduced ⊆ RowSpan (kernelLeafBasis problem shift) := by + refine rowSpan_subset_of_rows_mem (wellFormed_of_sizes hbasissizes) + (hredwidth.trans hbasiswidth.symm) ?_ + intro r hr + refine matrix_row_mem_rowSpan (wellFormed_of_sizes hbasissizes) ?_ + rw [hbasis_eq, MatrixRows, Array.toList_append] + exact List.mem_append.mpr (Or.inl hr) + exact hsub2 (hsub1 hrow_union) + +end Approximant + +end PolynomialMatrix + +end CompPoly diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/KernelLeafScalar.lean b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/KernelLeafScalar.lean new file mode 100644 index 00000000..27bd7370 --- /dev/null +++ b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/KernelLeafScalar.lean @@ -0,0 +1,1340 @@ +/- +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.LinearAlgebra.PolynomialMatrix.Approximant.PMBasis.KernelLeaf +import Mathlib.Algebra.BigOperators.Group.Finset.Basic +import Mathlib.Algebra.BigOperators.Group.Finset.Piecewise +import Mathlib.Algebra.Order.Field.Basic + +/-! +# Scalar Kernel Leaf Correctness + +Soundness and completeness of the row-array scalar RREF kernel used by the +PM-basis leaf: every emitted vector is orthogonal to the input rows, and +every orthogonal vector is an `F`-linear combination of the emitted basis. +-/ + +namespace CompPoly + +namespace PolynomialMatrix + +namespace Approximant + +variable {F : Type*} [Field F] [BEq F] [LawfulBEq F] + +/-! ## Scalar kernel leaf soundness + +Soundness of the row-array scalar RREF kernel used by the PM-basis leaf: +every vector produced by `homogeneousKernelBasisRows rows cols` has size +`cols` and is orthogonal (over the first `cols` coordinates) to every input +row. -/ + +/-! ## Generic array access lemmas -/ + +section ArrayAccess + +variable {α : Type*} + +/-- `getD` at an out-of-bounds index returns the default. -/ +theorem array_getD_of_le' (xs : Array α) (d : α) {i : Nat} + (h : xs.size ≤ i) : xs.getD i d = d := by + rw [Array.getD_eq_getD_getElem?, Array.getElem?_eq_none h] + rfl + +/-- `getD` at an in-bounds index returns the indexed element. -/ +theorem array_getD_of_lt' (xs : Array α) (d : α) {i : Nat} + (h : i < xs.size) : xs.getD i d = xs[i] := by + rw [Array.getD_eq_getD_getElem?, Array.getElem?_eq_getElem h] + rfl + +/-- `getD` after `setIfInBounds` in coordinates. -/ +theorem array_getD_setIfInBounds (xs : Array α) (j : Nat) (a : α) + (i : Nat) (d : α) : + (xs.setIfInBounds j a).getD i d = + if j = i ∧ j < xs.size then a else xs.getD i d := by + rw [Array.getD_eq_getD_getElem?, Array.getElem?_setIfInBounds] + by_cases hji : j = i + · subst hji + by_cases hj : j < xs.size + · simp [hj] + · simp [hj, Array.getD_eq_getD_getElem?] + · simp [hji, Array.getD_eq_getD_getElem?] + +private theorem array_getD_push (xs : Array α) (a : α) (i : Nat) (d : α) : + (xs.push a).getD i d = + if i = xs.size then a else xs.getD i d := by + rw [Array.getD_eq_getD_getElem?, Array.getElem?_push] + by_cases hi : i = xs.size + · simp [hi] + · simp [hi, Array.getD_eq_getD_getElem?] + +end ArrayAccess + +/-! ## Coordinate access helpers -/ + +section Coordinates + +variable {F : Type*} + +private theorem array_getD_eq_zero_of_le [Zero F] {r : Array F} {k : Nat} + (h : r.size ≤ k) : r.getD k 0 = 0 := + array_getD_of_le' r 0 h + +private theorem array_getD_eq_getElem [Zero F] {r : Array F} {k : Nat} + (h : k < r.size) : r.getD k 0 = r[k] := + array_getD_of_lt' r 0 h + +/-- Coordinatewise description of `addScaledScalarRow`. -/ +theorem addScaledScalarRow_getD [Field F] (target source : Array F) + (factor : F) (k : Nat) : + (addScaledScalarRow target source factor).getD k 0 = + target.getD k 0 + factor * source.getD k 0 := by + unfold addScaledScalarRow + by_cases hk : k < max target.size source.size + · rw [array_getD_eq_getElem (by simpa using hk)] + simp [List.getElem_toArray] + · rw [array_getD_eq_zero_of_le (by simpa using Nat.le_of_not_lt hk)] + have htarget : target.getD k 0 = 0 := + array_getD_eq_zero_of_le (by omega) + have hsource : source.getD k 0 = 0 := + array_getD_eq_zero_of_le (by omega) + rw [htarget, hsource, mul_zero, add_zero] + +/-- Coordinatewise description of `normalizeScalarRow` at a nonzero pivot. -/ +theorem normalizeScalarRow_getD [Field F] [BEq F] [LawfulBEq F] + (row : Array F) (pivotCol : Nat) (hpivot : row.getD pivotCol 0 ≠ 0) + (k : Nat) : + (normalizeScalarRow row pivotCol).getD k 0 = + row.getD k 0 / row.getD pivotCol 0 := by + unfold normalizeScalarRow + rw [if_neg (by simpa using hpivot)] + by_cases hk : k < row.size + · rw [array_getD_eq_getElem (by simpa using hk), array_getD_eq_getElem hk] + simp + · rw [array_getD_eq_zero_of_le (by simpa using Nat.le_of_not_lt hk), + array_getD_eq_zero_of_le (Nat.le_of_not_lt hk), zero_div] + +end Coordinates + +/-! ## Scalar dot products and orthogonality -/ + +section Dot + +variable {F : Type*} + +/-- Dot product of the first `cols` coordinates of two scalar rows, with +zero defaults beyond the stored lengths. -/ +def scalarDot [Field F] (cols : Nat) (r v : Array F) : F := + ∑ k ∈ Finset.range cols, r.getD k 0 * v.getD k 0 + +/-- The empty row is orthogonal to everything. -/ +theorem scalarDot_empty [Field F] (cols : Nat) (v : Array F) : + scalarDot cols (#[] : Array F) v = 0 := by + unfold scalarDot + refine Finset.sum_eq_zero fun k _ ↦ ?_ + rw [array_getD_eq_zero_of_le (by simp), zero_mul] + +/-- `scalarDot` is additive along `addScaledScalarRow`. -/ +theorem scalarDot_addScaledScalarRow [Field F] (cols : Nat) + (target source v : Array F) (factor : F) : + scalarDot cols (addScaledScalarRow target source factor) v = + scalarDot cols target v + factor * scalarDot cols source v := by + unfold scalarDot + rw [Finset.mul_sum, ← Finset.sum_add_distrib] + refine Finset.sum_congr rfl fun k _ ↦ ?_ + rw [addScaledScalarRow_getD] + ring + +/-- A row recovers from its normalization by rescaling with the pivot. -/ +theorem scalarDot_eq_pivot_mul_normalize [Field F] [BEq F] [LawfulBEq F] + (cols : Nat) (row v : Array F) (pivotCol : Nat) + (hpivot : row.getD pivotCol 0 ≠ 0) : + scalarDot cols row v = + row.getD pivotCol 0 * scalarDot cols (normalizeScalarRow row pivotCol) v := by + unfold scalarDot + rw [Finset.mul_sum] + refine Finset.sum_congr rfl fun k _ ↦ ?_ + rw [normalizeScalarRow_getD row pivotCol hpivot] + field_simp + +/-- Orthogonality of `v` to every row of a row-array matrix, expressed via +total `getD` access so that out-of-range indices are harmless. -/ +def OrthRows [Field F] (cols : Nat) (rows : Array (Array F)) (v : Array F) : + Prop := + ∀ i, scalarDot cols (rows.getD i #[]) v = 0 + +end Dot + +/-! ## Elementary-step characterizations -/ + +section Steps + +variable {F : Type*} + +/-- Swapping rows preserves the row count. -/ +theorem swapScalarRows_size (rows : Array (Array F)) (a b : Nat) : + (swapScalarRows rows a b).size = rows.size := by + simp [swapScalarRows, Array.size_setIfInBounds] + +private theorem swapScalarRows_getD (rows : Array (Array F)) {a b : Nat} + (ha : a < rows.size) (hb : b < rows.size) (i : Nat) : + (swapScalarRows rows a b).getD i #[] = + if i = b then rows.getD a #[] + else if i = a then rows.getD b #[] + else rows.getD i #[] := by + unfold swapScalarRows + rw [array_getD_setIfInBounds, Array.size_setIfInBounds, + array_getD_setIfInBounds] + by_cases hib : i = b + · subst hib + simp [hb] + · by_cases hia : i = a + · subst hia + have hba : ¬b = i := fun h ↦ hib h.symm + simp [hba, hib, ha] + · have hba : ¬b = i := fun h ↦ hib h.symm + have haa : ¬a = i := fun h ↦ hia h.symm + simp [hba, haa, hib, hia] + +private theorem findScalarPivotRow_eq_some [Field F] [BEq F] [LawfulBEq F] + {rows : Array (Array F)} {startRow col p : Nat} + (h : findScalarPivotRow rows startRow col = some p) : + startRow ≤ p ∧ p < rows.size ∧ (rows.getD p #[]).getD col 0 ≠ 0 := by + unfold findScalarPivotRow at h + have hmem := List.mem_of_find?_eq_some h + have hbounds := List.mem_range'_1.mp hmem + have hpred := List.find?_some h + refine ⟨hbounds.1, by omega, ?_⟩ + exact bne_iff_ne.mp hpred + +private theorem findScalarPivotRow_eq_none [Field F] [BEq F] [LawfulBEq F] + {rows : Array (Array F)} {startRow col : Nat} + (h : findScalarPivotRow rows startRow col = none) : + ∀ i, startRow ≤ i → (rows.getD i #[]).getD col 0 = 0 := by + intro i hi + by_cases hsize : i < rows.size + · have hmem : i ∈ List.range' startRow (rows.size - startRow) := + List.mem_range'_1.mpr ⟨hi, by omega⟩ + have hfail := List.find?_eq_none.mp h i hmem + simpa using hfail + · rw [array_getD_of_le' rows #[] (Nat.le_of_not_lt hsize)] + exact array_getD_of_le' _ _ (by simp) + +end Steps + +/-! ## Backward span: row operations are invertible -/ + +section Backward + +variable {F : Type*} + +/-- The elimination step performed for each row inside +`normalizeAndEliminateScalarRows`, lifted to a top-level definition so that +fold lemmas can be stated about it. -/ +private def elimStep [Field F] [BEq F] (pivotRow : Nat) + (pivotVector : Array F) (pivotCol : Nat) (rows : Array (Array F)) + (row : Nat) : Array (Array F) := + if row == pivotRow then + rows + else if -((rows.getD row #[]).getD pivotCol 0) == 0 then + rows + else + rows.setIfInBounds row + (addScaledScalarRow (rows.getD row #[]) pivotVector + (-((rows.getD row #[]).getD pivotCol 0))) + +private theorem normalizeAndEliminateScalarRows_eq_foldl [Field F] [BEq F] + [LawfulBEq F] (rows : Array (Array F)) (pivotRow pivotCol : Nat) + (h : (rows.getD pivotRow #[]).getD pivotCol 0 ≠ 0) : + normalizeAndEliminateScalarRows rows pivotRow pivotCol = + (List.range + (rows.setIfInBounds pivotRow + (normalizeScalarRow (rows.getD pivotRow #[]) pivotCol)).size).foldl + (elimStep pivotRow + (normalizeScalarRow (rows.getD pivotRow #[]) pivotCol) pivotCol) + (rows.setIfInBounds pivotRow + (normalizeScalarRow (rows.getD pivotRow #[]) pivotCol)) := by + unfold normalizeAndEliminateScalarRows elimStep + rw [if_neg (by simpa using h)] + +private theorem elimStep_size [Field F] [BEq F] (pivotRow : Nat) + (pivotVector : Array F) (pivotCol : Nat) (rows : Array (Array F)) + (row : Nat) : + (elimStep pivotRow pivotVector pivotCol rows row).size = rows.size := by + unfold elimStep + by_cases hrp : (row == pivotRow) = true + · rw [if_pos hrp] + · rw [if_neg hrp] + by_cases hfac : (-((rows.getD row #[]).getD pivotCol 0) == 0) = true + · rw [if_pos hfac] + · rw [if_neg hfac] + exact Array.size_setIfInBounds + +private theorem foldl_elimStep_size [Field F] [BEq F] (pivotRow : Nat) + (pivotVector : Array F) (pivotCol : Nat) : + ∀ (l : List Nat) (acc : Array (Array F)), + (l.foldl (elimStep pivotRow pivotVector pivotCol) acc).size = acc.size := by + intro l + induction l with + | nil => intro acc; rfl + | cons a l ih => + intro acc + rw [List.foldl_cons, ih, elimStep_size] + +private theorem elimStep_getD_pivotRow [Field F] [BEq F] (pivotRow : Nat) + (pivotVector : Array F) (pivotCol : Nat) (rows : Array (Array F)) + (row : Nat) : + (elimStep pivotRow pivotVector pivotCol rows row).getD pivotRow #[] = + rows.getD pivotRow #[] := by + unfold elimStep + by_cases hrp : (row == pivotRow) = true + · rw [if_pos hrp] + · rw [if_neg hrp] + by_cases hfac : (-((rows.getD row #[]).getD pivotCol 0) == 0) = true + · rw [if_pos hfac] + · rw [if_neg hfac] + have hne : ¬(row = pivotRow ∧ row < rows.size) := by + intro hc + exact hrp (beq_iff_eq.mpr hc.1) + rw [array_getD_setIfInBounds, if_neg hne] + +private theorem foldl_elimStep_getD_pivotRow [Field F] [BEq F] + (pivotRow : Nat) (pivotVector : Array F) (pivotCol : Nat) : + ∀ (l : List Nat) (acc : Array (Array F)), + ((l.foldl (elimStep pivotRow pivotVector pivotCol) acc).getD + pivotRow #[]) = acc.getD pivotRow #[] := by + intro l + induction l with + | nil => intro acc; rfl + | cons a l ih => + intro acc + rw [List.foldl_cons, ih, elimStep_getD_pivotRow] + +private theorem orthRows_of_elimStep [Field F] [BEq F] [LawfulBEq F] + {cols pivotRow pivotCol : Nat} {pivotVector v : Array F} + {rows : Array (Array F)} {a : Nat} + (hpv : scalarDot cols pivotVector v = 0) + (h : OrthRows cols (elimStep pivotRow pivotVector pivotCol rows a) v) : + OrthRows cols rows v := by + unfold elimStep at h + by_cases hap : (a == pivotRow) = true + · rwa [if_pos hap] at h + · rw [if_neg hap] at h + by_cases hfac : (-((rows.getD a #[]).getD pivotCol 0) == 0) = true + · rwa [if_pos hfac] at h + · rw [if_neg hfac] at h + intro i + by_cases hia : i = a + · subst hia + have hi := h i + rw [array_getD_setIfInBounds] at hi + by_cases hsize : i < rows.size + · rw [if_pos ⟨rfl, hsize⟩, scalarDot_addScaledScalarRow, hpv, + mul_zero, add_zero] at hi + exact hi + · have hne : ¬(i = i ∧ i < rows.size) := fun hc ↦ hsize hc.2 + rwa [if_neg hne] at hi + · have hi := h i + have hne : ¬(a = i ∧ a < rows.size) := fun hc ↦ hia hc.1.symm + rwa [array_getD_setIfInBounds, if_neg hne] at hi + +private theorem orthRows_of_foldl_elimStep [Field F] [BEq F] [LawfulBEq F] + {cols pivotRow pivotCol : Nat} {pivotVector v : Array F} + (hpv : scalarDot cols pivotVector v = 0) : + ∀ (l : List Nat) (acc : Array (Array F)), + OrthRows cols (l.foldl (elimStep pivotRow pivotVector pivotCol) acc) v → + OrthRows cols acc v := by + intro l + induction l with + | nil => intro acc h; exact h + | cons a l ih => + intro acc h + rw [List.foldl_cons] at h + exact orthRows_of_elimStep hpv (ih _ h) + +private theorem orthRows_of_normalizeAndEliminate [Field F] [BEq F] + [LawfulBEq F] {cols : Nat} {rows : Array (Array F)} + {pivotRow pivotCol : Nat} {v : Array F} (hpr : pivotRow < rows.size) + (h : OrthRows cols (normalizeAndEliminateScalarRows rows pivotRow pivotCol) + v) : + OrthRows cols rows v := by + by_cases hpivot : (rows.getD pivotRow #[]).getD pivotCol 0 = 0 + · unfold normalizeAndEliminateScalarRows at h + rwa [if_pos (by simpa using hpivot)] at h + · rw [normalizeAndEliminateScalarRows_eq_foldl rows pivotRow pivotCol hpivot] + at h + have hpv_entry : + (rows.setIfInBounds pivotRow + (normalizeScalarRow (rows.getD pivotRow #[]) pivotCol)).getD + pivotRow #[] = + normalizeScalarRow (rows.getD pivotRow #[]) pivotCol := by + rw [array_getD_setIfInBounds, if_pos ⟨rfl, hpr⟩] + have hpv : scalarDot cols + (normalizeScalarRow (rows.getD pivotRow #[]) pivotCol) v = 0 := by + have hp := h pivotRow + rwa [foldl_elimStep_getD_pivotRow, hpv_entry] at hp + have h₁ := orthRows_of_foldl_elimStep hpv _ _ h + intro i + by_cases hip : i = pivotRow + · subst hip + rw [scalarDot_eq_pivot_mul_normalize cols _ v pivotCol hpivot, hpv, + mul_zero] + · have hi := h₁ i + have hne : ¬(pivotRow = i ∧ pivotRow < rows.size) := + fun hc ↦ hip hc.1.symm + rwa [array_getD_setIfInBounds, if_neg hne] at hi + +private theorem orthRows_of_scalarRrefRowsLoop [Field F] [BEq F] [LawfulBEq F] + {cols : Nat} {v : Array F} : + ∀ (fuel col row : Nat) (rows : Array (Array F)) (pivots : Array Nat), + OrthRows cols + (scalarRrefRowsLoop cols fuel col row rows pivots).rows v → + OrthRows cols rows v := by + intro fuel + induction fuel with + | zero => + intro col row rows pivots h + exact h + | succ fuel ih => + intro col row rows pivots h + simp only [scalarRrefRowsLoop] at h + split at h + · exact h + · rename_i hguard + have hrow : row < rows.size := by + simp only [ge_iff_le, Bool.or_eq_true, decide_eq_true_eq, + not_or, not_le] at hguard + exact hguard.2 + cases hfind : findScalarPivotRow rows row col with + | none => + rw [hfind] at h + exact ih _ _ _ _ h + | some p => + rw [hfind] at h + obtain ⟨hp_le, hp_lt, _⟩ := findScalarPivotRow_eq_some hfind + have hreduced := ih _ _ _ _ h + have hswapped : OrthRows cols (swapScalarRows rows p row) v := + orthRows_of_normalizeAndEliminate + (by rwa [swapScalarRows_size]) hreduced + intro i + by_cases hib : i = row + · subst hib + have hs := hswapped p + rw [swapScalarRows_getD rows hp_lt hrow] at hs + by_cases hpr : p = i + · rwa [if_pos hpr, hpr] at hs + · rwa [if_neg hpr, if_pos rfl] at hs + · by_cases hia : i = p + · subst hia + have hs := hswapped row + rwa [swapScalarRows_getD rows hp_lt hrow, if_pos rfl] at hs + · have hs := hswapped i + rwa [swapScalarRows_getD rows hp_lt hrow, if_neg hib, + if_neg hia] at hs + +/-- Orthogonality to all rows of the final RREF matrix transfers back to all +rows of the original matrix: every row operation is invertible. -/ +theorem orthRows_of_scalarRrefRows [Field F] [BEq F] [LawfulBEq F] + {cols : Nat} {rows : Array (Array F)} {v : Array F} + (h : OrthRows cols (scalarRrefRows rows cols).rows v) : + OrthRows cols rows v := + orthRows_of_scalarRrefRowsLoop _ _ _ _ _ h + +end Backward + +/-! ## Forward RREF shape invariants -/ + +section Forward + +variable {F : Type*} + +private theorem elimStep_getD_other [Field F] [BEq F] {pivotRow : Nat} + {pivotVector : Array F} {pivotCol : Nat} {rows : Array (Array F)} + {a i : Nat} (hia : i ≠ a) : + (elimStep pivotRow pivotVector pivotCol rows a).getD i #[] = + rows.getD i #[] := by + unfold elimStep + by_cases hap : (a == pivotRow) = true + · rw [if_pos hap] + · rw [if_neg hap] + by_cases hfac : (-((rows.getD a #[]).getD pivotCol 0) == 0) = true + · rw [if_pos hfac] + · rw [if_neg hfac] + have hne : ¬(a = i ∧ a < rows.size) := fun hc ↦ hia hc.1.symm + rw [array_getD_setIfInBounds, if_neg hne] + +private theorem elimStep_getD_self_entry [Field F] [BEq F] [LawfulBEq F] + (pivotRow : Nat) (pivotVector : Array F) (pivotCol : Nat) + (rows : Array (Array F)) (a k : Nat) : + ((elimStep pivotRow pivotVector pivotCol rows a).getD a #[]).getD k 0 = + if a = pivotRow then + (rows.getD a #[]).getD k 0 + else + (rows.getD a #[]).getD k 0 - + (rows.getD a #[]).getD pivotCol 0 * pivotVector.getD k 0 := by + unfold elimStep + by_cases hap : (a == pivotRow) = true + · rw [if_pos hap, if_pos (beq_iff_eq.mp hap)] + · have hap' : ¬a = pivotRow := by simpa using hap + rw [if_neg hap, if_neg hap'] + by_cases hfac : (-((rows.getD a #[]).getD pivotCol 0) == 0) = true + · have hzero : (rows.getD a #[]).getD pivotCol 0 = 0 := + neg_eq_zero.mp (beq_iff_eq.mp hfac) + rw [if_pos hfac, hzero, zero_mul, sub_zero] + · rw [if_neg hfac] + by_cases hsize : a < rows.size + · rw [array_getD_setIfInBounds, if_pos ⟨rfl, hsize⟩, + addScaledScalarRow_getD, neg_mul, ← sub_eq_add_neg] + · have hne : ¬(a = a ∧ a < rows.size) := fun hc ↦ hsize hc.2 + rw [array_getD_setIfInBounds, if_neg hne, + array_getD_of_le' rows #[] (Nat.le_of_not_lt hsize)] + have h0 : ∀ j, (#[] : Array F).getD j 0 = 0 := + fun j ↦ array_getD_of_le' _ _ (by simp) + rw [h0, h0, zero_mul, sub_zero] + +private theorem foldl_elimStep_getD_entry [Field F] [BEq F] [LawfulBEq F] + (pivotRow : Nat) (pivotVector : Array F) (pivotCol : Nat) : + ∀ (l : List Nat), l.Nodup → + ∀ (acc : Array (Array F)) (i k : Nat), + (((l.foldl (elimStep pivotRow pivotVector pivotCol) acc).getD + i #[]).getD k 0) = + if i ∈ l ∧ i ≠ pivotRow then + (acc.getD i #[]).getD k 0 - + (acc.getD i #[]).getD pivotCol 0 * pivotVector.getD k 0 + else + (acc.getD i #[]).getD k 0 := by + intro l + induction l with + | nil => + intro _ acc i k + have hne : ¬(i ∈ ([] : List Nat) ∧ i ≠ pivotRow) := by simp + rw [List.foldl_nil, if_neg hne] + | cons a l ih => + intro hnodup acc i k + obtain ⟨ha_notin, hl⟩ := List.nodup_cons.mp hnodup + rw [List.foldl_cons, ih hl] + by_cases hia : i = a + · subst hia + have hnot : ¬(i ∈ l ∧ i ≠ pivotRow) := fun hc ↦ ha_notin hc.1 + rw [if_neg hnot, elimStep_getD_self_entry] + by_cases hip : i = pivotRow + · have hnot2 : ¬(i ∈ i :: l ∧ i ≠ pivotRow) := fun hc ↦ hc.2 hip + rw [if_pos hip, if_neg hnot2] + · rw [if_neg hip, if_pos ⟨by simp, hip⟩] + · rw [elimStep_getD_other hia] + by_cases hil : i ∈ l ∧ i ≠ pivotRow + · rw [if_pos hil, if_pos ⟨List.mem_cons_of_mem a hil.1, hil.2⟩] + · have hnot : ¬(i ∈ a :: l ∧ i ≠ pivotRow) := by + intro hc + rcases List.mem_cons.mp hc.1 with h | h + · exact hia h + · exact hil ⟨h, hc.2⟩ + rw [if_neg hil, if_neg hnot] + +private theorem normalizeAndEliminateScalarRows_getD_entry [Field F] [BEq F] + [LawfulBEq F] (rows : Array (Array F)) (pivotRow pivotCol : Nat) + (hpr : pivotRow < rows.size) + (hpivot : (rows.getD pivotRow #[]).getD pivotCol 0 ≠ 0) (i k : Nat) : + ((normalizeAndEliminateScalarRows rows pivotRow pivotCol).getD + i #[]).getD k 0 = + if i = pivotRow then + (rows.getD pivotRow #[]).getD k 0 / + (rows.getD pivotRow #[]).getD pivotCol 0 + else + (rows.getD i #[]).getD k 0 - + (rows.getD i #[]).getD pivotCol 0 * + ((rows.getD pivotRow #[]).getD k 0 / + (rows.getD pivotRow #[]).getD pivotCol 0) := by + rw [normalizeAndEliminateScalarRows_eq_foldl rows pivotRow pivotCol hpivot, + foldl_elimStep_getD_entry pivotRow _ pivotCol _ List.nodup_range] + have hrows₁_pr : + (rows.setIfInBounds pivotRow + (normalizeScalarRow (rows.getD pivotRow #[]) pivotCol)).getD + pivotRow #[] = + normalizeScalarRow (rows.getD pivotRow #[]) pivotCol := by + rw [array_getD_setIfInBounds, if_pos ⟨rfl, hpr⟩] + have hpv_getD := normalizeScalarRow_getD (rows.getD pivotRow #[]) pivotCol + hpivot + by_cases hip : i = pivotRow + · subst hip + have hnot : ¬(i ∈ List.range + (rows.setIfInBounds i + (normalizeScalarRow (rows.getD i #[]) pivotCol)).size ∧ i ≠ i) := + fun hc ↦ hc.2 rfl + rw [if_neg hnot, if_pos rfl, hrows₁_pr, hpv_getD] + · rw [if_neg hip] + have hrows₁_i : + (rows.setIfInBounds pivotRow + (normalizeScalarRow (rows.getD pivotRow #[]) pivotCol)).getD + i #[] = rows.getD i #[] := by + have hne : ¬(pivotRow = i ∧ pivotRow < rows.size) := + fun hc ↦ hip hc.1.symm + rw [array_getD_setIfInBounds, if_neg hne] + by_cases hisize : i < rows.size + · have hmem : i ∈ List.range + (rows.setIfInBounds pivotRow + (normalizeScalarRow (rows.getD pivotRow #[]) pivotCol)).size := by + rw [List.mem_range, Array.size_setIfInBounds] + exact hisize + rw [if_pos ⟨hmem, hip⟩, hrows₁_i, hpv_getD] + · have hnot : ¬(i ∈ List.range + (rows.setIfInBounds pivotRow + (normalizeScalarRow (rows.getD pivotRow #[]) pivotCol)).size ∧ + i ≠ pivotRow) := by + intro hc + have := List.mem_range.mp hc.1 + rw [Array.size_setIfInBounds] at this + exact hisize this + rw [if_neg hnot, hrows₁_i, + array_getD_of_le' rows #[] (Nat.le_of_not_lt hisize)] + have h0 : ∀ j, (#[] : Array F).getD j 0 = 0 := + fun j ↦ array_getD_of_le' _ _ (by simp) + rw [h0, h0, zero_mul, sub_zero] + +/-- Shape contract satisfied by the result of `scalarRrefRows`: recorded +pivot columns are strictly increasing and below `cols`, each pivot column is +a unit column with its one in the corresponding pivot row, and every row at +or beyond the pivot count vanishes on all columns below `cols`. -/ +structure ScalarRrefSpec [Field F] (cols : Nat) + (R : ScalarRrefResult (F := F)) : Prop where + mono : ∀ s t, s < t → t < R.pivots.size → + R.pivots.getD s 0 < R.pivots.getD t 0 + pivots_lt : ∀ t, t < R.pivots.size → R.pivots.getD t 0 < cols + unit : ∀ t, t < R.pivots.size → ∀ i, + (R.rows.getD i #[]).getD (R.pivots.getD t 0) 0 = if i = t then 1 else 0 + tail_zero : ∀ i, R.pivots.size ≤ i → ∀ k, k < cols → + (R.rows.getD i #[]).getD k 0 = 0 + +private theorem scalarRrefRowsLoop_spec [Field F] [BEq F] [LawfulBEq F] + {cols : Nat} : + ∀ (fuel col row : Nat) (rows : Array (Array F)) (pivots : Array Nat), + cols < col + fuel → + row = pivots.size → + (∀ s t, s < t → t < pivots.size → + pivots.getD s 0 < pivots.getD t 0) → + (∀ t, t < pivots.size → pivots.getD t 0 < col) → + (∀ t, t < pivots.size → pivots.getD t 0 < cols) → + (∀ t, t < pivots.size → ∀ i, + (rows.getD i #[]).getD (pivots.getD t 0) 0 = if i = t then 1 else 0) → + (∀ i, row ≤ i → ∀ k, k < col → (rows.getD i #[]).getD k 0 = 0) → + ScalarRrefSpec cols (scalarRrefRowsLoop cols fuel col row rows pivots) := by + intro fuel + induction fuel with + | zero => + intro col row rows pivots hfuel hrow hmono _hltcol hltcols hunit hzero + exact ⟨hmono, hltcols, hunit, fun i hi k hk ↦ + hzero i (le_of_eq_of_le hrow hi) k (Nat.lt_trans hk hfuel)⟩ + | succ fuel ih => + intro col row rows pivots hfuel hrow hmono hltcol hltcols hunit hzero + simp only [scalarRrefRowsLoop] + split + · rename_i hguard + have hguard' : cols ≤ col ∨ rows.size ≤ row := by + simpa [ge_iff_le, Bool.or_eq_true, decide_eq_true_eq] using hguard + refine ⟨hmono, hltcols, hunit, ?_⟩ + intro i hi k hk + have hi' : pivots.size ≤ i := hi + rcases hguard' with hcase | hcase + · exact hzero i (le_of_eq_of_le hrow hi') k (by omega) + · have hsize : rows.size ≤ i := by omega + rw [array_getD_of_le' rows #[] hsize] + exact array_getD_of_le' _ _ (by simp) + · rename_i hguard + have hguard' : col < cols ∧ row < rows.size := by + simpa [ge_iff_le, Bool.or_eq_true, decide_eq_true_eq, not_or, + not_le] using hguard + obtain ⟨hcol, hrowlt⟩ := hguard' + cases hfind : findScalarPivotRow rows row col with + | none => + apply ih (col + 1) row rows pivots (by omega) hrow hmono + (fun t ht ↦ Nat.lt_succ_of_lt (hltcol t ht)) hltcols hunit + intro i hi k hk + by_cases hkcol : k < col + · exact hzero i hi k hkcol + · have hk_eq : k = col := by omega + subst hk_eq + exact findScalarPivotRow_eq_none hfind i hi + | some p => + obtain ⟨hp_le, hp_lt, hp_ne⟩ := findScalarPivotRow_eq_some hfind + show ScalarRrefSpec cols (scalarRrefRowsLoop cols fuel (col + 1) + (row + 1) + (normalizeAndEliminateScalarRows (swapScalarRows rows p row) + row col) + (pivots.push col)) + have hgetD := swapScalarRows_getD rows hp_lt hrowlt + have hsize := swapScalarRows_size rows p row + generalize hS : swapScalarRows rows p row = S at hgetD hsize ⊢ + have hS_zero : ∀ i, row ≤ i → ∀ k, k < col → + (S.getD i #[]).getD k 0 = 0 := by + intro i hi k hk + rw [hgetD i] + by_cases hir : i = row + · rw [if_pos hir] + exact hzero p hp_le k hk + · rw [if_neg hir] + by_cases hip : i = p + · rw [if_pos hip] + exact hzero row (le_refl row) k hk + · rw [if_neg hip] + exact hzero i hi k hk + have hS_unit : ∀ t, t < pivots.size → ∀ i, + (S.getD i #[]).getD (pivots.getD t 0) 0 = + if i = t then 1 else 0 := by + intro t ht i + rw [hgetD i] + by_cases hir : i = row + · rw [if_pos hir, hzero p hp_le _ (hltcol t ht), + if_neg (by omega : ¬i = t)] + · rw [if_neg hir] + by_cases hip : i = p + · rw [if_pos hip, hzero row (le_refl row) _ (hltcol t ht)] + have hit : ¬i = t := by omega + rw [if_neg hit] + · rw [if_neg hip] + exact hunit t ht i + have hS_pivot : (S.getD row #[]).getD col 0 ≠ 0 := by + rw [hgetD row, if_pos rfl] + exact hp_ne + have hE := normalizeAndEliminateScalarRows_getD_entry S row col + (by omega) hS_pivot + apply ih (col + 1) (row + 1) _ (pivots.push col) (by omega) + · rw [Array.size_push] + omega + · intro s t hst ht + rw [Array.size_push] at ht + rw [array_getD_push, array_getD_push] + by_cases hts : t = pivots.size + · rw [if_pos hts, if_neg (by omega : ¬s = pivots.size)] + exact hltcol s (by omega) + · rw [if_neg hts, if_neg (by omega : ¬s = pivots.size)] + exact hmono s t hst (by omega) + · intro t ht + rw [Array.size_push] at ht + rw [array_getD_push] + by_cases hts : t = pivots.size + · rw [if_pos hts] + exact Nat.lt_succ_self col + · rw [if_neg hts] + exact Nat.lt_succ_of_lt (hltcol t (by omega)) + · intro t ht + rw [Array.size_push] at ht + rw [array_getD_push] + by_cases hts : t = pivots.size + · rw [if_pos hts] + exact hcol + · rw [if_neg hts] + exact hltcols t (by omega) + · intro t ht i + rw [Array.size_push] at ht + rw [array_getD_push] + by_cases hts : t = pivots.size + · rw [if_pos hts, hE i col] + by_cases hir : i = row + · rw [if_pos hir, div_self hS_pivot, + if_pos (by omega : i = t)] + · rw [if_neg hir, div_self hS_pivot, mul_one, sub_self, + if_neg (by omega : ¬i = t)] + · rw [if_neg hts] + have ht' : t < pivots.size := by omega + have hSrow_c : (S.getD row #[]).getD (pivots.getD t 0) 0 = 0 := by + rw [hgetD row, if_pos rfl] + exact hzero p hp_le _ (hltcol t ht') + rw [hE i (pivots.getD t 0)] + by_cases hir : i = row + · rw [if_pos hir, hSrow_c, zero_div, + if_neg (by omega : ¬i = t)] + · rw [if_neg hir, hSrow_c, zero_div, mul_zero, sub_zero] + exact hS_unit t ht' i + · intro i hi k hk + rw [hE i k, if_neg (by omega : ¬i = row)] + by_cases hkc : k < col + · rw [hS_zero i (by omega) k hkc, + hS_zero row (le_refl row) k hkc, zero_div, mul_zero, + sub_zero] + · have hk_eq : k = col := by omega + subst hk_eq + rw [div_self hS_pivot, mul_one, sub_self] + +/-- The row-array RREF driver satisfies the RREF shape contract. -/ +theorem scalarRrefRows_spec [Field F] [BEq F] [LawfulBEq F] + (rows : Array (Array F)) (cols : Nat) : + ScalarRrefSpec cols (scalarRrefRows rows cols) := by + unfold scalarRrefRows + apply scalarRrefRowsLoop_spec (cols + 1) 0 0 rows #[] (by omega) rfl + · intro s t _ ht + simp at ht + · intro t ht + simp at ht + · intro t ht + simp at ht + · intro t ht + simp at ht + · intro i _ k hk + omega + +end Forward + +/-! ## Kernel basis vectors -/ + +section Kernel + +variable {F : Type*} + +private theorem containsNat_false_getD_ne {xs : Array Nat} {x : Nat} + (h : DenseMatrix.containsNat xs x = false) : + ∀ t, t < xs.size → xs.getD t 0 ≠ x := by + intro t ht heq + have htrue : DenseMatrix.containsNat xs x = true := by + unfold DenseMatrix.containsNat + rw [Array.any_eq_true] + refine ⟨t, ht, ?_⟩ + rw [← array_getD_of_lt' xs 0 ht, heq] + exact beq_iff_eq.mpr rfl + rw [h] at htrue + exact Bool.false_ne_true htrue + +private theorem freeColumns_mem {cols : Nat} {pivots : Array Nat} {free : Nat} + (h : free ∈ (DenseMatrix.freeColumns cols pivots).toList) : + free < cols ∧ ∀ t, t < pivots.size → pivots.getD t 0 ≠ free := by + unfold DenseMatrix.freeColumns at h + rw [List.toList_toArray, List.mem_filter] at h + refine ⟨List.mem_range.mp h.1, ?_⟩ + have hfalse : DenseMatrix.containsNat pivots free = false := by + simpa using h.2 + exact containsNat_false_getD_ne hfalse + +private theorem pivotRowOfColumn?_some {pivots : Array Nat} {col t : Nat} + (h : DenseMatrix.pivotRowOfColumn? pivots col = some t) : + t < pivots.size ∧ pivots.getD t 0 = col := by + unfold DenseMatrix.pivotRowOfColumn? at h + have hmem := List.mem_of_find?_eq_some h + have hpred := List.find?_some h + exact ⟨List.mem_range.mp hmem, beq_iff_eq.mp hpred⟩ + +private theorem pivotRowOfColumn?_eq_some_of_mono {pivots : Array Nat} + (hmono : ∀ s t, s < t → t < pivots.size → + pivots.getD s 0 < pivots.getD t 0) + {t : Nat} (ht : t < pivots.size) : + DenseMatrix.pivotRowOfColumn? pivots (pivots.getD t 0) = some t := by + cases hfind : DenseMatrix.pivotRowOfColumn? pivots (pivots.getD t 0) with + | none => + exfalso + unfold DenseMatrix.pivotRowOfColumn? at hfind + exact List.find?_eq_none.mp hfind t (List.mem_range.mpr ht) + (beq_iff_eq.mpr rfl) + | some r => + obtain ⟨hr, hreq⟩ := pivotRowOfColumn?_some hfind + have hrt : r = t := by + rcases Nat.lt_trichotomy r t with hlt | heq | hgt + · exact absurd hreq (Nat.ne_of_lt (hmono r t hlt ht)) + · exact heq + · exact absurd hreq.symm (Nat.ne_of_lt (hmono t r hgt hr)) + rw [hrt] + +private theorem pivotRowOfColumn?_eq_none {pivots : Array Nat} {k : Nat} + (h : ∀ t, t < pivots.size → pivots.getD t 0 ≠ k) : + DenseMatrix.pivotRowOfColumn? pivots k = none := by + unfold DenseMatrix.pivotRowOfColumn? + rw [List.find?_eq_none] + intro x hx + simpa using h x (List.mem_range.mp hx) + +private theorem basisVectorForFreeColumnRows_size [Field F] + (rows : Array (Array F)) (pivots : Array Nat) (cols free : Nat) : + (basisVectorForFreeColumnRows rows pivots cols free).size = cols := by + unfold basisVectorForFreeColumnRows + exact Array.size_ofFn + +private theorem basisVectorForFreeColumnRows_getD_free [Field F] + (rows : Array (Array F)) (pivots : Array Nat) (cols : Nat) {free : Nat} + (hfree : free < cols) : + (basisVectorForFreeColumnRows rows pivots cols free).getD free 0 = 1 := by + unfold basisVectorForFreeColumnRows + rw [array_getD_of_lt' _ 0 (by rw [Array.size_ofFn]; exact hfree), + Array.getElem_ofFn] + dsimp only + rw [if_pos (beq_iff_eq.mpr rfl)] + +private theorem basisVectorForFreeColumnRows_getD_none [Field F] + (rows : Array (Array F)) (pivots : Array Nat) (cols free : Nat) {k : Nat} + (hk : k < cols) (hkf : ¬k = free) + (hnone : DenseMatrix.pivotRowOfColumn? pivots k = none) : + (basisVectorForFreeColumnRows rows pivots cols free).getD k 0 = 0 := by + unfold basisVectorForFreeColumnRows + rw [array_getD_of_lt' _ 0 (by rw [Array.size_ofFn]; exact hk), + Array.getElem_ofFn] + dsimp only + rw [if_neg (by simpa using hkf)] + simp only [hnone] + +private theorem basisVectorForFreeColumnRows_getD_some [Field F] + (rows : Array (Array F)) (pivots : Array Nat) (cols free : Nat) {k t : Nat} + (hk : k < cols) (hkf : ¬k = free) + (hsome : DenseMatrix.pivotRowOfColumn? pivots k = some t) : + (basisVectorForFreeColumnRows rows pivots cols free).getD k 0 = + -((rows.getD t #[]).getD free 0) := by + unfold basisVectorForFreeColumnRows + rw [array_getD_of_lt' _ 0 (by rw [Array.size_ofFn]; exact hk), + Array.getElem_ofFn] + dsimp only + rw [if_neg (by simpa using hkf)] + simp only [hsome] + +private theorem scalarDot_basisVector [Field F] [BEq F] [LawfulBEq F] + {cols : Nat} {R : ScalarRrefResult (F := F)} + (hspec : ScalarRrefSpec cols R) {free : Nat} (hfree : free < cols) + (hfreeNot : ∀ t, t < R.pivots.size → R.pivots.getD t 0 ≠ free) + (i : Nat) : + scalarDot cols (R.rows.getD i #[]) + (basisVectorForFreeColumnRows R.rows R.pivots cols free) = 0 := by + by_cases hi : i < R.pivots.size + · have hterm : ∀ k ∈ Finset.range cols, + (R.rows.getD i #[]).getD k 0 * + (basisVectorForFreeColumnRows R.rows R.pivots cols + free).getD k 0 = + (if k = free then (R.rows.getD i #[]).getD free 0 else 0) + + (if k = R.pivots.getD i 0 then + -((R.rows.getD i #[]).getD free 0) else 0) := by + intro k hk + have hk' : k < cols := Finset.mem_range.mp hk + by_cases hkf : k = free + · subst hkf + have hkp : ¬k = R.pivots.getD i 0 := fun h ↦ hfreeNot i hi h.symm + rw [basisVectorForFreeColumnRows_getD_free R.rows R.pivots cols hk', + if_pos rfl, if_neg hkp, mul_one, add_zero] + · rw [if_neg hkf, zero_add] + by_cases hkpiv : ∃ t, t < R.pivots.size ∧ R.pivots.getD t 0 = k + · obtain ⟨t, ht, htk⟩ := hkpiv + have hsome : DenseMatrix.pivotRowOfColumn? R.pivots k = some t := by + rw [← htk] + exact pivotRowOfColumn?_eq_some_of_mono hspec.mono ht + rw [basisVectorForFreeColumnRows_getD_some R.rows R.pivots cols + free hk' hkf hsome] + rw [← htk, hspec.unit t ht i] + by_cases hit : i = t + · subst hit + rw [if_pos rfl, if_pos rfl, one_mul] + · rw [if_neg hit, zero_mul] + have hne : ¬R.pivots.getD t 0 = R.pivots.getD i 0 := by + intro heq + rcases Nat.lt_trichotomy t i with hlt | heq' | hgt + · exact absurd heq (Nat.ne_of_lt (hspec.mono t i hlt hi)) + · exact hit heq'.symm + · exact absurd heq.symm (Nat.ne_of_lt (hspec.mono i t hgt ht)) + rw [if_neg hne] + · have hnone : DenseMatrix.pivotRowOfColumn? R.pivots k = none := + pivotRowOfColumn?_eq_none fun t ht heq ↦ hkpiv ⟨t, ht, heq⟩ + have hne : ¬k = R.pivots.getD i 0 := fun h ↦ hkpiv ⟨i, hi, h.symm⟩ + rw [basisVectorForFreeColumnRows_getD_none R.rows R.pivots cols + free hk' hkf hnone, mul_zero, if_neg hne] + unfold scalarDot + rw [Finset.sum_congr rfl hterm, Finset.sum_add_distrib, + Finset.sum_ite_eq_of_mem' (Finset.range cols) free _ + (Finset.mem_range.mpr hfree), + Finset.sum_ite_eq_of_mem' (Finset.range cols) (R.pivots.getD i 0) _ + (Finset.mem_range.mpr (hspec.pivots_lt i hi)), + add_neg_cancel] + · have hi' : R.pivots.size ≤ i := Nat.le_of_not_lt hi + unfold scalarDot + refine Finset.sum_eq_zero fun k hk ↦ ?_ + rw [hspec.tail_zero i hi' k (Finset.mem_range.mp hk), zero_mul] + +end Kernel + +/-! ## Main soundness theorems -/ + +section Main + +variable {F : Type*} + +/-- Every vector produced by `homogeneousKernelBasisRows rows cols` has +exactly `cols` stored coordinates. -/ +theorem homogeneousKernelBasisRows_size [Field F] [BEq F] [LawfulBEq F] + {rows : Array (Array F)} {cols : Nat} {v : Array F} + (hv : v ∈ (homogeneousKernelBasisRows rows cols).toList) : + v.size = cols := by + simp only [homogeneousKernelBasisRows] at hv + rw [Array.toList_map, List.mem_map] at hv + obtain ⟨free, _, rfl⟩ := hv + exact basisVectorForFreeColumnRows_size _ _ _ _ + +/-- **Soundness of the scalar kernel leaf.** Every vector produced by +`homogeneousKernelBasisRows rows cols` is orthogonal, over the coordinates +`0, …, cols - 1`, to every row of the input matrix. No width hypothesis on +the input rows is needed because the dot product only inspects the first +`cols` coordinates (with zero defaults). -/ +theorem homogeneousKernelBasisRows_dot_eq_zero [Field F] [BEq F] [LawfulBEq F] + {rows : Array (Array F)} {cols : Nat} {v : Array F} + (hv : v ∈ (homogeneousKernelBasisRows rows cols).toList) + {r : Array F} (hr : r ∈ rows.toList) : + ∑ k ∈ Finset.range cols, r.getD k 0 * v.getD k 0 = 0 := by + simp only [homogeneousKernelBasisRows] at hv + rw [Array.toList_map, List.mem_map] at hv + obtain ⟨free, hfree_mem, rfl⟩ := hv + obtain ⟨hfree_lt, hfree_not⟩ := freeColumns_mem hfree_mem + have hspec := scalarRrefRows_spec rows cols + have horthR : OrthRows cols (scalarRrefRows rows cols).rows + (basisVectorForFreeColumnRows (scalarRrefRows rows cols).rows + (scalarRrefRows rows cols).pivots cols free) := + fun i ↦ scalarDot_basisVector hspec hfree_lt hfree_not i + have horth := orthRows_of_scalarRrefRows horthR + obtain ⟨j, hj, hrj⟩ := List.mem_iff_getElem.mp hr + have hj' : j < rows.size := by simpa using hj + have hr_eq : rows.getD j #[] = r := by + rw [array_getD_of_lt' rows #[] hj', ← Array.getElem_toList hj'] + exact hrj + have hdot := horth j + rw [hr_eq] at hdot + exact hdot + +/-! ### Forward span: row operations preserve orthogonality -/ + +private theorem orthRows_elimStep_forward [Field F] [BEq F] + {cols pivotRow pivotCol : Nat} {pivotVector v : Array F} + {rows : Array (Array F)} {a : Nat} + (hpv : scalarDot cols pivotVector v = 0) + (h : OrthRows cols rows v) : + OrthRows cols (elimStep pivotRow pivotVector pivotCol rows a) v := by + unfold elimStep + by_cases hap : (a == pivotRow) = true + · rw [if_pos hap] + exact h + · rw [if_neg hap] + by_cases hfac : (-((rows.getD a #[]).getD pivotCol 0) == 0) = true + · rw [if_pos hfac] + exact h + · rw [if_neg hfac] + intro i + rw [array_getD_setIfInBounds] + by_cases hcond : a = i ∧ a < rows.size + · rw [if_pos hcond, scalarDot_addScaledScalarRow, h a, hpv, mul_zero, + add_zero] + · rw [if_neg hcond] + exact h i + +private theorem orthRows_foldl_elimStep_forward [Field F] [BEq F] + {cols pivotRow pivotCol : Nat} {pivotVector v : Array F} + (hpv : scalarDot cols pivotVector v = 0) : + ∀ (l : List Nat) (acc : Array (Array F)), + OrthRows cols acc v → + OrthRows cols (l.foldl (elimStep pivotRow pivotVector pivotCol) acc) + v := by + intro l + induction l with + | nil => + intro acc h + exact h + | cons a l ih => + intro acc h + rw [List.foldl_cons] + exact ih _ (orthRows_elimStep_forward hpv h) + +private theorem orthRows_normalizeAndEliminate_forward [Field F] [BEq F] + [LawfulBEq F] {cols : Nat} {rows : Array (Array F)} + {pivotRow pivotCol : Nat} {v : Array F} (h : OrthRows cols rows v) : + OrthRows cols (normalizeAndEliminateScalarRows rows pivotRow pivotCol) + v := by + by_cases hpivot : (rows.getD pivotRow #[]).getD pivotCol 0 = 0 + · unfold normalizeAndEliminateScalarRows + rw [if_pos (by simpa using hpivot)] + exact h + · rw [normalizeAndEliminateScalarRows_eq_foldl rows pivotRow pivotCol hpivot] + have hpv : scalarDot cols + (normalizeScalarRow (rows.getD pivotRow #[]) pivotCol) v = 0 := by + have h0 := h pivotRow + rw [scalarDot_eq_pivot_mul_normalize cols _ v pivotCol hpivot] at h0 + exact (mul_eq_zero.mp h0).resolve_left hpivot + refine orthRows_foldl_elimStep_forward hpv _ _ ?_ + intro i + rw [array_getD_setIfInBounds] + by_cases hcond : pivotRow = i ∧ pivotRow < rows.size + · rw [if_pos hcond] + exact hpv + · rw [if_neg hcond] + exact h i + +private theorem orthRows_swapScalarRows_forward [Field F] {cols : Nat} + {rows : Array (Array F)} {a b : Nat} (ha : a < rows.size) + (hb : b < rows.size) {v : Array F} (h : OrthRows cols rows v) : + OrthRows cols (swapScalarRows rows a b) v := by + intro i + rw [swapScalarRows_getD rows ha hb] + by_cases hib : i = b + · rw [if_pos hib] + exact h a + · rw [if_neg hib] + by_cases hia : i = a + · rw [if_pos hia] + exact h b + · rw [if_neg hia] + exact h i + +private theorem orthRows_scalarRrefRowsLoop_forward [Field F] [BEq F] + [LawfulBEq F] {cols : Nat} {v : Array F} : + ∀ (fuel col row : Nat) (rows : Array (Array F)) (pivots : Array Nat), + OrthRows cols rows v → + OrthRows cols (scalarRrefRowsLoop cols fuel col row rows pivots).rows + v := by + intro fuel + induction fuel with + | zero => + intro col row rows pivots h + exact h + | succ fuel ih => + intro col row rows pivots h + simp only [scalarRrefRowsLoop] + split + · exact h + · rename_i hguard + have hrow : row < rows.size := by + simp only [ge_iff_le, Bool.or_eq_true, decide_eq_true_eq, + not_or, not_le] at hguard + exact hguard.2 + cases hfind : findScalarPivotRow rows row col with + | none => exact ih _ _ _ _ h + | some p => + obtain ⟨_, hp_lt, _⟩ := findScalarPivotRow_eq_some hfind + exact ih _ _ _ _ + (orthRows_normalizeAndEliminate_forward + (orthRows_swapScalarRows_forward hp_lt hrow h)) + +private theorem orthRows_scalarRrefRows_forward [Field F] [BEq F] [LawfulBEq F] + {cols : Nat} {rows : Array (Array F)} {v : Array F} + (h : OrthRows cols rows v) : + OrthRows cols (scalarRrefRows rows cols).rows v := + orthRows_scalarRrefRowsLoop_forward _ _ _ _ _ h + +/-! ### Free-column bookkeeping for the completeness theorem -/ + +private theorem freeColumns_getD_mem {cols : Nat} {pivots : Array Nat} + {i : Nat} (hi : i < (DenseMatrix.freeColumns cols pivots).size) : + (DenseMatrix.freeColumns cols pivots).getD i 0 ∈ + (DenseMatrix.freeColumns cols pivots).toList := by + rw [array_getD_of_lt' _ 0 hi, ← Array.getElem_toList (by simpa using hi)] + exact List.getElem_mem _ + +private theorem freeColumns_getD_inj {cols : Nat} {pivots : Array Nat} + {i j : Nat} (hi : i < (DenseMatrix.freeColumns cols pivots).size) + (hj : j < (DenseMatrix.freeColumns cols pivots).size) + (h : (DenseMatrix.freeColumns cols pivots).getD i 0 = + (DenseMatrix.freeColumns cols pivots).getD j 0) : + i = j := by + rw [array_getD_of_lt' _ 0 hi, array_getD_of_lt' _ 0 hj] at h + unfold DenseMatrix.freeColumns at hi hj h + rw [List.getElem_toArray, List.getElem_toArray] at h + have hnodup : ((List.range cols).filter + fun col ↦ !(DenseMatrix.containsNat pivots col)).Nodup := + List.Nodup.filter _ List.nodup_range + exact (List.Nodup.getElem_inj_iff hnodup).mp h + +private theorem containsNat_true_exists {xs : Array Nat} {x : Nat} + (h : DenseMatrix.containsNat xs x = true) : + ∃ t, t < xs.size ∧ xs.getD t 0 = x := by + unfold DenseMatrix.containsNat at h + rw [Array.any_eq_true] at h + obtain ⟨t, ht, hbeq⟩ := h + refine ⟨t, ht, ?_⟩ + rw [array_getD_of_lt' xs 0 ht] + exact beq_iff_eq.mp hbeq + +private theorem freeColumns_or_pivot {cols : Nat} {pivots : Array Nat} + {k : Nat} (hk : k < cols) : + (∃ j, j < (DenseMatrix.freeColumns cols pivots).size ∧ + (DenseMatrix.freeColumns cols pivots).getD j 0 = k) ∨ + ∃ t, t < pivots.size ∧ pivots.getD t 0 = k := by + cases hc : DenseMatrix.containsNat pivots k with + | true => exact Or.inr (containsNat_true_exists hc) + | false => + left + have hmem : k ∈ (DenseMatrix.freeColumns cols pivots).toList := by + unfold DenseMatrix.freeColumns + rw [List.toList_toArray, List.mem_filter] + refine ⟨List.mem_range.mpr hk, ?_⟩ + rw [hc] + rfl + obtain ⟨j, hj, hjk⟩ := List.mem_iff_getElem.mp hmem + have hj' : j < (DenseMatrix.freeColumns cols pivots).size := by + simpa using hj + refine ⟨j, hj', ?_⟩ + rw [array_getD_of_lt' _ 0 hj', ← Array.getElem_toList hj] + exact hjk + +private theorem basisVector_getD_freeColumn [Field F] + (rows : Array (Array F)) (pivots : Array Nat) (cols : Nat) {i j : Nat} + (hi : i < (DenseMatrix.freeColumns cols pivots).size) + (hj : j < (DenseMatrix.freeColumns cols pivots).size) : + (basisVectorForFreeColumnRows rows pivots cols + ((DenseMatrix.freeColumns cols pivots).getD i 0)).getD + ((DenseMatrix.freeColumns cols pivots).getD j 0) 0 = + if j = i then 1 else 0 := by + obtain ⟨hjlt, hjnot⟩ := freeColumns_mem (freeColumns_getD_mem hj) + by_cases hij : j = i + · rw [if_pos hij, hij] + exact basisVectorForFreeColumnRows_getD_free rows pivots cols + (freeColumns_mem (freeColumns_getD_mem hi)).1 + · rw [if_neg hij] + exact basisVectorForFreeColumnRows_getD_none rows pivots cols _ hjlt + (fun hc ↦ hij (freeColumns_getD_inj hj hi hc)) + (pivotRowOfColumn?_eq_none hjnot) + +/-- Coordinatewise completeness of the kernel basis vectors against a fixed +RREF result: any vector orthogonal to the RREF rows agrees, below `cols`, +with the combination of basis vectors whose coefficients are its values at +the free columns. -/ +private theorem kernelBasis_complete_aux [Field F] [BEq F] [LawfulBEq F] + {cols : Nat} {R : ScalarRrefResult (F := F)} + (hspec : ScalarRrefSpec cols R) {v : Array F} + (horthR : OrthRows cols R.rows v) : + ∀ k, k < cols → + v.getD k 0 = + ∑ i ∈ Finset.range (DenseMatrix.freeColumns cols R.pivots).size, + v.getD ((DenseMatrix.freeColumns cols R.pivots).getD i 0) 0 * + (basisVectorForFreeColumnRows R.rows R.pivots cols + ((DenseMatrix.freeColumns cols R.pivots).getD i 0)).getD k 0 := by + have hfree_coord : ∀ j, j < (DenseMatrix.freeColumns cols R.pivots).size → + v.getD ((DenseMatrix.freeColumns cols R.pivots).getD j 0) 0 = + ∑ i ∈ Finset.range (DenseMatrix.freeColumns cols R.pivots).size, + v.getD ((DenseMatrix.freeColumns cols R.pivots).getD i 0) 0 * + (basisVectorForFreeColumnRows R.rows R.pivots cols + ((DenseMatrix.freeColumns cols R.pivots).getD i 0)).getD + ((DenseMatrix.freeColumns cols R.pivots).getD j 0) 0 := by + intro j hj + have h0 : ∀ i ∈ Finset.range (DenseMatrix.freeColumns cols R.pivots).size, + i ≠ j → + v.getD ((DenseMatrix.freeColumns cols R.pivots).getD i 0) 0 * + (basisVectorForFreeColumnRows R.rows R.pivots cols + ((DenseMatrix.freeColumns cols R.pivots).getD i 0)).getD + ((DenseMatrix.freeColumns cols R.pivots).getD j 0) 0 = 0 := by + intro i hi hij + rw [basisVector_getD_freeColumn R.rows R.pivots cols + (Finset.mem_range.mp hi) hj, if_neg (fun hc ↦ hij hc.symm), mul_zero] + rw [Finset.sum_eq_single_of_mem j (Finset.mem_range.mpr hj) h0, + basisVector_getD_freeColumn R.rows R.pivots cols hj hj, if_pos rfl, + mul_one] + intro k hk + rcases freeColumns_or_pivot (pivots := R.pivots) hk with + ⟨j, hj, hjk⟩ | ⟨t, ht, htk⟩ + · rw [← hjk] + exact hfree_coord j hj + · have hrow_v := horthR t + unfold scalarDot at hrow_v + have hrow_b : ∀ i, i < (DenseMatrix.freeColumns cols R.pivots).size → + ∑ k' ∈ Finset.range cols, (R.rows.getD t #[]).getD k' 0 * + (basisVectorForFreeColumnRows R.rows R.pivots cols + ((DenseMatrix.freeColumns cols R.pivots).getD i 0)).getD k' 0 = + 0 := by + intro i hi + obtain ⟨hilt, hinot⟩ := freeColumns_mem (freeColumns_getD_mem hi) + have hb := scalarDot_basisVector hspec hilt hinot t + unfold scalarDot at hb + exact hb + have hsum : ∑ k' ∈ Finset.range cols, + (R.rows.getD t #[]).getD k' 0 * + (v.getD k' 0 - + ∑ i ∈ Finset.range (DenseMatrix.freeColumns cols R.pivots).size, + v.getD ((DenseMatrix.freeColumns cols R.pivots).getD i 0) 0 * + (basisVectorForFreeColumnRows R.rows R.pivots cols + ((DenseMatrix.freeColumns cols R.pivots).getD i 0)).getD + k' 0) = 0 := by + simp only [mul_sub] + rw [Finset.sum_sub_distrib, hrow_v, zero_sub, neg_eq_zero] + simp only [Finset.mul_sum] + rw [Finset.sum_comm] + refine Finset.sum_eq_zero fun i hi ↦ ?_ + have hswap : ∀ k' : Nat, + (R.rows.getD t #[]).getD k' 0 * + (v.getD ((DenseMatrix.freeColumns cols R.pivots).getD i 0) 0 * + (basisVectorForFreeColumnRows R.rows R.pivots cols + ((DenseMatrix.freeColumns cols R.pivots).getD i 0)).getD + k' 0) = + v.getD ((DenseMatrix.freeColumns cols R.pivots).getD i 0) 0 * + ((R.rows.getD t #[]).getD k' 0 * + (basisVectorForFreeColumnRows R.rows R.pivots cols + ((DenseMatrix.freeColumns cols R.pivots).getD i 0)).getD + k' 0) := + fun k' ↦ mul_left_comm _ _ _ + rw [Finset.sum_congr rfl fun k' _ ↦ hswap k', ← Finset.mul_sum, + hrow_b i (Finset.mem_range.mp hi), mul_zero] + have hothers : ∀ k' ∈ Finset.range cols, k' ≠ R.pivots.getD t 0 → + (R.rows.getD t #[]).getD k' 0 * + (v.getD k' 0 - + ∑ i ∈ Finset.range (DenseMatrix.freeColumns cols R.pivots).size, + v.getD ((DenseMatrix.freeColumns cols R.pivots).getD i 0) 0 * + (basisVectorForFreeColumnRows R.rows R.pivots cols + ((DenseMatrix.freeColumns cols R.pivots).getD i 0)).getD + k' 0) = 0 := by + intro k' hk' hne + rcases freeColumns_or_pivot (pivots := R.pivots) + (Finset.mem_range.mp hk') with ⟨j, hj, hjk'⟩ | ⟨s, hs, hsk'⟩ + · rw [← hjk', ← hfree_coord j hj, sub_self, mul_zero] + · have hst : ¬t = s := fun hc ↦ hne (by rw [hc]; exact hsk'.symm) + rw [← hsk', hspec.unit s hs t, if_neg hst, zero_mul] + rw [Finset.sum_eq_single_of_mem (R.pivots.getD t 0) + (Finset.mem_range.mpr (hspec.pivots_lt t ht)) hothers, + hspec.unit t ht t, if_pos rfl, one_mul, sub_eq_zero] at hsum + rw [← htk] + exact hsum + +private theorem homogeneousKernelBasisRows_size_eq [Field F] [BEq F] + [LawfulBEq F] (rows : Array (Array F)) (cols : Nat) : + (homogeneousKernelBasisRows rows cols).size = + (DenseMatrix.freeColumns cols (scalarRrefRows rows cols).pivots).size := by + simp only [homogeneousKernelBasisRows, Array.size_map] + +private theorem homogeneousKernelBasisRows_getD_eq [Field F] [BEq F] + [LawfulBEq F] (rows : Array (Array F)) (cols : Nat) {i : Nat} + (hi : i < + (DenseMatrix.freeColumns cols (scalarRrefRows rows cols).pivots).size) : + (homogeneousKernelBasisRows rows cols).getD i #[] = + basisVectorForFreeColumnRows (scalarRrefRows rows cols).rows + (scalarRrefRows rows cols).pivots cols + ((DenseMatrix.freeColumns cols + (scalarRrefRows rows cols).pivots).getD i 0) := by + simp only [homogeneousKernelBasisRows] + rw [array_getD_of_lt' _ #[] (by rw [Array.size_map]; exact hi), + Array.getElem_map, array_getD_of_lt' _ 0 hi] + +/-- **Completeness of the scalar kernel leaf.** Every vector orthogonal to +all input rows over the coordinates `0, …, cols - 1` is, coordinatewise below +`cols`, the `F`-linear combination of the emitted kernel basis vectors whose +coefficients are the values of the vector at the corresponding free +columns. -/ +theorem homogeneousKernelBasisRows_complete [Field F] [BEq F] [LawfulBEq F] + (rows : Array (Array F)) (cols : Nat) {v : Array F} + (hv : ∀ r ∈ rows.toList, + ∑ k ∈ Finset.range cols, r.getD k 0 * v.getD k 0 = 0) : + ∀ k, k < cols → + v.getD k 0 = + ∑ i ∈ Finset.range (homogeneousKernelBasisRows rows cols).size, + v.getD ((DenseMatrix.freeColumns cols + (scalarRrefRows rows cols).pivots).getD i 0) 0 * + ((homogeneousKernelBasisRows rows cols).getD i #[]).getD k 0 := by + have horth : OrthRows cols rows v := by + intro i + by_cases hi : i < rows.size + · have hmem : rows[i] ∈ rows.toList := by + rw [← Array.getElem_toList (by simpa using hi)] + exact List.getElem_mem _ + have h0 := hv rows[i] hmem + unfold scalarDot + rw [array_getD_of_lt' rows #[] hi] + exact h0 + · rw [array_getD_of_le' rows #[] (Nat.le_of_not_lt hi)] + exact scalarDot_empty cols v + have hmain := kernelBasis_complete_aux (scalarRrefRows_spec rows cols) + (orthRows_scalarRrefRows_forward horth) + intro k hk + rw [homogeneousKernelBasisRows_size_eq rows cols, hmain k hk] + refine Finset.sum_congr rfl fun i hi ↦ ?_ + rw [homogeneousKernelBasisRows_getD_eq rows cols (Finset.mem_range.mp hi)] + +end Main + +end Approximant + +end PolynomialMatrix + +end CompPoly diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/KernelLeafSoundness.lean b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/KernelLeafSoundness.lean new file mode 100644 index 00000000..8ebf9c6b --- /dev/null +++ b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/KernelLeafSoundness.lean @@ -0,0 +1,613 @@ +/- +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.LinearAlgebra.PolynomialMatrix.Approximant.PMBasis.XAdicSoundness +import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant.PMBasis.KernelLeafScalar + +/-! +# Kernel-Leaf Basis Soundness + +Every row of the kernel-leaf basis satisfies the X-adic approximant +conditions and has the principal row width: kernel vectors reconstruct to +solutions, and the reduction, completion, and compaction steps preserve +soundness. +-/ + +namespace CompPoly + +namespace PolynomialMatrix + +namespace Approximant + +variable {F : Type*} [Field F] [BEq F] [LawfulBEq F] + +/-! ## Kernel-leaf basis soundness -/ + +/-- Coefficient-shift row scaling preserves the row size. -/ +theorem rowScaleCoeffX_size (c : F) (d : Nat) (row : PolynomialRow F) : + (rowScaleCoeffX c d row).size = row.size := by + simp [rowScaleCoeffX] + +private theorem insertKernelLeafPivotRowWithFuel_invariant + (Q : PolynomialRow F → Prop) + (hclosed : ∀ (target reducer : PolynomialRow F) (shift : Array Nat), + Q target → Q reducer → Q (cancelKernelLeafLeadingTerm target reducer shift)) : + ∀ (fuel : Nat) (pivots : Array (Option (PolynomialRow F))) + (shift : Array Nat) (row : PolynomialRow F), + (∀ p r, pivots.getD p none = some r → Q r) → Q row → + ∀ p r, + (insertKernelLeafPivotRowWithFuel fuel pivots shift row).getD p none = + some r → Q r := by + intro fuel + induction fuel with + | zero => + intro pivots shift row hpivots _hrow p r hget + exact hpivots p r hget + | succ fuel ih => + intro pivots shift row hpivots hrow p r hget + rw [insertKernelLeafPivotRowWithFuel] at hget + have hset : ∀ (position : Nat) (newRow : PolynomialRow F), + Q newRow → + ∀ p' r', + (pivots.setIfInBounds position (some newRow)).getD p' none = some r' → + Q r' := by + intro position newRow hnew p' r' hget' + by_cases hpos : p' = position ∧ position < pivots.size + · rcases hpos with ⟨hp, hlt⟩ + subst hp + rw [Array.getD_eq_getD_getElem?, + Array.getElem?_setIfInBounds_self_of_lt hlt, Option.getD_some] at hget' + cases hget' + exact hnew + · rcases Nat.lt_or_ge p' pivots.size with hplt | hpge + · have hne : p' ≠ position := fun hcontra ↦ hpos ⟨hcontra, hcontra ▸ hplt⟩ + rw [Array.getD_eq_getD_getElem?, + Array.getElem?_setIfInBounds_ne (by omega)] at hget' + exact hpivots p' r' (by rw [Array.getD_eq_getD_getElem?]; exact hget') + · rw [Array.getD_eq_getD_getElem?, Array.getElem?_eq_none + (by simpa using hpge)] at hget' + cases hget' + cases hterm : rowShiftedLeadingTerm? row shift with + | none => + simp only [hterm] at hget + exact hpivots p r hget + | some target => + simp only [hterm] at hget + cases hpivot : pivots.getD target.position none with + | none => + simp only [hpivot] at hget + exact hset target.position row hrow p r hget + | some pivot => + simp only [hpivot] at hget + cases hpterm : rowShiftedLeadingTerm? pivot shift with + | none => + simp only [hpterm] at hget + exact hset target.position row hrow p r hget + | some reducer => + simp only [hpterm] at hget + have hpivotrow : Q pivot := hpivots target.position pivot hpivot + split at hget + · exact ih (pivots.setIfInBounds target.position (some row)) shift + (cancelKernelLeafLeadingTerm pivot row shift) + (fun p' r' hget' ↦ hset target.position row hrow p' r' hget') + (hclosed _ _ _ hpivotrow hrow) p r hget + · exact ih pivots shift + (cancelKernelLeafLeadingTerm row pivot shift) hpivots + (hclosed _ _ _ hrow hpivotrow) p r hget + +omit [BEq F] [LawfulBEq F] in +/-- Any predicate holding for all stored pivot rows holds for all extracted rows. -/ +theorem pivotRows_invariant (Q : PolynomialRow F → Prop) + {pivots : Array (Option (PolynomialRow F))} + (hpivots : ∀ p r, pivots.getD p none = some r → Q r) + {row : PolynomialRow F} (hrow : row ∈ MatrixRows (pivotRows pivots)) : + Q row := by + rw [MatrixRows, pivotRows, List.toList_toArray] at hrow + rcases List.mem_filterMap.mp hrow with ⟨entry, hentry, hid⟩ + rcases List.getElem_of_mem hentry with ⟨p, hp, hget⟩ + refine hpivots p row ?_ + rw [Array.getD_eq_getD_getElem?, Array.getElem?_eq_getElem (by simpa using hp), + Option.getD_some] + rw [show pivots[p] = pivots.toList[p] from by rw [Array.getElem_toList], hget] + exact hid + +/-- Any predicate closed under leading-term cancellation is preserved by the +pivot-table reduction. -/ +theorem reduceKernelLeafRowsByPivots_invariant (Q : PolynomialRow F → Prop) + (hclosed : ∀ (target reducer : PolynomialRow F) (shift : Array Nat), + Q target → Q reducer → Q (cancelKernelLeafLeadingTerm target reducer shift)) + {rows : PolynomialMatrix F} (shift : Array Nat) + (hrows : ∀ row ∈ MatrixRows rows, Q row) + {row : PolynomialRow F} + (hrow : row ∈ MatrixRows (reduceKernelLeafRowsByPivots rows shift)) : + Q row := by + rw [reduceKernelLeafRowsByPivots] at hrow + refine pivotRows_invariant Q ?_ hrow + have hfold : ∀ (l : List (PolynomialRow F)) + (pivots : Array (Option (PolynomialRow F))), + (∀ r ∈ l, Q r) → + (∀ p r, pivots.getD p none = some r → Q r) → + ∀ p r, + (l.foldl (fun pivots row ↦ + insertKernelLeafPivotRowWithFuel (reduceKernelLeafFuel rows shift) + pivots shift row) pivots).getD p none = some r → Q r := by + intro l + induction l with + | nil => + intro pivots _hl hpivots p r hget + exact hpivots p r hget + | cons head tail ih => + intro pivots hl hpivots p r hget + rw [List.foldl_cons] at hget + refine ih _ (fun r hr ↦ hl r (List.mem_cons_of_mem head hr)) ?_ p r hget + intro p' r' hget' + exact insertKernelLeafPivotRowWithFuel_invariant Q hclosed + (reduceKernelLeafFuel rows shift) pivots shift head hpivots + (hl head List.mem_cons_self) p' r' hget' + rw [← Array.foldl_toList] + refine hfold rows.toList _ (fun r hr ↦ hrows r hr) ?_ + intro p r hget + rw [Array.getD_eq_getD_getElem?] at hget + rcases Nat.lt_or_ge p (Array.replicate (MatrixWidth rows) + (none : Option (PolynomialRow F))).size with hp | hp + · rw [Array.getElem?_eq_getElem hp, Option.getD_some] at hget + rw [Array.getElem_replicate] at hget + cases hget + · rw [Array.getElem?_eq_none hp] at hget + cases hget + +omit [BEq F] [LawfulBEq F] in +/-- A row of `rows.push row` is either a row of `rows` or `row` itself. -/ +theorem mem_matrixRows_push {rows : PolynomialMatrix F} + {row r : PolynomialRow F} (hr : r ∈ MatrixRows (rows.push row)) : + r ∈ MatrixRows rows ∨ r = row := by + rw [MatrixRows, Array.toList_push, List.mem_append] at hr + rcases hr with hr | hr + · exact Or.inl hr + · exact Or.inr (by simpa using hr) + +/-- Any predicate closed under leading-term cancellation is preserved by the +incremental pivot reduction. -/ +theorem reduceKernelLeafRowsIncremental_invariant + (Q : PolynomialRow F → Prop) + (hclosed : ∀ (target reducer : PolynomialRow F) (shift : Array Nat), + Q target → Q reducer → Q (cancelKernelLeafLeadingTerm target reducer shift)) + {rows : PolynomialMatrix F} (shift : Array Nat) + (hrows : ∀ row ∈ MatrixRows rows, Q row) + {row : PolynomialRow F} + (hrow : row ∈ MatrixRows (reduceKernelLeafRowsIncremental rows shift)) : + Q row := by + rw [reduceKernelLeafRowsIncremental, ← Array.foldl_toList] at hrow + have hfold : ∀ (l : List (PolynomialRow F)) (basis : PolynomialMatrix F), + (∀ r ∈ l, Q r) → + (∀ r ∈ MatrixRows basis, Q r) → + ∀ r ∈ MatrixRows (l.foldl + (fun basis row ↦ insertKernelLeafRowIncremental basis shift row) basis), + Q r := by + intro l + induction l with + | nil => + intro basis _hl hbasis r hr + exact hbasis r hr + | cons head tail ih => + intro basis hl hbasis r hr + rw [List.foldl_cons] at hr + refine ih _ (fun x hx ↦ hl x (List.mem_cons_of_mem head hx)) ?_ r hr + intro x hx + rw [insertKernelLeafRowIncremental] at hx + have hx' : x ∈ MatrixRows (reduceKernelLeafRows (basis.push head) shift) := by + rw [MatrixRows] at hx ⊢ + have hmem : x ∈ reduceKernelLeafRows (basis.push head) shift ∧ + rowIsZero x = false := by + simpa using hx + simpa using hmem.1 + rw [reduceKernelLeafRows] at hx' + refine reduceKernelLeafRowsByPivots_invariant Q hclosed shift ?_ hx' + intro y hy + rcases mem_matrixRows_push hy with hy | rfl + · exact hbasis y hy + · exact hl _ List.mem_cons_self + exact hfold rows.toList #[] (fun r hr ↦ hrows r hr) + (by intro r hr; simp [MatrixRows] at hr) row hrow + +private theorem pm_foldl_push_toList {α β : Type*} (g : α → β) : + ∀ (l : List α) (acc : Array β), + (l.foldl (fun out x ↦ out.push (g x)) acc).toList = acc.toList ++ l.map g := by + intro l + induction l with + | nil => intro acc; simp + | cons head tail ih => + intro acc + rw [List.foldl_cons, ih, Array.toList_push, List.map_cons, + List.append_assoc, List.singleton_append] + +private theorem pm_doubleFoldl_toList (orders : Array Nat) : + ∀ (l : List Nat) (acc : Array (Nat × Nat)), + (l.foldl (fun out j ↦ (List.range (orders.getD j 0)).foldl + (fun out t ↦ out.push (j, t)) out) acc).toList = + acc.toList ++ l.flatMap + (fun j ↦ (List.range (orders.getD j 0)).map (fun t ↦ (j, t))) := by + intro l + induction l with + | nil => intro acc; simp + | cons head tail ih => + intro acc + rw [List.foldl_cons, ih, pm_foldl_push_toList, List.flatMap_cons, + List.append_assoc] + +/-- Every in-range equation index appears in `coefficientEquationIndices`. -/ +private theorem mem_coefficientEquationIndices {orders : Array Nat} {j t : Nat} + (hj : j < orders.size) (ht : t < orders.getD j 0) : + (j, t) ∈ (coefficientEquationIndices orders).toList := by + rw [coefficientEquationIndices] + simp only [Std.Legacy.Range.forIn_eq_forIn_range', Std.Legacy.Range.size, + Nat.sub_zero, Nat.add_sub_cancel, Nat.div_one, + List.forIn_pure_yield_eq_foldl, bind_pure_comp, map_pure, bind_pure, + Id.run_pure] + have hrange : ∀ n : Nat, List.range' 0 n = List.range n := fun n ↦ + (List.range_eq_range' (n := n)).symm + simp only [hrange] + rw [pm_doubleFoldl_toList] + simp only [List.nil_append] + exact List.mem_flatMap.mpr ⟨j, List.mem_range.mpr hj, + List.mem_map.mpr ⟨t, List.mem_range.mpr ht, rfl⟩⟩ + +private theorem pm_sum_range_mul {M : Type*} [AddCommMonoid M] (cap : Nat) + (f : Nat → M) : + ∀ n : Nat, + ∑ col ∈ Finset.range (n * cap), f col = + ∑ k ∈ Finset.range n, ∑ a ∈ Finset.range cap, f (k * cap + a) := by + intro n + induction n with + | zero => simp + | succ n ih => + rw [Nat.succ_mul, Finset.sum_range_add, ih, Finset.sum_range_succ] + +omit [BEq F] [LawfulBEq F] in +/-- In-range entries of a scalar coefficient row. -/ +private theorem coefficientMatrixRow_getD (problem : XAdicProblem F) + (degreeCap : Nat) (equation : Nat × Nat) {col : Nat} + (hcol : col < problem.matrix.size * degreeCap) : + (coefficientMatrixRow problem degreeCap equation).getD col 0 = + if col % degreeCap ≤ equation.2 then + CPolynomial.coeff + (rowGet (problem.matrix.getD (col / degreeCap) #[]) equation.1) + (equation.2 - col % degreeCap) + else 0 := by + rw [coefficientMatrixRow, Array.getD_eq_getD_getElem?, List.getElem?_toArray, + List.getElem?_map, List.getElem?_range hcol, Option.map_some, Option.getD_some] + +/-- Coefficients of polynomial rows reconstructed from scalar vectors. -/ +theorem rowGet_vectorToPolynomialRow_coeff (cap width : Nat) + (v : Array F) (k a : Nat) : + CPolynomial.coeff (rowGet (vectorToPolynomialRow cap width v) k) a = + if k < width ∧ a < cap then v.getD (k * cap + a) 0 else 0 := by + rw [vectorToPolynomialRow, rowGet] + rcases Nat.lt_or_ge k width with hk | hk + · rw [Array.getD_eq_getD_getElem?, List.getElem?_toArray, List.getElem?_map, + List.getElem?_range hk, Option.map_some, Option.getD_some, + CPolynomial.coeff_ofArray] + rcases Nat.lt_or_ge a cap with ha | ha + · rw [if_pos ⟨hk, ha⟩, Array.getD_eq_getD_getElem?, List.getElem?_toArray, + List.getElem?_map, List.getElem?_range ha, Option.map_some, + Option.getD_some] + · rw [if_neg (by omega), Array.getD_eq_getD_getElem?, List.getElem?_toArray, + List.getElem?_eq_none (by simpa using ha), Option.getD_none] + · rw [Array.getD_eq_getD_getElem?, List.getElem?_toArray, + List.getElem?_eq_none (by simpa using hk), Option.getD_none, + if_neg (by omega)] + exact CPolynomial.coeff_zero a + +/-- Polynomial rows reconstructed from scalar kernel vectors satisfy the +X-adic conditions of the bounded leaf problem. -/ +theorem vectorToPolynomialRow_approximates (mulCtx : CPolynomial.MulContext F) + (problem : XAdicProblem F) {v : Array F} + (hv : v ∈ (homogeneousKernelBasisRows (coefficientMatrixRows problem) + (problem.matrix.size * leafDegreeCap problem)).toList) : + RowApproximates mulCtx problem + (vectorToPolynomialRow (leafDegreeCap problem) problem.matrix.size v) := by + rw [rowApproximates_iff] + intro j hj _hjw + rw [Polynomial.X_pow_dvd_iff] + intro t ht + have hcap_pos : 0 < leafDegreeCap problem := le_max_left 1 _ + have htcap : t < leafDegreeCap problem := + lt_of_lt_of_le ht (le_trans (getD_le_maxOrder problem hj) (Nat.le_max_right 1 _)) + have hr : coefficientMatrixRow problem (leafDegreeCap problem) (j, t) ∈ + (coefficientMatrixRows problem).toList := by + rw [coefficientMatrixRows, Array.toList_map] + exact List.mem_map.mpr ⟨(j, t), mem_coefficientEquationIndices hj ht, rfl⟩ + have hdot := homogeneousKernelBasisRows_dot_eq_zero hv hr + rw [Polynomial.finset_sum_coeff] + refine Eq.trans ?_ hdot + rw [pm_sum_range_mul] + refine Finset.sum_congr rfl fun k hk ↦ ?_ + have hk' : k < problem.matrix.size := Finset.mem_range.mp hk + have hentry : ∀ a, a < leafDegreeCap problem → + (coefficientMatrixRow problem (leafDegreeCap problem) (j, t)).getD + (k * leafDegreeCap problem + a) 0 = + if a ≤ t then + CPolynomial.coeff (rowGet (problem.matrix.getD k #[]) j) (t - a) + else 0 := by + intro a ha + have hcol : k * leafDegreeCap problem + a < + problem.matrix.size * leafDegreeCap problem := by + have h1 : k * leafDegreeCap problem + a < + (k + 1) * leafDegreeCap problem := by + rw [Nat.succ_mul] + omega + exact lt_of_lt_of_le h1 (Nat.mul_le_mul_right _ (by omega)) + have hdiv : (k * leafDegreeCap problem + a) / leafDegreeCap problem = k := by + rw [Nat.add_comm, Nat.add_mul_div_right _ _ hcap_pos, + Nat.div_eq_of_lt ha, Nat.zero_add] + have hmod : (k * leafDegreeCap problem + a) % leafDegreeCap problem = a := by + rw [Nat.add_comm, Nat.add_mul_mod_self_right, Nat.mod_eq_of_lt ha] + rw [coefficientMatrixRow_getD problem _ _ hcol, hdiv, hmod] + rw [Polynomial.coeff_mul, Finset.Nat.sum_antidiagonal_eq_sum_range_succ_mk] + refine Eq.trans (Finset.sum_congr rfl fun a ha ↦ ?_) + (Finset.sum_subset (fun x hx ↦ Finset.mem_range.mpr + (Nat.lt_of_lt_of_le (Finset.mem_range.mp hx) htcap)) fun a ha hnot ↦ ?_) + · have ha' : a < t + 1 := Finset.mem_range.mp ha + rw [← CPolynomial.coeff_toPoly, ← CPolynomial.coeff_toPoly, + rowGet_vectorToPolynomialRow_coeff, if_pos ⟨hk', by omega⟩, + hentry a (by omega), if_pos (by omega)] + exact mul_comm _ _ + · have ha1 : a < leafDegreeCap problem := Finset.mem_range.mp ha + have ha2 : t + 1 ≤ a := by + by_contra hcon + exact hnot (Finset.mem_range.mpr (by omega)) + rw [hentry a ha1, if_neg (by omega), zero_mul] + +/-- Arithmetic of the packed index `k * cap + a`. -/ +theorem pm_pack_index {cap : Nat} (hcap : 0 < cap) {size k a : Nat} + (hk : k < size) (ha : a < cap) : + k * cap + a < size * cap ∧ (k * cap + a) / cap = k ∧ + (k * cap + a) % cap = a := by + refine ⟨?_, ?_, ?_⟩ + · have h1 : k * cap + a < (k + 1) * cap := by + rw [Nat.succ_mul] + omega + exact lt_of_lt_of_le h1 (Nat.mul_le_mul_right _ (by omega)) + · rw [Nat.add_comm, Nat.add_mul_div_right _ _ hcap, Nat.div_eq_of_lt ha, + Nat.zero_add] + · rw [Nat.add_comm, Nat.add_mul_mod_self_right, Nat.mod_eq_of_lt ha] + +/-- Pack the coefficients below the leaf degree cap of a polynomial row into +one flat scalar vector: coefficient `a` of coordinate `k` is stored at index +`k * leafDegreeCap problem + a`. This is the coefficient-side inverse of +`vectorToPolynomialRow`. -/ +def rowToCoefficientVector (problem : XAdicProblem F) (row : PolynomialRow F) : + Array F := + (List.range (problem.matrix.size * leafDegreeCap problem)).map + (fun c ↦ CPolynomial.coeff (rowGet row (c / leafDegreeCap problem)) + (c % leafDegreeCap problem)) |>.toArray + +omit [BEq F] [LawfulBEq F] in +/-- In-range entries of the packed coefficient vector. -/ +private theorem rowToCoefficientVector_getD (problem : XAdicProblem F) + (row : PolynomialRow F) {c : Nat} + (hc : c < problem.matrix.size * leafDegreeCap problem) : + (rowToCoefficientVector problem row).getD c 0 = + CPolynomial.coeff (rowGet row (c / leafDegreeCap problem)) + (c % leafDegreeCap problem) := by + rw [rowToCoefficientVector, Array.getD_eq_getD_getElem?, + List.getElem?_toArray, List.getElem?_map, List.getElem?_range hc, + Option.map_some, Option.getD_some] + +/-- Reconstructing a polynomial row from its packed coefficient vector is the +identity on rows of the principal width whose coefficients respect the leaf +degree cap. -/ +theorem vectorToPolynomialRow_rowToCoefficientVector + (problem : XAdicProblem F) {row : PolynomialRow F} + (hsize : row.size = problem.matrix.size) + (hdeg : ∀ k, k < row.size → ∀ a, leafDegreeCap problem ≤ a → + CPolynomial.coeff (rowGet row k) a = 0) : + vectorToPolynomialRow (leafDegreeCap problem) problem.matrix.size + (rowToCoefficientVector problem row) = row := by + have hcap_pos : 0 < leafDegreeCap problem := le_max_left 1 _ + have hcoord : ∀ k, + rowGet (vectorToPolynomialRow (leafDegreeCap problem) + problem.matrix.size (rowToCoefficientVector problem row)) k = + rowGet row k := by + intro k + apply CPolynomial.eq_iff_coeff.2 + intro a + rw [rowGet_vectorToPolynomialRow_coeff] + rcases Nat.lt_or_ge k problem.matrix.size with hk | hk + · rcases Nat.lt_or_ge a (leafDegreeCap problem) with ha | ha + · obtain ⟨hc, hdiv, hmod⟩ := pm_pack_index hcap_pos hk ha + rw [if_pos ⟨hk, ha⟩, rowToCoefficientVector_getD problem row hc, + hdiv, hmod] + · rw [if_neg (by omega), hdeg k (by omega) a ha] + · rw [if_neg (by omega)] + have hzero : rowGet row k = 0 := by + rw [rowGet, Array.getD_eq_getD_getElem?, + Array.getElem?_eq_none (by omega)] + rfl + rw [hzero] + exact (CPolynomial.coeff_zero a).symm + refine Array.ext ?_ fun i hi hi' ↦ ?_ + · rw [vectorToPolynomialRow] + simp only [List.size_toArray, List.length_map, List.length_range, hsize] + · have h := hcoord i + rw [rowGet, rowGet, array_getD_of_lt' _ 0 hi, array_getD_of_lt' _ 0 hi'] + at h + exact h + +/-- Conversely to `mem_coefficientEquationIndices`, every member of +`coefficientEquationIndices` is an in-range equation index. -/ +private theorem mem_coefficientEquationIndices_bounds {orders : Array Nat} + {j t : Nat} + (h : (j, t) ∈ (coefficientEquationIndices orders).toList) : + j < orders.size ∧ t < orders.getD j 0 := by + rw [coefficientEquationIndices] at h + simp only [Std.Legacy.Range.forIn_eq_forIn_range', Std.Legacy.Range.size, + Nat.sub_zero, Nat.add_sub_cancel, Nat.div_one, + List.forIn_pure_yield_eq_foldl, bind_pure_comp, map_pure, bind_pure, + Id.run_pure] at h + have hrange : ∀ n : Nat, List.range' 0 n = List.range n := fun n ↦ + (List.range_eq_range' (n := n)).symm + simp only [hrange] at h + rw [pm_doubleFoldl_toList] at h + simp only [List.nil_append] at h + obtain ⟨j', hj', hmem⟩ := List.mem_flatMap.mp h + obtain ⟨t', ht', heq⟩ := List.mem_map.mp hmem + have h1 : j' = j := congrArg Prod.fst heq + have h2 : t' = t := congrArg Prod.snd heq + subst h1 + subst h2 + exact ⟨List.mem_range.mp hj', List.mem_range.mp ht'⟩ + +/-- **Converse coefficient bridge.** A polynomial row of a well-formed +bounded leaf problem that satisfies the X-adic conditions yields a packed +coefficient vector orthogonal to every scalar coefficient row. -/ +theorem coefficientMatrixRows_dot_eq_zero_of_approximates + (mulCtx : CPolynomial.MulContext F) (problem : XAdicProblem F) + {row : PolynomialRow F} + (happrox : RowApproximates mulCtx problem row) + (hwf : WellFormed problem.matrix) : + ∀ r ∈ (coefficientMatrixRows problem).toList, + ∑ c ∈ Finset.range (problem.matrix.size * leafDegreeCap problem), + r.getD c 0 * (rowToCoefficientVector problem row).getD c 0 = 0 := by + intro r hr + simp only [coefficientMatrixRows] at hr + rw [Array.toList_map] at hr + obtain ⟨eqn, heqmem, rfl⟩ := List.mem_map.mp hr + obtain ⟨j, t⟩ := eqn + obtain ⟨hj, ht⟩ := mem_coefficientEquationIndices_bounds heqmem + have hcap_pos : 0 < leafDegreeCap problem := le_max_left 1 _ + have htcap : t < leafDegreeCap problem := + lt_of_lt_of_le ht (le_trans (getD_le_maxOrder problem hj) + (Nat.le_max_right 1 _)) + have hdvd : (Polynomial.X : Polynomial F) ^ (problem.orders.getD j 0) ∣ + ∑ k ∈ Finset.range problem.matrix.size, + (rowGet row k).toPoly * + (rowGet (problem.matrix.getD k #[]) j).toPoly := by + rcases Nat.lt_or_ge j (MatrixWidth problem.matrix) with hjw | hjw + · exact (rowApproximates_iff mulCtx problem row).mp happrox j hj hjw + · have hzero : ∀ k ∈ Finset.range problem.matrix.size, + (rowGet row k).toPoly * + (rowGet (problem.matrix.getD k #[]) j).toPoly = 0 := by + intro k hk + have hk' : k < problem.matrix.size := Finset.mem_range.mp hk + have hmemrow : problem.matrix.getD k #[] ∈ + MatrixRows problem.matrix := by + rw [MatrixRows, array_getD_of_lt' _ #[] hk', + ← Array.getElem_toList (by simpa using hk')] + exact List.getElem_mem _ + have hsz := hwf _ hmemrow + have h0 : rowGet (problem.matrix.getD k #[]) j = 0 := by + rw [rowGet, Array.getD_eq_getD_getElem?, + Array.getElem?_eq_none (by omega)] + rfl + rw [h0, CPolynomial.toPoly_zero, mul_zero] + rw [Finset.sum_eq_zero hzero] + exact dvd_zero _ + have hcoeff := Polynomial.X_pow_dvd_iff.mp hdvd t ht + have hentry : ∀ k, k < problem.matrix.size → + ∀ a, a < leafDegreeCap problem → + (coefficientMatrixRow problem (leafDegreeCap problem) (j, t)).getD + (k * leafDegreeCap problem + a) 0 = + if a ≤ t then + CPolynomial.coeff (rowGet (problem.matrix.getD k #[]) j) (t - a) + else 0 := by + intro k hk a ha + obtain ⟨hc, hdiv, hmod⟩ := pm_pack_index hcap_pos hk ha + rw [coefficientMatrixRow_getD problem _ _ hc, hdiv, hmod] + have hw : ∀ k, k < problem.matrix.size → + ∀ a, a < leafDegreeCap problem → + (rowToCoefficientVector problem row).getD + (k * leafDegreeCap problem + a) 0 = + CPolynomial.coeff (rowGet row k) a := by + intro k hk a ha + obtain ⟨hc, hdiv, hmod⟩ := pm_pack_index hcap_pos hk ha + rw [rowToCoefficientVector_getD problem row hc, hdiv, hmod] + have hinner : ∀ k, k < problem.matrix.size → + ∑ a ∈ Finset.range (leafDegreeCap problem), + (coefficientMatrixRow problem (leafDegreeCap problem) (j, t)).getD + (k * leafDegreeCap problem + a) 0 * + (rowToCoefficientVector problem row).getD + (k * leafDegreeCap problem + a) 0 = + Polynomial.coeff ((rowGet row k).toPoly * + (rowGet (problem.matrix.getD k #[]) j).toPoly) t := by + intro k hk + symm + rw [Polynomial.coeff_mul, Finset.Nat.sum_antidiagonal_eq_sum_range_succ_mk] + refine Eq.trans (Finset.sum_congr rfl fun a ha ↦ ?_) + (Finset.sum_subset (fun x hx ↦ Finset.mem_range.mpr + (Nat.lt_of_lt_of_le (Finset.mem_range.mp hx) htcap)) + fun a ha hnot ↦ ?_) + · have ha' : a < t + 1 := Finset.mem_range.mp ha + rw [← CPolynomial.coeff_toPoly, ← CPolynomial.coeff_toPoly, + hentry k hk a (by omega), if_pos (by omega), hw k hk a (by omega)] + exact mul_comm _ _ + · have ha1 : a < leafDegreeCap problem := Finset.mem_range.mp ha + have ha2 : t + 1 ≤ a := by + by_contra hcon + exact hnot (Finset.mem_range.mpr (by omega)) + rw [hentry k hk a ha1, if_neg (by omega), zero_mul] + rw [pm_sum_range_mul, + Finset.sum_congr rfl fun k hk ↦ hinner k (Finset.mem_range.mp hk), + ← Polynomial.finset_sum_coeff] + exact hcoeff + +/-- Every kernel-leaf basis row approximates the problem and has the principal +row width. -/ +theorem kernelLeafBasis_rows (mulCtx : CPolynomial.MulContext F) + (problem : XAdicProblem F) (shift : Array Nat) : + ∀ row ∈ MatrixRows (kernelLeafBasis problem shift), + RowApproximates mulCtx problem row ∧ row.size = problem.matrix.size := by + set Q : PolynomialRow F → Prop := fun row ↦ + RowApproximates mulCtx problem row ∧ row.size = problem.matrix.size with hQ + have hclosed : ∀ (target reducer : PolynomialRow F) (shift' : Array Nat), + Q target → Q reducer → + Q (cancelKernelLeafLeadingTerm target reducer shift') := by + intro target reducer shift' ht hr + refine ⟨rowApproximates_cancelKernelLeafLeadingTerm mulCtx problem shift' + ht.1 hr.1, ?_⟩ + rw [cancelKernelLeafLeadingTerm] + split + · split + · split + · exact ht.2 + · rw [rowSub_size, rowScaleCoeffX_size] + have h1 := ht.2 + have h2 := hr.2 + omega + · exact ht.2 + · exact ht.2 + intro row hrow + simp only [kernelLeafBasis] at hrow + rw [MatrixRows, completeMissingPivotRows, Array.toList_append] at hrow + rcases List.mem_append.mp hrow with hmem | hmem + · refine reduceKernelLeafRowsIncremental_invariant Q hclosed shift ?_ hmem + intro r hr + rw [MatrixRows, Array.toList_append] at hr + rcases List.mem_append.mp hr with hr | hr + · rw [Array.toList_map] at hr + rcases List.mem_map.mp hr with ⟨w, hw, rfl⟩ + refine ⟨vectorToPolynomialRow_approximates mulCtx problem hw, ?_⟩ + simp [vectorToPolynomialRow] + · simp only [kernelLeafCompletionRows] at hr + rcases List.mem_map.mp hr with ⟨i, _hi, rfl⟩ + refine ⟨rowApproximates_monomialUnitRow mulCtx problem + (fun j hj ↦ le_trans (getD_le_maxOrder problem hj) + (Nat.le_max_right 1 _)), ?_⟩ + simp [monomialUnitRow] + · have happrox := missingCompletionRows_approximates mulCtx problem shift _ hmem + refine ⟨happrox, ?_⟩ + rw [missingCompletionRows, List.toList_toArray] at hmem + rcases List.mem_filterMap.mp hmem with ⟨i, _hi, hsome⟩ + split at hsome + · cases hsome + · cases hsome + simp [monomialUnitRow] + +end Approximant + +end PolynomialMatrix + +end CompPoly diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/KernelLeafSpan.lean b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/KernelLeafSpan.lean new file mode 100644 index 00000000..ab2fb787 --- /dev/null +++ b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/KernelLeafSpan.lean @@ -0,0 +1,1212 @@ +/- +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.LinearAlgebra.PolynomialMatrix.Approximant.PMBasis.KernelLeafSoundness +import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant.PMBasis.XAdicSoundness +import CompPoly.LinearAlgebra.PolynomialMatrix.MuldersStorjohannCorrectness.MatrixRows +import CompPoly.LinearAlgebra.PolynomialMatrix.MuldersStorjohannCorrectness.Measure + +/-! +# Kernel-Leaf Reduction Row-Span Soundness + +The shifted pivot-table reduction preserves the generated row module: the +fuel bound dominates the shifted row measure, every displaced row re-enters +the table as a reduced combination, and the incremental reduction loop keeps +every input row inside the span of its output. +-/ + +namespace CompPoly + +namespace PolynomialMatrix + +namespace Approximant + +variable {F : Type*} [Field F] [BEq F] [LawfulBEq F] + +/-! ## Kernel-leaf reduction row-span soundness + +The pivot-table reduction only ever replaces rows by row operations that are +invertible inside the generated row module, so no original row leaves the row +span. The proofs below make this precise: leading-term cancellation is the +Mulders-Storjohann cancellation in disguise, the pivot-table insertion loop is +tracked through a fuel-indexed measure argument, and the incremental reducer +chains the per-call result through row-span transitivity. -/ + +/-- Coefficient-shift scaling agrees with monomial multiplication. -/ +private theorem polynomialScaleCoeffX_eq_monomial_mul [DecidableEq F] + (c : F) (d : Nat) (p : CPolynomial F) : + polynomialScaleCoeffX c d p = CPolynomial.monomial d c * p := by + apply (CPolynomial.eq_iff_coeff).2 + intro i + rw [CPolynomial.coeff_monomial_mul, CPolynomial.coeff_toPoly, + polynomialScaleCoeffX_toPoly, mul_assoc, Polynomial.coeff_C_mul, + Polynomial.X_pow_mul, Polynomial.coeff_mul_X_pow'] + split_ifs with h + · rw [CPolynomial.coeff_toPoly] + · rw [mul_zero] + +/-- Coefficient-shift row scaling agrees with monomial row scaling. -/ +private theorem rowScaleCoeffX_eq_rowScaleMonomial [DecidableEq F] + (c : F) (d : Nat) (row : PolynomialRow F) : + rowScaleCoeffX c d row = rowScaleMonomial c d row := by + rw [rowScaleCoeffX, rowScaleMonomial, rowScalePolynomial] + exact Array.map_congr_left fun p _hp ↦ polynomialScaleCoeffX_eq_monomial_mul c d p + +/-- The kernel-leaf cancellation is the Mulders-Storjohann cancellation. -/ +private theorem cancelKernelLeafLeadingTerm_eq_cancelShifted [DecidableEq F] + (target reducer : PolynomialRow F) (shift : Array Nat) : + cancelKernelLeafLeadingTerm target reducer shift = + cancelShiftedLeadingTerm target reducer shift := by + rw [cancelKernelLeafLeadingTerm, cancelShiftedLeadingTerm] + cases rowShiftedLeadingTerm? target shift with + | none => rfl + | some t => + cases rowShiftedLeadingTerm? reducer shift with + | none => rfl + | some r => simp only [rowScaleCoeffX_eq_rowScaleMonomial] + +/-- Kernel-leaf cancellation preserves the row width. -/ +private theorem cancelKernelLeafLeadingTerm_size + {target reducer : PolynomialRow F} {shift : Array Nat} + (hsize : reducer.size = target.size) : + (cancelKernelLeafLeadingTerm target reducer shift).size = target.size := by + rw [cancelKernelLeafLeadingTerm] + split + · split + · split + · rfl + · rw [rowSub_size, rowScaleCoeffX_size, hsize, Nat.max_self] + · rfl + · rfl + +/-- Kernel-leaf cancellation stays inside a row span. -/ +private theorem cancelKernelLeafLeadingTerm_mem_rowSpan [DecidableEq F] + {M : PolynomialMatrix F} {target reducer : PolynomialRow F} + {shift : Array Nat} + (htarget : target ∈ RowSpan M) (hreducer : reducer ∈ RowSpan M) : + cancelKernelLeafLeadingTerm target reducer shift ∈ RowSpan M := by + rw [cancelKernelLeafLeadingTerm_eq_cancelShifted] + exact cancelShiftedLeadingTerm_mem_rowSpan htarget hreducer + +/-- Kernel-leaf cancellation can be undone inside a row span. -/ +private theorem cancelKernelLeafLeadingTerm_target_mem_rowSpan [DecidableEq F] + {M : PolynomialMatrix F} {target reducer : PolynomialRow F} + {shift : Array Nat} + (hcancel : cancelKernelLeafLeadingTerm target reducer shift ∈ RowSpan M) + (hreducer : reducer ∈ RowSpan M) + (hsize : reducer.size = target.size) : + target ∈ RowSpan M := by + rw [cancelKernelLeafLeadingTerm_eq_cancelShifted] at hcancel + exact cancelShiftedLeadingTerm_target_mem_rowSpan hcancel hreducer hsize + +/-- Kernel-leaf cancellation strictly decreases the shifted row measure. -/ +private theorem cancelKernelLeafLeadingTerm_shiftedRowMeasure_lt [DecidableEq F] + {target reducer : PolynomialRow F} {shift : Array Nat} + {t r : ShiftedLeadingTerm F} + (ht : rowShiftedLeadingTerm? target shift = some t) + (hr : rowShiftedLeadingTerm? reducer shift = some r) + (hpos : t.position = r.position) + (hle : r.shiftedDegree ≤ t.shiftedDegree) + (hsize : reducer.size = target.size) : + shiftedRowMeasure (cancelKernelLeafLeadingTerm target reducer shift) shift < + shiftedRowMeasure target shift := by + rw [cancelKernelLeafLeadingTerm_eq_cancelShifted] + exact cancelShiftedLeadingTerm_shiftedRowMeasure_lt ht hr hpos hle hsize + +omit [LawfulBEq F] in +/-- A zero row is the zero row of its width. -/ +theorem rowIsZero_eq_zeroRow {row : PolynomialRow F} + (h : RowIsZero row) : row = zeroRow row.size := by + apply Array.ext + · simp [zeroRow] + · intro j hj hj' + have hzero : row[j] = 0 := h row[j] (Array.getElem_mem_toList hj) + simp [zeroRow, hzero] + +/-- Rows without a shifted leading term are zero rows. -/ +private theorem rowIsZero_of_rowShiftedLeadingTerm?_eq_none + {row : PolynomialRow F} {shift : Array Nat} + (h : rowShiftedLeadingTerm? row shift = none) : RowIsZero row := by + cases hdeg : rowShiftedDegree? row shift with + | none => exact rowShiftedDegree?_eq_none_iff.1 hdeg + | some d => + rcases rowShiftedLeadingPosition?_some_of_degree hdeg with ⟨pos, hpos⟩ + rcases rowShiftedLeadingTerm?_some_of_position hpos with ⟨term, hterm, _⟩ + rw [hterm] at h + cases h + +/-- Rows with a shifted leading position are nonzero. -/ +private theorem not_rowIsZero_of_rowShiftedLeadingPosition?_eq_some + {row : PolynomialRow F} {shift : Array Nat} {p : Nat} + (h : rowShiftedLeadingPosition? row shift = some p) : ¬ RowIsZero row := by + intro hz + have hdeg : rowShiftedDegree? row shift = none := + rowShiftedDegree?_eq_none_iff.2 hz + rw [rowShiftedLeadingPosition?, hdeg] at h + cases h + +/-- A `some` slot index of an option array is in bounds. -/ +private theorem pivot_getD_some_lt_size {α : Type*} + {pivots : Array (Option α)} {p : Nat} {r : α} + (h : pivots.getD p none = some r) : p < pivots.size := by + by_contra hge + rw [array_getD_of_le' pivots none (Nat.le_of_not_lt hge)] at h + cases h + +omit [BEq F] [LawfulBEq F] in +/-- Every `some` slot of a pivot table appears among its pivot rows. -/ +private theorem mem_pivotRows_of_getD + {pivots : Array (Option (PolynomialRow F))} {p : Nat} + {r : PolynomialRow F} (h : pivots.getD p none = some r) : + r ∈ MatrixRows (pivotRows pivots) := by + have hp : p < pivots.size := pivot_getD_some_lt_size h + rw [MatrixRows, pivotRows, List.toList_toArray] + refine List.mem_filterMap.mpr ⟨some r, ?_, rfl⟩ + rw [array_getD_of_lt' pivots none hp] at h + rw [← h] + exact Array.getElem_mem_toList hp + +omit [BEq F] [LawfulBEq F] in +/-- Matrices with a member row are nonempty. -/ +theorem size_pos_of_mem_matrixRows {M : PolynomialMatrix F} + {r : PolynomialRow F} (h : r ∈ MatrixRows M) : 0 < M.size := by + rw [MatrixRows] at h + have hne := List.ne_nil_of_mem h + rw [← Array.length_toList] + exact List.length_pos_of_ne_nil hne + +omit [BEq F] [LawfulBEq F] in +/-- Uniform row widths make a matrix well formed. -/ +theorem wellFormed_of_sizes {M : PolynomialMatrix F} {n : Nat} + (h : ∀ r ∈ MatrixRows M, r.size = n) : WellFormed M := by + intro r hr + rw [matrixWidth_eq_of_first_row (size_pos_of_mem_matrixRows hr) h] + exact h r hr + +omit [BEq F] [LawfulBEq F] in +/-- Pivot rows inherit a uniform slot width. -/ +private theorem pivotRows_sizes {n : Nat} + {pivots : Array (Option (PolynomialRow F))} + (hsz : ∀ p r, pivots.getD p none = some r → r.size = n) : + ∀ r ∈ MatrixRows (pivotRows pivots), r.size = n := by + intro r hr + exact pivotRows_invariant (fun row ↦ row.size = n) hsz hr + +/-- Stored pivot rows lie in the pivot-row span. -/ +private theorem stored_mem_rowSpan_pivotRows [DecidableEq F] {n : Nat} + {pivots : Array (Option (PolynomialRow F))} + (hsz : ∀ p r, pivots.getD p none = some r → r.size = n) + {p : Nat} {r : PolynomialRow F} (hget : pivots.getD p none = some r) : + r ∈ RowSpan (pivotRows pivots) := + matrix_row_mem_rowSpan (wellFormed_of_sizes (pivotRows_sizes hsz)) + (mem_pivotRows_of_getD hget) + +/-- The width-`n` zero row lies in any nonempty pivot-row span of width `n`. -/ +private theorem zeroRow_mem_rowSpan_pivotRows [DecidableEq F] {n : Nat} + {pivots : Array (Option (PolynomialRow F))} + (hsz : ∀ p r, pivots.getD p none = some r → r.size = n) + {q : Nat} {s : PolynomialRow F} (hentry : pivots.getD q none = some s) : + zeroRow (F := F) n ∈ RowSpan (pivotRows pivots) := by + have hmem := mem_pivotRows_of_getD hentry + have hwidth : MatrixWidth (pivotRows pivots) = n := + matrixWidth_eq_of_first_row (size_pos_of_mem_matrixRows hmem) + (pivotRows_sizes hsz) + have hzero := zeroRow_mem_rowSpan (wellFormed_of_sizes (pivotRows_sizes hsz)) + rwa [hwidth] at hzero + +/-- Measure contribution of one pivot-table slot. -/ +private def pivotEntryMeasure [DecidableEq F] (shift : Array Nat) : + Option (PolynomialRow F) → Nat + | none => 0 + | some r => shiftedRowMeasure r shift + +/-- Total shifted measure of all stored pivot-table rows. -/ +private def pivotTableMeasure [DecidableEq F] + (pivots : Array (Option (PolynomialRow F))) (shift : Array Nat) : Nat := + (List.range pivots.size).foldl + (fun acc p ↦ acc + pivotEntryMeasure shift (pivots.getD p none)) 0 + +private theorem foldRange_add_eq_zero (n : Nat) {f : Nat → Nat} + (h : ∀ k, k < n → f k = 0) : + (List.range n).foldl (fun acc k ↦ acc + f k) 0 = 0 := by + induction n with + | zero => simp + | succ n ih => + rw [List.range_succ, List.foldl_append] + simp only [List.foldl_cons, List.foldl_nil] + rw [ih (fun k hk ↦ h k (by omega)), h n (by omega)] + +private theorem foldRange_add_update (n : Nat) (f : Nat → Nat) {p : Nat} + (hp : p < n) (v : Nat) : + (List.range n).foldl (fun acc k ↦ acc + (if k = p then v else f k)) 0 + + f p = + (List.range n).foldl (fun acc k ↦ acc + f k) 0 + v := by + induction n with + | zero => omega + | succ n ih => + rw [List.range_succ, List.foldl_append, List.foldl_append] + simp only [List.foldl_cons, List.foldl_nil] + by_cases hpn : p = n + · subst hpn + have hpref : + (List.range p).foldl + (fun acc k ↦ acc + (if k = p then v else f k)) 0 = + (List.range p).foldl (fun acc k ↦ acc + f k) 0 := by + apply foldRange_add_eq_of_pointwise + intro k hk + rw [if_neg (by omega)] + rw [hpref, if_pos rfl] + omega + · have hstep := ih (by omega) + rw [if_neg (fun h ↦ hpn h.symm)] + omega + +omit [LawfulBEq F] in +/-- The empty pivot table has measure zero. -/ +private theorem pivotTableMeasure_replicate [DecidableEq F] + (w : Nat) (shift : Array Nat) : + pivotTableMeasure + (Array.replicate w (none : Option (PolynomialRow F))) shift = 0 := by + rw [pivotTableMeasure, Array.size_replicate] + apply foldRange_add_eq_zero + intro k hk + rw [array_getD_of_lt' _ _ (by simpa using hk), Array.getElem_replicate] + rfl + +omit [LawfulBEq F] in +/-- Pivot-table measure of an in-bounds slot update. -/ +private theorem pivotTableMeasure_setIfInBounds [DecidableEq F] + (pivots : Array (Option (PolynomialRow F))) (shift : Array Nat) + {p : Nat} (hp : p < pivots.size) (row : PolynomialRow F) : + pivotTableMeasure (pivots.setIfInBounds p (some row)) shift + + pivotEntryMeasure shift (pivots.getD p none) = + pivotTableMeasure pivots shift + shiftedRowMeasure row shift := by + rw [pivotTableMeasure, pivotTableMeasure, Array.size_setIfInBounds] + have hpoint : + (List.range pivots.size).foldl + (fun acc k ↦ acc + + pivotEntryMeasure shift + ((pivots.setIfInBounds p (some row)).getD k none)) 0 = + (List.range pivots.size).foldl + (fun acc k ↦ acc + + (if k = p then shiftedRowMeasure row shift + else pivotEntryMeasure shift (pivots.getD k none))) 0 := by + apply foldRange_add_eq_of_pointwise + intro k hk + rw [array_getD_setIfInBounds] + by_cases hkp : k = p + · subst hkp + rw [if_pos ⟨rfl, hp⟩, if_pos rfl] + rfl + · rw [if_neg (fun hcon ↦ hkp hcon.1.symm), if_neg hkp] + rw [hpoint] + exact foldRange_add_update pivots.size _ hp (shiftedRowMeasure row shift) + +/-- Pivot-table insertion preserves the table size. -/ +private theorem insertKernelLeafPivotRowWithFuel_size : + ∀ (fuel : Nat) (pivots : Array (Option (PolynomialRow F))) + (shift : Array Nat) (row : PolynomialRow F), + (insertKernelLeafPivotRowWithFuel fuel pivots shift row).size = + pivots.size := by + intro fuel + induction fuel with + | zero => intro pivots shift row; rfl + | succ fuel ih => + intro pivots shift row + rw [insertKernelLeafPivotRowWithFuel] + split + · rfl + · split + · exact Array.size_setIfInBounds + · split + · exact Array.size_setIfInBounds + · split + · simp only [] + rw [ih, Array.size_setIfInBounds] + · simp only [] + rw [ih] + +omit [LawfulBEq F] in +private theorem pivotEntryMeasure_none [DecidableEq F] (shift : Array Nat) : + pivotEntryMeasure (F := F) shift none = 0 := rfl + +omit [LawfulBEq F] in +private theorem pivotEntryMeasure_some [DecidableEq F] (shift : Array Nat) + (r : PolynomialRow F) : + pivotEntryMeasure shift (some r) = shiftedRowMeasure r shift := rfl + +/-- Pivot-table insertion preserves slot widths and slot leading positions. -/ +private theorem insertKernelLeafPivotRowWithFuel_pivotInv {n : Nat} : + ∀ (fuel : Nat) (pivots : Array (Option (PolynomialRow F))) + (shift : Array Nat) (row : PolynomialRow F), + row.size = n → + (∀ p r, pivots.getD p none = some r → + r.size = n ∧ rowShiftedLeadingPosition? r shift = some p) → + ∀ p r, + (insertKernelLeafPivotRowWithFuel fuel pivots shift row).getD p none = + some r → + r.size = n ∧ rowShiftedLeadingPosition? r shift = some p := by + intro fuel + induction fuel with + | zero => + intro pivots shift row _hrow hinv p r hget + exact hinv p r hget + | succ fuel ih => + intro pivots shift row hrow hinv p r hget + have hset : ∀ (position : Nat) (newRow : PolynomialRow F), + newRow.size = n → + rowShiftedLeadingPosition? newRow shift = some position → + ∀ p' r', + (pivots.setIfInBounds position (some newRow)).getD p' none = + some r' → + r'.size = n ∧ rowShiftedLeadingPosition? r' shift = some p' := by + intro position newRow hsize hpos p' r' hget' + rw [array_getD_setIfInBounds] at hget' + by_cases hp' : position = p' ∧ position < pivots.size + · rw [if_pos hp'] at hget' + cases hget' + exact ⟨hsize, hp'.1 ▸ hpos⟩ + · rw [if_neg hp'] at hget' + exact hinv p' r' hget' + rw [insertKernelLeafPivotRowWithFuel] at hget + cases hterm : rowShiftedLeadingTerm? row shift with + | none => + simp only [hterm] at hget + exact hinv p r hget + | some target => + simp only [hterm] at hget + have hrowpos : rowShiftedLeadingPosition? row shift = + some target.position := + (rowShiftedLeadingTerm?_some_data hterm).2.1 + cases hpiv : pivots.getD target.position none with + | none => + simp only [hpiv] at hget + exact hset target.position row hrow hrowpos p r hget + | some pivot => + simp only [hpiv] at hget + cases hpterm : rowShiftedLeadingTerm? pivot shift with + | none => + simp only [hpterm] at hget + exact hset target.position row hrow hrowpos p r hget + | some reducer => + simp only [hpterm] at hget + have hpivn : pivot.size = n := + (hinv target.position pivot hpiv).1 + split at hget + · refine ih (pivots.setIfInBounds target.position (some row)) + shift (cancelKernelLeafLeadingTerm pivot row shift) ?_ ?_ + p r hget + · rw [cancelKernelLeafLeadingTerm_size (by omega)] + exact hpivn + · exact hset target.position row hrow hrowpos + · refine ih pivots shift + (cancelKernelLeafLeadingTerm row pivot shift) ?_ hinv + p r hget + rw [cancelKernelLeafLeadingTerm_size (by omega)] + exact hrow + +/-- Pivot-table insertion never erases an occupied slot. -/ +private theorem insertKernelLeafPivotRowWithFuel_persist : + ∀ (fuel : Nat) (pivots : Array (Option (PolynomialRow F))) + (shift : Array Nat) (row : PolynomialRow F) {q : Nat} + {s : PolynomialRow F}, + pivots.getD q none = some s → + ∃ s', + (insertKernelLeafPivotRowWithFuel fuel pivots shift row).getD q none = + some s' := by + intro fuel + induction fuel with + | zero => + intro pivots shift row q s hq + exact ⟨s, hq⟩ + | succ fuel ih => + intro pivots shift row q s hq + have hset : ∀ (position : Nat) (newRow : PolynomialRow F), + ∃ s', + (pivots.setIfInBounds position (some newRow)).getD q none = + some s' := by + intro position newRow + rw [array_getD_setIfInBounds] + by_cases hpq : position = q ∧ position < pivots.size + · rw [if_pos hpq] + exact ⟨newRow, rfl⟩ + · rw [if_neg hpq] + exact ⟨s, hq⟩ + rw [insertKernelLeafPivotRowWithFuel] + split + · exact ⟨s, hq⟩ + · split + · exact hset _ row + · split + · exact hset _ row + · split + · simp only [] + rcases hset _ row with ⟨s', hs'⟩ + exact ih _ shift _ hs' + · simp only [] + exact ih _ shift _ hq + +/-- Pivot-table insertion grows the table measure by at most the measure of +the inserted row. -/ +private theorem insertKernelLeafPivotRowWithFuel_measure_le [DecidableEq F] + {n : Nat} : + ∀ (fuel : Nat) (pivots : Array (Option (PolynomialRow F))) + (shift : Array Nat) (row : PolynomialRow F), + pivots.size = n → + row.size = n → + (∀ p r, pivots.getD p none = some r → + r.size = n ∧ rowShiftedLeadingPosition? r shift = some p) → + pivotTableMeasure (insertKernelLeafPivotRowWithFuel fuel pivots shift row) + shift ≤ + shiftedRowMeasure row shift + pivotTableMeasure pivots shift := by + intro fuel + induction fuel with + | zero => + intro pivots shift row _ _ _ + exact Nat.le_add_left _ _ + | succ fuel ih => + intro pivots shift row hpsize hrow hinv + rw [insertKernelLeafPivotRowWithFuel] + split + · exact Nat.le_add_left _ _ + · rename_i target hterm + have hrowpos : rowShiftedLeadingPosition? row shift = + some target.position := + (rowShiftedLeadingTerm?_some_data hterm).2.1 + have hb : target.position < pivots.size := by + have := rowShiftedLeadingPosition?_lt hrowpos + omega + split + · rename_i hpiv + have hmeas := pivotTableMeasure_setIfInBounds pivots shift hb row + rw [hpiv, pivotEntryMeasure_none] at hmeas + omega + · rename_i pivot hpiv + have hpivn : pivot.size = n := (hinv target.position pivot hpiv).1 + have hpivpos : rowShiftedLeadingPosition? pivot shift = + some target.position := (hinv target.position pivot hpiv).2 + split + · rename_i hpterm + rcases rowShiftedLeadingTerm?_some_of_position hpivpos with + ⟨term, hterm', _⟩ + rw [hterm'] at hpterm + cases hpterm + · rename_i reducer hpterm + have hredpos : target.position = reducer.position := by + have hd := (rowShiftedLeadingTerm?_some_data hpterm).2.1 + rw [hpivpos] at hd + exact Option.some.inj hd + split + · rename_i hlt + simp only [] + have hsetinv : ∀ p r, + (pivots.setIfInBounds target.position (some row)).getD p + none = some r → + r.size = n ∧ rowShiftedLeadingPosition? r shift = some p := by + intro p r hget' + rw [array_getD_setIfInBounds] at hget' + by_cases hp' : target.position = p ∧ + target.position < pivots.size + · rw [if_pos hp'] at hget' + cases hget' + exact ⟨hrow, hp'.1 ▸ hrowpos⟩ + · rw [if_neg hp'] at hget' + exact hinv p r hget' + have hredsize : + (cancelKernelLeafLeadingTerm pivot row shift).size = n := by + rw [cancelKernelLeafLeadingTerm_size (by omega)] + exact hpivn + have hih := ih (pivots.setIfInBounds target.position (some row)) + shift (cancelKernelLeafLeadingTerm pivot row shift) + (by rw [Array.size_setIfInBounds]; exact hpsize) hredsize + hsetinv + have hmeas := pivotTableMeasure_setIfInBounds pivots shift hb row + rw [hpiv, pivotEntryMeasure_some] at hmeas + have hdec : + shiftedRowMeasure (cancelKernelLeafLeadingTerm pivot row + shift) shift < shiftedRowMeasure pivot shift := + cancelKernelLeafLeadingTerm_shiftedRowMeasure_lt hpterm hterm + hredpos.symm (Nat.le_of_lt hlt) (by omega) + omega + · rename_i hlt + simp only [] + have hredsize : + (cancelKernelLeafLeadingTerm row pivot shift).size = n := by + rw [cancelKernelLeafLeadingTerm_size (by omega)] + exact hrow + have hih := ih pivots shift + (cancelKernelLeafLeadingTerm row pivot shift) hpsize hredsize + hinv + have hdec : + shiftedRowMeasure (cancelKernelLeafLeadingTerm row pivot + shift) shift < shiftedRowMeasure row shift := + cancelKernelLeafLeadingTerm_shiftedRowMeasure_lt hterm hpterm + hredpos (Nat.le_of_not_lt hlt) (by omega) + omega + +/-- Pivot-table insertion keeps the carried row and every stored row inside +the row span of the resulting pivot rows. The fuel hypothesis is the exact +measure bound consumed by the recursion. -/ +private theorem insertKernelLeafPivotRowWithFuel_rowSpan [DecidableEq F] + {n : Nat} : + ∀ (fuel : Nat) (pivots : Array (Option (PolynomialRow F))) + (shift : Array Nat) (row : PolynomialRow F), + pivots.size = n → + row.size = n → + (∀ p r, pivots.getD p none = some r → + r.size = n ∧ rowShiftedLeadingPosition? r shift = some p) → + shiftedRowMeasure row shift + pivotTableMeasure pivots shift < fuel → + (∀ p r, pivots.getD p none = some r → + r ∈ RowSpan + (pivotRows + (insertKernelLeafPivotRowWithFuel fuel pivots shift row))) ∧ + ((¬ RowIsZero row ∨ ∃ q s, pivots.getD q none = some s) → + row ∈ RowSpan + (pivotRows + (insertKernelLeafPivotRowWithFuel fuel pivots shift row))) := by + intro fuel + induction fuel with + | zero => + intro pivots shift row _ _ _ hfuel + exact absurd hfuel (Nat.not_lt_zero _) + | succ fuel ih => + intro pivots shift row hpsize hrow hinv hfuel + rw [insertKernelLeafPivotRowWithFuel] + split + · rename_i hterm + refine ⟨fun p r hget ↦ + stored_mem_rowSpan_pivotRows (fun p r h ↦ (hinv p r h).1) hget, ?_⟩ + intro hcase + have hz : RowIsZero row := + rowIsZero_of_rowShiftedLeadingTerm?_eq_none hterm + rcases hcase with hnz | ⟨q, s, hqs⟩ + · exact absurd hz hnz + · have hrow0 : row = zeroRow n := by + rw [← hrow] + exact rowIsZero_eq_zeroRow hz + rw [hrow0] + exact zeroRow_mem_rowSpan_pivotRows (fun p r h ↦ (hinv p r h).1) hqs + · rename_i target hterm + have hrowpos : rowShiftedLeadingPosition? row shift = + some target.position := + (rowShiftedLeadingTerm?_some_data hterm).2.1 + have hb : target.position < pivots.size := by + have := rowShiftedLeadingPosition?_lt hrowpos + omega + have hsetinv : ∀ p r, + (pivots.setIfInBounds target.position (some row)).getD p none = + some r → + r.size = n ∧ rowShiftedLeadingPosition? r shift = some p := by + intro p r hget' + rw [array_getD_setIfInBounds] at hget' + by_cases hp' : target.position = p ∧ target.position < pivots.size + · rw [if_pos hp'] at hget' + cases hget' + exact ⟨hrow, hp'.1 ▸ hrowpos⟩ + · rw [if_neg hp'] at hget' + exact hinv p r hget' + have hgetrow : + (pivots.setIfInBounds target.position (some row)).getD + target.position none = some row := by + rw [array_getD_setIfInBounds, if_pos ⟨rfl, hb⟩] + split + · rename_i hpiv + refine ⟨?_, fun _ ↦ + stored_mem_rowSpan_pivotRows (fun p r h ↦ (hsetinv p r h).1) + hgetrow⟩ + intro p r hget + have hpne : ¬ (target.position = p ∧ + target.position < pivots.size) := by + rintro ⟨hpe, -⟩ + rw [← hpe, hpiv] at hget + cases hget + have hget' : + (pivots.setIfInBounds target.position (some row)).getD p none = + some r := by + rw [array_getD_setIfInBounds, if_neg hpne] + exact hget + exact stored_mem_rowSpan_pivotRows (fun p r h ↦ (hsetinv p r h).1) + hget' + · rename_i pivot hpiv + have hpivn : pivot.size = n := (hinv target.position pivot hpiv).1 + have hpivpos : rowShiftedLeadingPosition? pivot shift = + some target.position := (hinv target.position pivot hpiv).2 + split + · rename_i hpterm + rcases rowShiftedLeadingTerm?_some_of_position hpivpos with + ⟨term, hterm', _⟩ + rw [hterm'] at hpterm + cases hpterm + · rename_i reducer hpterm + have hredpos : target.position = reducer.position := by + have hd := (rowShiftedLeadingTerm?_some_data hpterm).2.1 + rw [hpivpos] at hd + exact Option.some.inj hd + split + · rename_i hlt + simp only [] + have hredsize : + (cancelKernelLeafLeadingTerm pivot row shift).size = n := by + rw [cancelKernelLeafLeadingTerm_size (by omega)] + exact hpivn + have hmeas := pivotTableMeasure_setIfInBounds pivots shift hb row + rw [hpiv, pivotEntryMeasure_some] at hmeas + have hdec : + shiftedRowMeasure (cancelKernelLeafLeadingTerm pivot row + shift) shift < shiftedRowMeasure pivot shift := + cancelKernelLeafLeadingTerm_shiftedRowMeasure_lt hpterm hterm + hredpos.symm (Nat.le_of_lt hlt) (by omega) + have hIH := ih (pivots.setIfInBounds target.position (some row)) + shift (cancelKernelLeafLeadingTerm pivot row shift) + (by rw [Array.size_setIfInBounds]; exact hpsize) hredsize + hsetinv (by omega) + have hrowspan := hIH.1 target.position row hgetrow + have hredspan := hIH.2 + (Or.inr ⟨target.position, row, hgetrow⟩) + have hpivspan := + cancelKernelLeafLeadingTerm_target_mem_rowSpan hredspan + hrowspan (by omega) + refine ⟨?_, fun _ ↦ hrowspan⟩ + intro p r hget + by_cases hp : p = target.position + · subst hp + rw [hpiv] at hget + cases hget + exact hpivspan + · have hpne : ¬ (target.position = p ∧ + target.position < pivots.size) := by + rintro ⟨hpe, -⟩ + exact hp hpe.symm + have hget' : + (pivots.setIfInBounds target.position (some row)).getD p + none = some r := by + rw [array_getD_setIfInBounds, if_neg hpne] + exact hget + exact hIH.1 p r hget' + · rename_i hlt + simp only [] + have hredsize : + (cancelKernelLeafLeadingTerm row pivot shift).size = n := by + rw [cancelKernelLeafLeadingTerm_size (by omega)] + exact hrow + have hdec : + shiftedRowMeasure (cancelKernelLeafLeadingTerm row pivot + shift) shift < shiftedRowMeasure row shift := + cancelKernelLeafLeadingTerm_shiftedRowMeasure_lt hterm hpterm + hredpos (Nat.le_of_not_lt hlt) (by omega) + have hIH := ih pivots shift + (cancelKernelLeafLeadingTerm row pivot shift) hpsize hredsize + hinv (by omega) + have hpivspan := hIH.1 target.position pivot hpiv + have hredspan := hIH.2 + (Or.inr ⟨target.position, pivot, hpiv⟩) + have hrowspan := + cancelKernelLeafLeadingTerm_target_mem_rowSpan hredspan + hpivspan (by omega) + exact ⟨fun p r hget ↦ hIH.1 p r hget, fun _ ↦ hrowspan⟩ + +/-- The row span of the empty matrix only contains the empty row. -/ +theorem eq_empty_of_mem_rowSpan_empty {x : PolynomialRow F} + (hx : x ∈ RowSpan (#[] : PolynomialMatrix F)) : x = #[] := by + rcases hx with ⟨coeffs, _, rfl⟩ + rfl + +/-- A nonempty pivot-row span certifies a stored slot. -/ +private theorem exists_entry_of_mem_rowSpan_pivotRows + {pivots : Array (Option (PolynomialRow F))} {x : PolynomialRow F} + (hx : x ∈ RowSpan (pivotRows pivots)) (hxne : x ≠ #[]) : + ∃ q s, pivots.getD q none = some s := by + by_cases h : (pivotRows pivots).size = 0 + · have hempty : pivotRows pivots = #[] := Array.eq_empty_of_size_eq_zero h + rw [hempty] at hx + exact absurd (eq_empty_of_mem_rowSpan_empty hx) hxne + · have h0 : 0 < (pivotRows pivots).size := Nat.pos_of_ne_zero h + have hmem : (pivotRows pivots)[0] ∈ MatrixRows (pivotRows pivots) := by + rw [MatrixRows] + exact Array.getElem_mem_toList h0 + exact pivotRows_invariant (fun _ ↦ ∃ q s, pivots.getD q none = some s) + (fun q s hqs ↦ ⟨q, s, hqs⟩) hmem + +/-- Lift membership in one pivot-row span to a later pivot-row span when every +stored row of the first table stays in the later span. -/ +private theorem rowSpan_pivotRows_trans [DecidableEq F] {n : Nat} + {pivots tableT : Array (Option (PolynomialRow F))} + (hsz : ∀ p r, pivots.getD p none = some r → r.size = n) + (hszT : ∀ p r, tableT.getD p none = some r → r.size = n) + (hmono : ∀ p r, pivots.getD p none = some r → + r ∈ RowSpan (pivotRows tableT)) + (hpersist : ∀ q s, pivots.getD q none = some s → + ∃ s', tableT.getD q none = some s') + {x : PolynomialRow F} (hxnz : ¬ RowIsZero x) + (hx : x ∈ RowSpan (pivotRows pivots)) : + x ∈ RowSpan (pivotRows tableT) := by + have hxne : x ≠ #[] := by + intro h + apply hxnz + rw [h] + intro p hp + simp at hp + rcases exists_entry_of_mem_rowSpan_pivotRows hx hxne with ⟨q, s, hqs⟩ + rcases hpersist q s hqs with ⟨s', hqs'⟩ + have hw1 : MatrixWidth (pivotRows pivots) = n := + matrixWidth_eq_of_first_row + (size_pos_of_mem_matrixRows (mem_pivotRows_of_getD hqs)) + (pivotRows_sizes hsz) + have hw2 : MatrixWidth (pivotRows tableT) = n := + matrixWidth_eq_of_first_row + (size_pos_of_mem_matrixRows (mem_pivotRows_of_getD hqs')) + (pivotRows_sizes hszT) + rcases hx with ⟨coeffs, _, rfl⟩ + exact rowLinearCombination_mem_rowSpan_of_rows_mem + (wellFormed_of_sizes (pivotRows_sizes hszT)) (hw1.trans hw2.symm) + (fun r hr ↦ pivotRows_invariant _ hmono hr) coeffs + +/-- Folded pivot-table insertion preserves the table invariant. -/ +theorem insertKernelLeaf_foldl_pivotInv {n : Nat} : + ∀ (l : List (PolynomialRow F)) (fuel : Nat) + (pivots : Array (Option (PolynomialRow F))) (shift : Array Nat), + (∀ r ∈ l, r.size = n) → + (∀ p r, pivots.getD p none = some r → + r.size = n ∧ rowShiftedLeadingPosition? r shift = some p) → + ∀ p r, + (l.foldl (fun pv r ↦ insertKernelLeafPivotRowWithFuel fuel pv shift r) + pivots).getD p none = some r → + r.size = n ∧ rowShiftedLeadingPosition? r shift = some p := by + intro l + induction l with + | nil => + intro fuel pivots shift _ hinv p r hget + exact hinv p r hget + | cons head tail ihl => + intro fuel pivots shift hl hinv p r hget + rw [List.foldl_cons] at hget + exact ihl fuel _ shift (fun r hr ↦ hl r (List.mem_cons_of_mem head hr)) + (insertKernelLeafPivotRowWithFuel_pivotInv fuel pivots shift head + (hl head List.mem_cons_self) hinv) p r hget + +/-- Folded pivot-table insertion never erases an occupied slot. -/ +private theorem insertKernelLeaf_foldl_persist : + ∀ (l : List (PolynomialRow F)) (fuel : Nat) + (pivots : Array (Option (PolynomialRow F))) (shift : Array Nat) + {q : Nat} {s : PolynomialRow F}, + pivots.getD q none = some s → + ∃ s', + (l.foldl (fun pv r ↦ insertKernelLeafPivotRowWithFuel fuel pv shift r) + pivots).getD q none = some s' := by + intro l + induction l with + | nil => + intro fuel pivots shift q s hq + exact ⟨s, hq⟩ + | cons head tail ihl => + intro fuel pivots shift q s hq + rw [List.foldl_cons] + rcases insertKernelLeafPivotRowWithFuel_persist fuel pivots shift head hq + with ⟨s', hs'⟩ + exact ihl fuel _ shift hs' + +/-- Folded pivot-table insertion keeps every inserted nonzero row and every +initially stored row inside the row span of the final pivot rows. -/ +private theorem insertKernelLeaf_foldl_rowSpan [DecidableEq F] {n : Nat} : + ∀ (l : List (PolynomialRow F)) (fuel : Nat) + (pivots : Array (Option (PolynomialRow F))) (shift : Array Nat), + pivots.size = n → + (∀ r ∈ l, r.size = n) → + (∀ p r, pivots.getD p none = some r → + r.size = n ∧ rowShiftedLeadingPosition? r shift = some p) → + pivotTableMeasure pivots shift + + (l.map (fun r ↦ shiftedRowMeasure r shift)).sum < fuel → + (∀ p r, pivots.getD p none = some r → + r ∈ RowSpan (pivotRows + (l.foldl (fun pv r ↦ insertKernelLeafPivotRowWithFuel fuel pv shift r) + pivots))) ∧ + (∀ r ∈ l, ¬ RowIsZero r → + r ∈ RowSpan (pivotRows + (l.foldl (fun pv r ↦ insertKernelLeafPivotRowWithFuel fuel pv shift r) + pivots))) := by + intro l + induction l with + | nil => + intro fuel pivots shift _ _ hinv _ + refine ⟨fun p r hget ↦ + stored_mem_rowSpan_pivotRows (fun p r h ↦ (hinv p r h).1) hget, ?_⟩ + intro r hr + simp at hr + | cons head tail ihl => + intro fuel pivots shift hpsize hl hinv hfuel + rw [List.map_cons, List.sum_cons] at hfuel + have hheadsize : head.size = n := hl head List.mem_cons_self + have htailsizes : ∀ r ∈ tail, r.size = n := + fun r hr ↦ hl r (List.mem_cons_of_mem head hr) + rw [List.foldl_cons] + set pivots₁ := insertKernelLeafPivotRowWithFuel fuel pivots shift head + with hp₁ + have hsize₁ : pivots₁.size = n := by + rw [hp₁, insertKernelLeafPivotRowWithFuel_size] + exact hpsize + have hinv₁ : ∀ p r, pivots₁.getD p none = some r → + r.size = n ∧ rowShiftedLeadingPosition? r shift = some p := + insertKernelLeafPivotRowWithFuel_pivotInv fuel pivots shift head + hheadsize hinv + have hm₁ : pivotTableMeasure pivots₁ shift ≤ + shiftedRowMeasure head shift + pivotTableMeasure pivots shift := + insertKernelLeafPivotRowWithFuel_measure_le fuel pivots shift head + hpsize hheadsize hinv + have hC := insertKernelLeafPivotRowWithFuel_rowSpan fuel pivots shift + head hpsize hheadsize hinv (by omega) + have hIH := ihl fuel pivots₁ shift hsize₁ htailsizes hinv₁ (by omega) + have htrans : ∀ {x : PolynomialRow F}, ¬ RowIsZero x → + x ∈ RowSpan (pivotRows pivots₁) → + x ∈ RowSpan (pivotRows + (tail.foldl + (fun pv r ↦ insertKernelLeafPivotRowWithFuel fuel pv shift r) + pivots₁)) := by + intro x hxnz hx + exact rowSpan_pivotRows_trans (fun p r h ↦ (hinv₁ p r h).1) + (fun p r h ↦ + (insertKernelLeaf_foldl_pivotInv tail fuel pivots₁ shift + htailsizes hinv₁ p r h).1) + hIH.1 + (fun q s hqs ↦ + insertKernelLeaf_foldl_persist tail fuel pivots₁ shift hqs) + hxnz hx + constructor + · intro p r hget + have hrnz : ¬ RowIsZero r := + not_rowIsZero_of_rowShiftedLeadingPosition?_eq_some (hinv p r hget).2 + exact htrans hrnz (hC.1 p r hget) + · intro r hr hrnz + rcases List.mem_cons.mp hr with heq | hrtail + · subst heq + exact htrans hrnz (hC.2 (Or.inl hrnz)) + · exact hIH.2 r hrtail hrnz + +/-- The max-degree fold step used by `reduceKernelLeafFuel`. -/ +private def kernelLeafDegreeStep (rows : PolynomialMatrix F) + (shift : Array Nat) (acc : Nat) (i : Nat) : Nat := + match rowShiftedDegree? (rows.getD i #[]) shift with + | none => acc + | some degree => max acc degree + +omit [LawfulBEq F] in +private theorem reduceKernelLeafFuel_eq (rows : PolynomialMatrix F) + (shift : Array Nat) : + reduceKernelLeafFuel rows shift = + (rows.size + 1) * (MatrixWidth rows + 1) * + ((List.range rows.size).foldl (kernelLeafDegreeStep rows shift) 0 + + 1) := rfl + +omit [LawfulBEq F] in +private theorem kernelLeafDegreeStep_fold_le_acc (rows : PolynomialMatrix F) + (shift : Array Nat) : + ∀ (xs : List Nat) (acc : Nat), + acc ≤ xs.foldl (kernelLeafDegreeStep rows shift) acc := by + intro xs + induction xs with + | nil => intro acc; exact Nat.le_refl acc + | cons x xs ihx => + intro acc + rw [List.foldl_cons] + cases hx : rowShiftedDegree? (rows.getD x #[]) shift with + | none => + have hstep : kernelLeafDegreeStep rows shift acc x = acc := by + simp only [kernelLeafDegreeStep, hx] + rw [hstep] + exact ihx acc + | some d => + have hstep : kernelLeafDegreeStep rows shift acc x = max acc d := by + simp only [kernelLeafDegreeStep, hx] + rw [hstep] + exact Nat.le_trans (Nat.le_max_left acc d) (ihx (max acc d)) + +omit [LawfulBEq F] in +private theorem kernelLeafDegreeStep_fold_bound (rows : PolynomialMatrix F) + (shift : Array Nat) : + ∀ (xs : List Nat) (acc : Nat) {i d : Nat}, + i ∈ xs → + rowShiftedDegree? (rows.getD i #[]) shift = some d → + d ≤ xs.foldl (kernelLeafDegreeStep rows shift) acc := by + intro xs + induction xs with + | nil => + intro acc i d hi _ + cases hi + | cons x xs ihx => + intro acc i d hi hdeg + rw [List.foldl_cons] + rcases List.mem_cons.mp hi with heq | hi' + · have hdeg' : rowShiftedDegree? (rows.getD x #[]) shift = some d := + heq ▸ hdeg + have hstep : kernelLeafDegreeStep rows shift acc x = max acc d := by + simp only [kernelLeafDegreeStep, hdeg'] + rw [hstep] + exact Nat.le_trans (Nat.le_max_right acc d) + (kernelLeafDegreeStep_fold_le_acc rows shift xs (max acc d)) + · exact ihx _ hi' hdeg + +omit [LawfulBEq F] in +/-- Total shifted measure of all matrix rows is below the kernel-leaf fuel. -/ +private theorem sum_shiftedRowMeasure_lt_reduceKernelLeafFuel [DecidableEq F] + {rows : PolynomialMatrix F} {shift : Array Nat} {n : Nat} + (hsizes : ∀ r ∈ MatrixRows rows, r.size = n) + (hw : MatrixWidth rows = n) : + (rows.toList.map (fun r ↦ shiftedRowMeasure r shift)).sum < + reduceKernelLeafFuel rows shift := by + set D := (List.range rows.size).foldl (kernelLeafDegreeStep rows shift) 0 + with hD + have hbound : ∀ r ∈ rows.toList, + shiftedRowMeasure r shift ≤ (D + 1) * (n + 1) := by + intro r hr + rcases List.getElem_of_mem hr with ⟨i, hi, hget⟩ + have hi' : i < rows.size := by simpa using hi + have hgetD : rows.getD i #[] = r := by + rw [array_getD_of_lt' _ _ hi', + show rows[i] = rows.toList[i] from (Array.getElem_toList hi').symm] + exact hget + have hrsize : r.size = n := hsizes r hr + have hmle := shiftedRowMeasure_le_of_degree_bound + (row := r) (shift := shift) (d := D) ?_ + · rw [hrsize] at hmle + exact hmle + · intro rowDeg hdeg + refine kernelLeafDegreeStep_fold_bound rows shift (List.range rows.size) + 0 (List.mem_range.mpr hi') ?_ + rw [hgetD] + exact hdeg + have hsum := List.sum_le_card_nsmul + (rows.toList.map (fun r ↦ shiftedRowMeasure r shift)) ((D + 1) * (n + 1)) + (by + intro x hx + rcases List.mem_map.mp hx with ⟨r, hr, rfl⟩ + exact hbound r hr) + rw [List.length_map, Array.length_toList, smul_eq_mul] at hsum + rw [reduceKernelLeafFuel_eq, hw, ← hD] + calc (rows.toList.map (fun r ↦ shiftedRowMeasure r shift)).sum + ≤ rows.size * ((D + 1) * (n + 1)) := hsum + _ < (rows.size + 1) * ((D + 1) * (n + 1)) := + Nat.mul_lt_mul_of_pos_right (Nat.lt_succ_self rows.size) + (by positivity) + _ = (rows.size + 1) * (n + 1) * (D + 1) := by ring + +/-- The pivot-table reduction preserves the generated row module: every +nonzero source row stays inside the row span of the reduced matrix. -/ +theorem reduceKernelLeafRowsByPivots_rowSpan_superset [DecidableEq F] + {rows : PolynomialMatrix F} {shift : Array Nat} {n : Nat} + (hsizes : ∀ r ∈ MatrixRows rows, r.size = n) + {row : PolynomialRow F} (hrow : row ∈ MatrixRows rows) + (hnz : ¬ RowIsZero row) : + row ∈ RowSpan (reduceKernelLeafRowsByPivots rows shift) := by + have hpos : 0 < rows.size := size_pos_of_mem_matrixRows hrow + have hw : MatrixWidth rows = n := matrixWidth_eq_of_first_row hpos hsizes + rw [reduceKernelLeafRowsByPivots] + rw [← Array.foldl_toList] + refine (insertKernelLeaf_foldl_rowSpan (n := n) rows.toList + (reduceKernelLeafFuel rows shift) + (Array.replicate (MatrixWidth rows) none) shift ?_ ?_ ?_ ?_).2 row hrow hnz + · rw [Array.size_replicate] + exact hw + · exact fun r hr ↦ hsizes r hr + · intro p r hget + rcases Nat.lt_or_ge p + (Array.replicate (MatrixWidth rows) + (none : Option (PolynomialRow F))).size with hp | hp + · rw [array_getD_of_lt' _ _ hp, Array.getElem_replicate] at hget + cases hget + · rw [array_getD_of_le' _ _ hp] at hget + cases hget + · rw [pivotTableMeasure_replicate, Nat.zero_add] + exact sum_shiftedRowMeasure_lt_reduceKernelLeafFuel hsizes hw + +/-- Reduced kernel-leaf rows keep the uniform width and are nonzero. -/ +theorem reduceKernelLeafRowsByPivots_rows {n : Nat} + {rows : PolynomialMatrix F} {shift : Array Nat} + (hsizes : ∀ r ∈ MatrixRows rows, r.size = n) : + ∀ r ∈ MatrixRows (reduceKernelLeafRowsByPivots rows shift), + r.size = n ∧ rowIsZero r = false := by + intro r hr + rw [reduceKernelLeafRowsByPivots, ← Array.foldl_toList] at hr + have hinit : ∀ p r, + (Array.replicate (MatrixWidth rows) + (none : Option (PolynomialRow F))).getD p none = some r → + r.size = n ∧ rowShiftedLeadingPosition? r shift = some p := by + intro p r hget + rcases Nat.lt_or_ge p + (Array.replicate (MatrixWidth rows) + (none : Option (PolynomialRow F))).size with hp | hp + · rw [array_getD_of_lt' _ _ hp, Array.getElem_replicate] at hget + cases hget + · rw [array_getD_of_le' _ _ hp] at hget + cases hget + have hinv := insertKernelLeaf_foldl_pivotInv (n := n) rows.toList + (reduceKernelLeafFuel rows shift) + (Array.replicate (MatrixWidth rows) none) shift + (fun r hr ↦ hsizes r hr) hinit + refine pivotRows_invariant + (fun r ↦ r.size = n ∧ rowIsZero r = false) ?_ hr + intro p r hget + refine ⟨(hinv p r hget).1, ?_⟩ + have hnz := not_rowIsZero_of_rowShiftedLeadingPosition?_eq_some + (hinv p r hget).2 + cases hb : rowIsZero r + · rfl + · exact absurd (rowIsZero_iff.1 hb) hnz + +/-- The incremental insertion step is the plain pivot reduction of the pushed +matrix: the nonzero-row filter never removes anything. -/ +private theorem insertKernelLeafRowIncremental_eq {n : Nat} + {basis : PolynomialMatrix F} {shift : Array Nat} {row : PolynomialRow F} + (hsizes : ∀ r ∈ MatrixRows (basis.push row), r.size = n) : + insertKernelLeafRowIncremental basis shift row = + reduceKernelLeafRowsByPivots (basis.push row) shift := by + rw [insertKernelLeafRowIncremental, reduceKernelLeafRows] + rw [Array.filter_eq_self] + intro r hr + have hr' : r ∈ MatrixRows (reduceKernelLeafRowsByPivots (basis.push row) + shift) := by + rw [MatrixRows] + exact Array.mem_def.mp hr + rw [(reduceKernelLeafRowsByPivots_rows hsizes r hr').2] + rfl + +omit [BEq F] [LawfulBEq F] in +/-- Rows of a matrix stay rows after a push. -/ +private theorem mem_matrixRows_push_left {basis : PolynomialMatrix F} + {row r : PolynomialRow F} (hr : r ∈ MatrixRows basis) : + r ∈ MatrixRows (basis.push row) := by + rw [MatrixRows, Array.toList_push] + exact List.mem_append.mpr (Or.inl hr) + +omit [BEq F] [LawfulBEq F] in +/-- The pushed row is a row of the pushed matrix. -/ +private theorem mem_matrixRows_push_self {basis : PolynomialMatrix F} + {row : PolynomialRow F} : row ∈ MatrixRows (basis.push row) := by + rw [MatrixRows, Array.toList_push] + exact List.mem_append.mpr (Or.inr List.mem_cons_self) + +omit [LawfulBEq F] in +/-- The empty row is a zero row. -/ +theorem rowIsZero_empty : RowIsZero (#[] : PolynomialRow F) := by + intro p hp + simp at hp + +/-- Incremental kernel-leaf insertion keeps every previously accounted nonzero +row and every inserted nonzero row inside the row span of the final basis. -/ +private theorem insertIncremental_foldl_rowSpan [DecidableEq F] {n : Nat} : + ∀ (l : List (PolynomialRow F)) (basis : PolynomialMatrix F) + (shift : Array Nat), + (∀ r ∈ l, r.size = n) → + (∀ r ∈ MatrixRows basis, r.size = n) → + (∀ r ∈ MatrixRows basis, rowIsZero r = false) → + (∀ x : PolynomialRow F, ¬ RowIsZero x → x ∈ RowSpan basis → + x ∈ RowSpan (l.foldl + (fun b r ↦ insertKernelLeafRowIncremental b shift r) basis)) ∧ + (∀ r ∈ l, ¬ RowIsZero r → + r ∈ RowSpan (l.foldl + (fun b r ↦ insertKernelLeafRowIncremental b shift r) basis)) := by + intro l + induction l with + | nil => + intro basis shift _ _ _ + refine ⟨fun x _ hx ↦ hx, ?_⟩ + intro r hr + simp at hr + | cons head tail ihl => + intro basis shift hl hbsz hbnz + have hheadsize : head.size = n := hl head List.mem_cons_self + have htailsizes : ∀ r ∈ tail, r.size = n := + fun r hr ↦ hl r (List.mem_cons_of_mem head hr) + rw [List.foldl_cons] + have hpushsz : ∀ r ∈ MatrixRows (basis.push head), r.size = n := by + intro r hr + rcases mem_matrixRows_push hr with hr' | rfl + · exact hbsz r hr' + · exact hheadsize + set basis₁ := insertKernelLeafRowIncremental basis shift head with hb₁ + have hb₁eq : + basis₁ = reduceKernelLeafRowsByPivots (basis.push head) shift := + insertKernelLeafRowIncremental_eq hpushsz + have hb₁sz : ∀ r ∈ MatrixRows basis₁, r.size = n := by + rw [hb₁eq] + exact fun r hr ↦ (reduceKernelLeafRowsByPivots_rows hpushsz r hr).1 + have hb₁nz : ∀ r ∈ MatrixRows basis₁, rowIsZero r = false := by + rw [hb₁eq] + exact fun r hr ↦ (reduceKernelLeafRowsByPivots_rows hpushsz r hr).2 + have hIH := ihl basis₁ shift htailsizes hb₁sz hb₁nz + have hstep : ∀ r ∈ MatrixRows (basis.push head), ¬ RowIsZero r → + r ∈ RowSpan basis₁ := by + intro r hr hrnz + rw [hb₁eq] + exact reduceKernelLeafRowsByPivots_rowSpan_superset hpushsz hr hrnz + have hbasisnz : ∀ r ∈ MatrixRows basis, ¬ RowIsZero r := by + intro r hr hz + have hzb := rowIsZero_iff.2 hz + rw [hbnz r hr] at hzb + cases hzb + have hA : ∀ x : PolynomialRow F, ¬ RowIsZero x → x ∈ RowSpan basis → + x ∈ RowSpan basis₁ := by + intro x hxnz hx + by_cases hbsize : basis.size = 0 + · have hbempty : basis = #[] := + Array.eq_empty_of_size_eq_zero hbsize + rw [hbempty] at hx + have hxempty := eq_empty_of_mem_rowSpan_empty hx + rw [hxempty] at hxnz + exact absurd rowIsZero_empty hxnz + · have hbpos : 0 < basis.size := Nat.pos_of_ne_zero hbsize + have hwb : MatrixWidth basis = n := + matrixWidth_eq_of_first_row hbpos hbsz + have hb0mem : basis[0] ∈ MatrixRows basis := by + rw [MatrixRows] + exact Array.getElem_mem_toList hbpos + have hb0nz : ¬ RowIsZero basis[0] := hbasisnz basis[0] hb0mem + have hb0span : basis[0] ∈ RowSpan basis₁ := + hstep basis[0] (mem_matrixRows_push_left hb0mem) hb0nz + have hb₁pos : 0 < basis₁.size := by + rcases Nat.eq_zero_or_pos basis₁.size with h0 | hpos' + · have hb₁empty : basis₁ = #[] := + Array.eq_empty_of_size_eq_zero h0 + rw [hb₁empty] at hb0span + have h0eq := eq_empty_of_mem_rowSpan_empty hb0span + rw [h0eq] at hb0nz + exact absurd rowIsZero_empty hb0nz + · exact hpos' + have hwb₁ : MatrixWidth basis₁ = n := + matrixWidth_eq_of_first_row hb₁pos hb₁sz + rcases hx with ⟨coeffs, _, rfl⟩ + refine rowLinearCombination_mem_rowSpan_of_rows_mem + (wellFormed_of_sizes hb₁sz) (hwb.trans hwb₁.symm) ?_ coeffs + intro r hr + exact hstep r (mem_matrixRows_push_left hr) (hbasisnz r hr) + constructor + · intro x hxnz hx + exact hIH.1 x hxnz (hA x hxnz hx) + · intro r hr hrnz + rcases List.mem_cons.mp hr with heq | hrtail + · subst heq + exact hIH.1 r hrnz (hstep r mem_matrixRows_push_self hrnz) + · exact hIH.2 r hrtail hrnz + +/-- The incremental kernel-leaf reduction preserves the generated row module: +every nonzero source row stays inside the row span of the reduced basis. -/ +theorem reduceKernelLeafRowsIncremental_rowSpan_superset [DecidableEq F] + {rows : PolynomialMatrix F} {shift : Array Nat} {n : Nat} + (hsizes : ∀ r ∈ MatrixRows rows, r.size = n) + {row : PolynomialRow F} (hrow : row ∈ MatrixRows rows) + (hnz : ¬ RowIsZero row) : + row ∈ RowSpan (reduceKernelLeafRowsIncremental rows shift) := by + rw [reduceKernelLeafRowsIncremental, ← Array.foldl_toList] + refine (insertIncremental_foldl_rowSpan (n := n) rows.toList #[] shift + (fun r hr ↦ hsizes r hr) ?_ ?_).2 row hrow hnz + · intro r hr + simp [MatrixRows] at hr + · intro r hr + simp [MatrixRows] at hr + +end Approximant + +end PolynomialMatrix + +end CompPoly diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/Recursion.lean b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/Recursion.lean new file mode 100644 index 00000000..46cea01d --- /dev/null +++ b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/Recursion.lean @@ -0,0 +1,174 @@ +/- +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.LinearAlgebra.PolynomialMatrix.Approximant.PMBasis.KernelLeaf + +/-! +# Recursive PM-Basis Driver + +Runtime data for recursive PM-basis computation, the fuel-bounded +divide-and-conquer driver, root normalization, the `pmBasis` entry point, and +the `PMBasisContext` contract structure. +-/ + +namespace CompPoly + +namespace PolynomialMatrix + +namespace Approximant + +variable {F : Type*} [Field F] [BEq F] [LawfulBEq F] + +/-- Runtime data for recursive PM-basis computation. -/ +structure PMBasisRuntime (F : Type*) [Field F] [BEq F] [LawfulBEq F] where + mulContext : CPolynomial.MulContext F + lowMulContext : PolynomialMatrix.MulLowContext F + composeBasis : PolynomialMatrix F → PolynomialMatrix F → PolynomialMatrix F + residualProduct : + Array Nat → PolynomialMatrix F → PolynomialMatrix F → PolynomialMatrix F + leafCutoff : Nat + leafBasis : XAdicProblem F → Array Nat → PolynomialMatrix F + +/-- Recursive PM-basis runtime using the scalar dense-kernel routine as its +small-leaf solver and an independently tuned basis-composition cutoff. -/ +def kernelLeafRuntimeWithLowAndCompose (mulCtx : CPolynomial.MulContext F) + (lowCtx : PolynomialMatrix.MulLowContext F) + (leafCutoff composeLeafCutoff : Nat) : + PMBasisRuntime F where + mulContext := mulCtx + lowMulContext := lowCtx + composeBasis := PolynomialMatrix.mulStrassenWith lowCtx composeLeafCutoff + residualProduct := PolynomialMatrix.mulTruncColumnStrassenWith lowCtx + composeLeafCutoff + leafCutoff := leafCutoff + leafBasis := kernelLeafBasis + +/-- Recursive PM-basis runtime using the scalar dense-kernel routine as its +small-leaf solver. -/ +def kernelLeafRuntimeWithLow (mulCtx : CPolynomial.MulContext F) + (lowCtx : PolynomialMatrix.MulLowContext F) (leafCutoff : Nat) : + PMBasisRuntime F := + kernelLeafRuntimeWithLowAndCompose mulCtx lowCtx leafCutoff leafCutoff + +/-- Compatibility runtime whose low products are obtained by truncating full +products. -/ +def kernelLeafRuntime (mulCtx : CPolynomial.MulContext F) (leafCutoff : Nat) : + PMBasisRuntime F := + kernelLeafRuntimeWithLow mulCtx (PolynomialMatrix.MulLowContext.fromMulContext mulCtx) + leafCutoff + +/-- Fuel-bounded recursive PM-basis driver, compacting zero rows after each +leaf and composition step. + +Internal nodes return the composed product `P₂ * P₁` without re-reduction: +composition of minimal half-bases under the updated shift is itself a basis of +the full-order approximant module, so re-reducing at every node would only add +work that is quadratic in the row degrees and outside the PM-basis cost model. +A single weak-Popov normalization pass runs once at the root entry points. -/ +def pmBasisWithFuelCore (runtime : PMBasisRuntime F) : + Nat → XAdicProblem F → Array Nat → PolynomialMatrix F + | 0, problem, shift => compactNonzeroRows (runtime.leafBasis problem shift) + | fuel + 1, problem, shift => + let order := maxOrder problem + if order ≤ runtime.leafCutoff || order ≤ 1 then + compactNonzeroRows (runtime.leafBasis problem shift) + else + let d₁ := order / 2 + let lower : XAdicProblem F := + { orders := lowerOrders problem d₁, matrix := problem.matrix } + let P₁ := pmBasisWithFuelCore runtime fuel lower shift + let residualOrders := residualOrders problem d₁ + let residual : XAdicProblem F := + { orders := residualOrders + matrix := residualMatrixWithProduct runtime.residualProduct P₁ + problem.matrix d₁ residualOrders } + let shifted := updateShiftByRows P₁ shift + let P₂ := pmBasisWithFuelCore runtime fuel residual shifted + compactNonzeroRows (runtime.composeBasis P₂ P₁) + +/-- Root normalization for a recursively composed approximant basis: one +weak-Popov reduction pass plus completion rows for any leading position that +lost its representative. When the recursion preserved minimality this pass +performs no cascading cancellations; it is a semantic guard, not part of the +recursive cost model. -/ +def pmBasisNormalizeRoot (problem : XAdicProblem F) (shift : Array Nat) + (basis : PolynomialMatrix F) : PolynomialMatrix F := + completeMissingPivotRows problem shift + (compactNonzeroRows (reduceKernelLeafRows basis shift)) + +/-- Fuel-bounded recursive PM-basis driver with root normalization. -/ +def pmBasisWithFuel (runtime : PMBasisRuntime F) + (fuel : Nat) (problem : XAdicProblem F) (shift : Array Nat) : + PolynomialMatrix F := + pmBasisNormalizeRoot problem shift (pmBasisWithFuelCore runtime fuel problem shift) + +/-- Default fuel choice, large enough to split each positive order down to the +leaf cutoff. -/ +def pmBasisFuel [Zero F] (problem : XAdicProblem F) : Nat := + maxOrder problem + 1 + +/-- Recursive PM-basis entry point. -/ +def pmBasis (runtime : PMBasisRuntime F) + (problem : XAdicProblem F) (shift : Array Nat) : PolynomialMatrix F := + pmBasisWithFuel runtime (pmBasisFuel problem) problem shift + +/-- Monomial completion never returns an empty matrix for problems with at +least one module row: an empty candidate set is completed with one monomial +row per module coordinate. -/ +theorem completeMissingPivotRows_size_pos + (problem : XAdicProblem F) (shift : Array Nat) (rows : PolynomialMatrix F) + (hsize : 0 < problem.matrix.size) : + 0 < (completeMissingPivotRows problem shift rows).size := by + rw [completeMissingPivotRows, Array.size_append] + by_cases hrows : rows.size = 0 + · have hempty : rows = #[] := Array.eq_empty_of_size_eq_zero hrows + subst hempty + have hmissing : (missingCompletionRows problem shift (#[] : PolynomialMatrix F)).size = + problem.matrix.size := by + simp [missingCompletionRows, rowsContainLeadingPosition] + omega + · omega + +/-- The root-normalized recursive PM-basis is nonempty for problems with at +least one module row. -/ +theorem pmBasisNormalizeRoot_size_pos + (problem : XAdicProblem F) (shift : Array Nat) (basis : PolynomialMatrix F) + (hsize : 0 < problem.matrix.size) : + 0 < (pmBasisNormalizeRoot problem shift basis).size := by + rw [pmBasisNormalizeRoot] + exact completeMissingPivotRows_size_pos problem shift _ hsize + +/-- Context packaging the executable PM-basis operation with theorem fields. -/ +structure PMBasisContext (F : Type*) [Field F] [BEq F] [LawfulBEq F] where + runtime : PMBasisRuntime F + basis : XAdicProblem F → Array Nat → PolynomialMatrix F := pmBasis runtime + sound : + ∀ problem shift row, + row ∈ MatrixRows (basis problem shift) → + ∀ j, j < problem.orders.size → + truncateX (problem.orders.getD j 0) + (rowGet (rowMulMatrixWith runtime.mulContext row problem.matrix) j) = 0 + complete_minimal : + ∀ problem shift row, + 0 < problem.matrix.size → + WellFormed problem.matrix → + (∀ j, j < problem.orders.size → + truncateX (problem.orders.getD j 0) + (rowGet (rowMulMatrixWith runtime.mulContext row problem.matrix) j) = 0) → + rowIsZero row = false → + row.size ≤ problem.matrix.size → + ∃ basisRow degree, + basisRow ∈ MatrixRows (basis problem shift) ∧ + basisRow.size ≤ problem.matrix.size ∧ + rowShiftedDegree? basisRow shift = some degree ∧ + ∀ rowDegree, rowShiftedDegree? row shift = some rowDegree → + degree ≤ rowDegree + +end Approximant + +end PolynomialMatrix + +end CompPoly diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/XAdicSoundness.lean b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/XAdicSoundness.lean new file mode 100644 index 00000000..0467a220 --- /dev/null +++ b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/XAdicSoundness.lean @@ -0,0 +1,787 @@ +/- +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.LinearAlgebra.PolynomialMatrix.Approximant.PMBasis.KernelLeaf +import CompPoly.LinearAlgebra.PolynomialMatrix.StrassenCorrectness +import Mathlib.Algebra.BigOperators.Group.Finset.Basic + +/-! +# X-Adic Row Soundness Toolkit + +The `RowApproximates` predicate, its divisibility characterization, closure +under the row operations used by the leaf reduction and completion steps, and +the soundness of one divide-and-conquer composition step. +-/ + +namespace CompPoly + +namespace PolynomialMatrix + +namespace Approximant + +variable {F : Type*} [Field F] [BEq F] [LawfulBEq F] + +/-! ## X-adic row soundness toolkit + +Semantic tools for proving that every row produced by the recursive PM-basis +driver satisfies the X-adic approximant conditions. Soundness is phrased via +`X^order` divisibility of the `toPoly` image, which makes it closed under the +row operations used by the reduction, completion, and composition steps. -/ + +/-- A row approximates an X-adic problem when every column product vanishes to +the required order. -/ +def RowApproximates (mulCtx : CPolynomial.MulContext F) + (problem : XAdicProblem F) (row : PolynomialRow F) : Prop := + ∀ j, j < problem.orders.size → + truncateX (problem.orders.getD j 0) + (rowGet (rowMulMatrixWith mulCtx row problem.matrix) j) = 0 + +/-- Truncation vanishes exactly on `X^order`-multiples under `toPoly`. -/ +theorem truncateX_eq_zero_iff_X_pow_dvd (order : Nat) (p : CPolynomial F) : + truncateX order p = 0 ↔ (Polynomial.X : Polynomial F) ^ order ∣ p.toPoly := by + rw [Polynomial.X_pow_dvd_iff] + constructor + · intro h t ht + have hcoeff := congrArg (fun q ↦ CPolynomial.coeff q t) h + simp only [truncateX_coeff, if_pos ht, CPolynomial.coeff_zero] at hcoeff + rw [← CPolynomial.coeff_toPoly] + exact hcoeff + · intro h + apply CPolynomial.eq_iff_coeff.2 + intro t + rw [truncateX_coeff, CPolynomial.coeff_zero] + split + · rename_i ht + rw [CPolynomial.coeff_toPoly] + exact h t ht + · rfl + +/-- Truncation of the zero polynomial is zero. -/ +theorem truncateX_zero_eq_zero (order : Nat) : + truncateX (F := F) order 0 = 0 := by + rw [truncateX_eq_zero_iff_X_pow_dvd, CPolynomial.toPoly_zero] + exact dvd_zero _ + +private theorem pm_foldl_add_eq_sum {M : Type*} [AddCommMonoid M] (f : Nat → M) : + ∀ n : Nat, + (List.range n).foldl (fun acc k ↦ acc + f k) 0 = ∑ k ∈ Finset.range n, f k := by + intro n + induction n with + | zero => simp + | succ n ih => + rw [List.range_succ, List.foldl_append, ih, List.foldl_cons, List.foldl_nil, + Finset.sum_range_succ] + +/-- `toPoly` commutes with finite range sums. -/ +theorem pm_toPoly_finset_sum (f : Nat → CPolynomial F) (n : Nat) : + (∑ k ∈ Finset.range n, f k).toPoly = ∑ k ∈ Finset.range n, (f k).toPoly := by + induction n with + | zero => + simp [CPolynomial.toPoly_zero] + | succ n ih => + rw [Finset.sum_range_succ, Finset.sum_range_succ, CPolynomial.toPoly_add, ih] + +private theorem rowMulMatrixWith_size (mulCtx : CPolynomial.MulContext F) + (row : PolynomialRow F) (M : PolynomialMatrix F) : + (rowMulMatrixWith mulCtx row M).size = MatrixWidth M := by + simp [rowMulMatrixWith] + +private theorem rowGet_rowMulMatrixWith_of_width_le (mulCtx : CPolynomial.MulContext F) + (row : PolynomialRow F) (M : PolynomialMatrix F) {j : Nat} + (hj : MatrixWidth M ≤ j) : + rowGet (rowMulMatrixWith mulCtx row M) j = 0 := by + rw [rowGet, Array.getD_eq_getD_getElem?, Array.getElem?_eq_none + (by rw [rowMulMatrixWith_size]; omega)] + rfl + +/-- Column entries of a row-by-matrix product as sums over the matrix height, +under `toPoly`. -/ +theorem rowGet_rowMulMatrixWith_toPoly (mulCtx : CPolynomial.MulContext F) + (row : PolynomialRow F) (M : PolynomialMatrix F) {j : Nat} + (hj : j < MatrixWidth M) : + (rowGet (rowMulMatrixWith mulCtx row M) j).toPoly = + ∑ k ∈ Finset.range M.size, + (rowGet row k).toPoly * (rowGet (M.getD k #[]) j).toPoly := by + rw [rowMulMatrixWith, rowGet, Array.getD_eq_getD_getElem?, List.getElem?_toArray, + List.getElem?_map, List.getElem?_range hj, Option.map_some, Option.getD_some] + rw [pm_foldl_add_eq_sum (fun k ↦ mulCtx.mul (rowGet row k) (rowGet (M.getD k #[]) j))] + have hsum : ∀ n : Nat, + (∑ k ∈ Finset.range n, + mulCtx.mul (rowGet row k) (rowGet (M.getD k #[]) j)).toPoly = + ∑ k ∈ Finset.range n, + (rowGet row k).toPoly * (rowGet (M.getD k #[]) j).toPoly := by + intro n + rw [pm_toPoly_finset_sum] + refine Finset.sum_congr rfl fun k _hk ↦ ?_ + rw [mulCtx.mul_eq_mul, CPolynomial.toPoly_mul] + rw [hsum] + rcases Nat.le_total row.size M.size with h | h + · refine Finset.sum_subset + (by intro x hx; simp only [Finset.mem_range] at hx ⊢; omega) + fun k _hk hknot ↦ ?_ + have hk : row.size ≤ k := by simpa using hknot + have hzero : rowGet row k = 0 := by + rw [rowGet, Array.getD_eq_getD_getElem?, Array.getElem?_eq_none hk] + rfl + rw [hzero, CPolynomial.toPoly_zero, zero_mul] + · symm + refine Finset.sum_subset + (by intro x hx; simp only [Finset.mem_range] at hx ⊢; omega) + fun k _hk hknot ↦ ?_ + have hk : M.size ≤ k := by simpa using hknot + have hzero : M.getD k #[] = #[] := by + rw [Array.getD_eq_getD_getElem?, Array.getElem?_eq_none hk] + rfl + rw [hzero, show rowGet (#[] : PolynomialRow F) j = 0 from rfl, + CPolynomial.toPoly_zero, mul_zero] + +/-- Divisibility form of the approximant condition, one column at a time. -/ +theorem rowApproximates_iff (mulCtx : CPolynomial.MulContext F) + (problem : XAdicProblem F) (row : PolynomialRow F) : + RowApproximates mulCtx problem row ↔ + ∀ j, j < problem.orders.size → j < MatrixWidth problem.matrix → + (Polynomial.X : Polynomial F) ^ (problem.orders.getD j 0) ∣ + ∑ k ∈ Finset.range problem.matrix.size, + (rowGet row k).toPoly * + (rowGet (problem.matrix.getD k #[]) j).toPoly := by + constructor + · intro h j hj hjw + have := h j hj + rw [truncateX_eq_zero_iff_X_pow_dvd, + rowGet_rowMulMatrixWith_toPoly mulCtx row problem.matrix hjw] at this + exact this + · intro h j hj + rcases Nat.lt_or_ge j (MatrixWidth problem.matrix) with hjw | hjw + · rw [truncateX_eq_zero_iff_X_pow_dvd, + rowGet_rowMulMatrixWith_toPoly mulCtx row problem.matrix hjw] + exact h j hj hjw + · rw [rowGet_rowMulMatrixWith_of_width_le mulCtx row problem.matrix hjw] + exact truncateX_zero_eq_zero _ + +/-- Row subtraction preserves the approximant condition. -/ +theorem rowApproximates_rowSub (mulCtx : CPolynomial.MulContext F) + (problem : XAdicProblem F) {a b : PolynomialRow F} + (ha : RowApproximates mulCtx problem a) + (hb : RowApproximates mulCtx problem b) : + RowApproximates mulCtx problem (rowSub a b) := by + rw [rowApproximates_iff] at ha hb ⊢ + intro j hj hjw + have hsum : ∑ k ∈ Finset.range problem.matrix.size, + (rowGet (rowSub a b) k).toPoly * + (rowGet (problem.matrix.getD k #[]) j).toPoly = + (∑ k ∈ Finset.range problem.matrix.size, + (rowGet a k).toPoly * (rowGet (problem.matrix.getD k #[]) j).toPoly) - + ∑ k ∈ Finset.range problem.matrix.size, + (rowGet b k).toPoly * (rowGet (problem.matrix.getD k #[]) j).toPoly := by + rw [← Finset.sum_sub_distrib] + refine Finset.sum_congr rfl fun k _hk ↦ ?_ + rw [rowGet_rowSub, CPolynomial.toPoly_sub, sub_mul] + rw [hsum] + exact dvd_sub (ha j hj hjw) (hb j hj hjw) + +private theorem polynomialScaleCoeffX_zero (c : F) (d : Nat) : + polynomialScaleCoeffX c d (0 : CPolynomial F) = 0 := by + rw [polynomialScaleCoeffX] + split + · rfl + · rw [if_pos (by simp)] + +/-- Coefficient-shift scaling under `toPoly`. -/ +theorem polynomialScaleCoeffX_toPoly (c : F) (d : Nat) (p : CPolynomial F) : + (polynomialScaleCoeffX c d p).toPoly = + Polynomial.C c * Polynomial.X ^ d * p.toPoly := by + rw [polynomialScaleCoeffX] + by_cases hc : c == 0 + · rw [if_pos hc] + have hc' : c = 0 := by simpa using hc + rw [hc', CPolynomial.toPoly_zero] + simp + · rw [if_neg hc] + by_cases hp : p == 0 + · rw [if_pos hp] + have hp' : p = 0 := by simpa using hp + rw [hp', CPolynomial.toPoly_zero] + simp + · rw [if_neg hp] + apply Polynomial.ext + intro t + rw [← CPolynomial.coeff_toPoly, CPolynomial.coeff_ofArray] + have hrhs : (Polynomial.C c * Polynomial.X ^ d * p.toPoly).coeff t = + if d ≤ t then c * (p.toPoly.coeff (t - d)) else 0 := by + rw [mul_assoc, Polynomial.coeff_C_mul, Polynomial.X_pow_mul, + Polynomial.coeff_mul_X_pow'] + split + · rfl + · rw [mul_zero] + rw [hrhs] + rcases Nat.lt_or_ge t d with htd | htd + · rw [if_neg (by omega)] + rw [Array.getD_eq_getD_getElem?, List.getElem?_toArray, + List.getElem?_append_left (by simpa using htd), + List.getElem?_replicate_of_lt htd] + rfl + · rw [if_pos htd] + rw [Array.getD_eq_getD_getElem?, List.getElem?_toArray, + List.getElem?_append_right (by simpa using htd)] + rw [List.length_replicate] + rw [List.getElem?_map] + rw [← CPolynomial.coeff_toPoly] + rcases Nat.lt_or_ge (t - d) p.val.toList.length with hin | hin + · rw [List.getElem?_eq_getElem hin, Option.map_some, Option.getD_some] + congr 1 + rw [show CPolynomial.coeff p (t - d) = p.val.coeff (t - d) from rfl] + rw [CPolynomial.Raw.coeff] + rw [Array.getD_eq_getD_getElem?, Array.getElem?_eq_getElem + (by simpa using hin), Option.getD_some] + simp [Array.getElem_toList] + · rw [List.getElem?_eq_none hin, Option.map_none, Option.getD_none] + rw [show CPolynomial.coeff p (t - d) = p.val.coeff (t - d) from rfl] + rw [CPolynomial.Raw.coeff, Array.getD_eq_getD_getElem?, + Array.getElem?_eq_none (by simpa using hin)] + simp + +private theorem rowGet_rowScaleCoeffX (c : F) (d : Nat) (row : PolynomialRow F) + (k : Nat) : + rowGet (rowScaleCoeffX c d row) k = polynomialScaleCoeffX c d (rowGet row k) := by + rcases Nat.lt_or_ge k row.size with hk | hk + · rw [rowScaleCoeffX, rowGet, Array.getD_eq_getD_getElem?, + Array.getElem?_eq_getElem (by simpa using hk), Option.getD_some, + Array.getElem_map] + rw [rowGet, Array.getD_eq_getD_getElem?, Array.getElem?_eq_getElem hk, + Option.getD_some] + · rw [rowScaleCoeffX, rowGet, Array.getD_eq_getD_getElem?, + Array.getElem?_eq_none (by simpa using hk)] + rw [rowGet, Array.getD_eq_getD_getElem?, Array.getElem?_eq_none hk] + rw [show ((none : Option (CPolynomial F)).getD 0) = 0 from rfl, + polynomialScaleCoeffX_zero] + +/-- Coefficient-shift row scaling preserves the approximant condition. -/ +theorem rowApproximates_rowScaleCoeffX (mulCtx : CPolynomial.MulContext F) + (problem : XAdicProblem F) {row : PolynomialRow F} (c : F) (d : Nat) + (h : RowApproximates mulCtx problem row) : + RowApproximates mulCtx problem (rowScaleCoeffX c d row) := by + rw [rowApproximates_iff] at h ⊢ + intro j hj hjw + have hsum : ∑ k ∈ Finset.range problem.matrix.size, + (rowGet (rowScaleCoeffX c d row) k).toPoly * + (rowGet (problem.matrix.getD k #[]) j).toPoly = + Polynomial.C c * Polynomial.X ^ d * + ∑ k ∈ Finset.range problem.matrix.size, + (rowGet row k).toPoly * (rowGet (problem.matrix.getD k #[]) j).toPoly := by + rw [Finset.mul_sum] + refine Finset.sum_congr rfl fun k _hk ↦ ?_ + rw [rowGet_rowScaleCoeffX, polynomialScaleCoeffX_toPoly, mul_assoc, mul_assoc] + rw [hsum] + exact Dvd.dvd.mul_left (h j hj hjw) _ + +/-- Leading-term cancellation preserves the approximant condition. -/ +theorem rowApproximates_cancelKernelLeafLeadingTerm + (mulCtx : CPolynomial.MulContext F) (problem : XAdicProblem F) + {target reducer : PolynomialRow F} (shift : Array Nat) + (htarget : RowApproximates mulCtx problem target) + (hreducer : RowApproximates mulCtx problem reducer) : + RowApproximates mulCtx problem + (cancelKernelLeafLeadingTerm target reducer shift) := by + rw [cancelKernelLeafLeadingTerm] + split + · split + · split + · exact htarget + · exact rowApproximates_rowSub mulCtx problem htarget + (rowApproximates_rowScaleCoeffX mulCtx problem _ _ hreducer) + · exact htarget + · exact htarget + +/-- Pivot-table insertion preserves the approximant condition of all stored +rows. -/ +theorem insertKernelLeafPivotRowWithFuel_approximates + (mulCtx : CPolynomial.MulContext F) (problem : XAdicProblem F) : + ∀ (fuel : Nat) (pivots : Array (Option (PolynomialRow F))) + (shift : Array Nat) (row : PolynomialRow F), + (∀ p r, pivots.getD p none = some r → RowApproximates mulCtx problem r) → + RowApproximates mulCtx problem row → + ∀ p r, + (insertKernelLeafPivotRowWithFuel fuel pivots shift row).getD p none = + some r → + RowApproximates mulCtx problem r := by + intro fuel + induction fuel with + | zero => + intro pivots shift row hpivots _hrow p r hget + exact hpivots p r hget + | succ fuel ih => + intro pivots shift row hpivots hrow p r hget + rw [insertKernelLeafPivotRowWithFuel] at hget + have hset : ∀ (position : Nat) (newRow : PolynomialRow F), + RowApproximates mulCtx problem newRow → + ∀ p' r', + (pivots.setIfInBounds position (some newRow)).getD p' none = some r' → + RowApproximates mulCtx problem r' := by + intro position newRow hnew p' r' hget' + by_cases hpos : p' = position ∧ position < pivots.size + · rcases hpos with ⟨hp, hlt⟩ + subst hp + rw [Array.getD_eq_getD_getElem?, + Array.getElem?_setIfInBounds_self_of_lt hlt, Option.getD_some] at hget' + cases hget' + exact hnew + · rcases Nat.lt_or_ge p' pivots.size with hplt | hpge + · have hne : p' ≠ position := by + intro hcontra + exact hpos ⟨hcontra, hcontra ▸ hplt⟩ + rw [Array.getD_eq_getD_getElem?, + Array.getElem?_setIfInBounds_ne (by omega)] at hget' + exact hpivots p' r' (by rw [Array.getD_eq_getD_getElem?]; exact hget') + · rw [Array.getD_eq_getD_getElem?, Array.getElem?_eq_none + (by simpa using hpge)] at hget' + cases hget' + cases hterm : rowShiftedLeadingTerm? row shift with + | none => + simp only [hterm] at hget + exact hpivots p r hget + | some target => + simp only [hterm] at hget + cases hpivot : pivots.getD target.position none with + | none => + simp only [hpivot] at hget + exact hset target.position row hrow p r hget + | some pivot => + simp only [hpivot] at hget + cases hpterm : rowShiftedLeadingTerm? pivot shift with + | none => + simp only [hpterm] at hget + exact hset target.position row hrow p r hget + | some reducer => + simp only [hpterm] at hget + have hpivotrow : RowApproximates mulCtx problem pivot := + hpivots target.position pivot hpivot + split at hget + · refine ih (pivots.setIfInBounds target.position (some row)) shift + (cancelKernelLeafLeadingTerm pivot row shift) ?_ ?_ p r hget + · intro p' r' hget' + exact hset target.position row hrow p' r' hget' + · exact rowApproximates_cancelKernelLeafLeadingTerm mulCtx problem + shift hpivotrow hrow + · exact ih pivots shift (cancelKernelLeafLeadingTerm row pivot shift) + hpivots + (rowApproximates_cancelKernelLeafLeadingTerm mulCtx problem + shift hrow hpivotrow) + p r hget + +/-- Rows extracted from a sound pivot table satisfy the approximant +condition. -/ +theorem pivotRows_approximates (mulCtx : CPolynomial.MulContext F) + (problem : XAdicProblem F) {pivots : Array (Option (PolynomialRow F))} + (hpivots : ∀ p r, pivots.getD p none = some r → + RowApproximates mulCtx problem r) + {row : PolynomialRow F} (hrow : row ∈ MatrixRows (pivotRows pivots)) : + RowApproximates mulCtx problem row := by + rw [MatrixRows, pivotRows] at hrow + rw [List.toList_toArray] at hrow + rcases List.mem_filterMap.mp hrow with ⟨entry, hentry, hid⟩ + rcases List.getElem_of_mem hentry with ⟨p, hp, hget⟩ + refine hpivots p row ?_ + rw [Array.getD_eq_getD_getElem?, Array.getElem?_eq_getElem (by simpa using hp), + Option.getD_some] + rw [show pivots[p] = pivots.toList[p] from by rw [Array.getElem_toList]] + rw [hget] + exact hid + +/-- Pivot-table reduction preserves the approximant condition. -/ +theorem reduceKernelLeafRowsByPivots_approximates + (mulCtx : CPolynomial.MulContext F) (problem : XAdicProblem F) + {rows : PolynomialMatrix F} (shift : Array Nat) + (hrows : ∀ row, row ∈ MatrixRows rows → RowApproximates mulCtx problem row) + {row : PolynomialRow F} + (hrow : row ∈ MatrixRows (reduceKernelLeafRowsByPivots rows shift)) : + RowApproximates mulCtx problem row := by + rw [reduceKernelLeafRowsByPivots] at hrow + refine pivotRows_approximates mulCtx problem ?_ hrow + have hfold : ∀ (l : List (PolynomialRow F)) + (pivots : Array (Option (PolynomialRow F))), + (∀ r, r ∈ l → RowApproximates mulCtx problem r) → + (∀ p r, pivots.getD p none = some r → RowApproximates mulCtx problem r) → + ∀ p r, + (l.foldl (fun pivots row ↦ + insertKernelLeafPivotRowWithFuel (reduceKernelLeafFuel rows shift) + pivots shift row) pivots).getD p none = some r → + RowApproximates mulCtx problem r := by + intro l + induction l with + | nil => + intro pivots _hl hpivots p r hget + exact hpivots p r hget + | cons head tail ih => + intro pivots hl hpivots p r hget + rw [List.foldl_cons] at hget + refine ih _ (fun r hr ↦ hl r (List.mem_cons_of_mem head hr)) ?_ p r hget + intro p' r' hget' + exact insertKernelLeafPivotRowWithFuel_approximates mulCtx problem + (reduceKernelLeafFuel rows shift) pivots shift head hpivots + (hl head List.mem_cons_self) p' r' hget' + rw [← Array.foldl_toList] + refine hfold rows.toList _ (fun r hr ↦ hrows r hr) ?_ + intro p r hget + rw [Array.getD_eq_getD_getElem?] at hget + rcases Nat.lt_or_ge p (Array.replicate (MatrixWidth rows) + (none : Option (PolynomialRow F))).size with hp | hp + · rw [Array.getElem?_eq_getElem hp, Option.getD_some] at hget + rw [Array.getElem_replicate] at hget + cases hget + · rw [Array.getElem?_eq_none hp] at hget + cases hget + +omit [LawfulBEq F] in +/-- Compaction preserves row membership soundness. -/ +theorem compactNonzeroRows_subset {rows : PolynomialMatrix F} + {row : PolynomialRow F} (hrow : row ∈ MatrixRows (compactNonzeroRows rows)) : + row ∈ MatrixRows rows := by + rw [MatrixRows, compactNonzeroRows] at hrow + rw [MatrixRows] + have hmem : row ∈ rows ∧ rowIsZero row = false := by simpa using hrow + simpa using hmem.1 + +omit [LawfulBEq F] in +/-- Compacted rows are nonzero. -/ +theorem compactNonzeroRows_nonzero {rows : PolynomialMatrix F} + {row : PolynomialRow F} (hrow : row ∈ MatrixRows (compactNonzeroRows rows)) : + rowIsZero row = false := by + rw [MatrixRows, compactNonzeroRows] at hrow + have hmem : row ∈ rows ∧ rowIsZero row = false := by simpa using hrow + exact hmem.2 + +/-- The monomial `coeffXPower c d` under `toPoly`. -/ +theorem coeffXPower_toPoly (c : F) (d : Nat) : + (coeffXPower c d).toPoly = Polynomial.C c * Polynomial.X ^ d := by + apply Polynomial.ext + intro t + rw [← CPolynomial.coeff_toPoly, coeffXPower, CPolynomial.coeff_ofArray] + rw [Polynomial.coeff_C_mul, Polynomial.coeff_X_pow] + rcases Nat.lt_trichotomy t d with ht | ht | ht + · rw [Array.getD_eq_getD_getElem?, Array.getElem?_push_lt + (by simpa using ht), Option.getD_some, Array.getElem_replicate, + if_neg (by omega), mul_zero] + · subst ht + rw [Array.getD_eq_getD_getElem?] + rw [show ((Array.replicate t (0 : F)).push c)[t]? = some c from by + rw [Array.getElem?_push] + simp] + simp + · rw [Array.getD_eq_getD_getElem?, Array.getElem?_eq_none (by simp; omega)] + simp [Nat.ne_of_gt ht] + +/-- Monomial unit rows of sufficiently high degree satisfy every X-adic +condition. -/ +theorem rowApproximates_monomialUnitRow (mulCtx : CPolynomial.MulContext F) + (problem : XAdicProblem F) {i d : Nat} + (hd : ∀ j, j < problem.orders.size → problem.orders.getD j 0 ≤ d) : + RowApproximates mulCtx problem + (monomialUnitRow problem.matrix.size i d) := by + rw [rowApproximates_iff] + intro j hj hjw + refine Finset.dvd_sum fun k _hk ↦ ?_ + rcases Nat.lt_or_ge k problem.matrix.size with hk | hk + · have hentry : rowGet (monomialUnitRow (F := F) problem.matrix.size i d) k = + if i == k then coeffXPower 1 d else 0 := by + rw [monomialUnitRow, rowGet, Array.getD_eq_getD_getElem?, + List.getElem?_toArray, List.getElem?_map, List.getElem?_range hk, + Option.map_some, Option.getD_some] + rw [hentry] + by_cases hik : i == k + · rw [if_pos hik, coeffXPower_toPoly] + calc (Polynomial.X : Polynomial F) ^ (problem.orders.getD j 0) + ∣ (Polynomial.X : Polynomial F) ^ d := pow_dvd_pow _ (hd j hj) + _ ∣ Polynomial.C (1 : F) * Polynomial.X ^ d * + (rowGet (problem.matrix.getD k #[]) j).toPoly := + Dvd.dvd.mul_right (Dvd.dvd.mul_left dvd_rfl _) _ + · rw [if_neg hik, CPolynomial.toPoly_zero, zero_mul] + exact dvd_zero _ + · have hentry : rowGet (monomialUnitRow (F := F) problem.matrix.size i d) k = + 0 := by + rw [monomialUnitRow, rowGet, Array.getD_eq_getD_getElem?, + List.getElem?_toArray, List.getElem?_eq_none (by simpa using hk), + Option.getD_none] + rw [hentry, CPolynomial.toPoly_zero, zero_mul] + exact dvd_zero _ + +omit [BEq F] [LawfulBEq F] in +/-- Every entry of the order vector is bounded by `maxOrder`. -/ +theorem getD_le_maxOrder (problem : XAdicProblem F) {j : Nat} + (hj : j < problem.orders.size) : + problem.orders.getD j 0 ≤ maxOrder problem := by + rw [maxOrder] + have hmem : problem.orders.getD j 0 ∈ problem.orders.toList := by + rw [Array.getD_eq_getD_getElem?, Array.getElem?_eq_getElem hj, Option.getD_some] + exact Array.getElem_mem_toList hj + rw [← Array.foldl_toList] + have hgen : ∀ (l : List Nat) (acc : Nat) (x : Nat), x ∈ l → + x ≤ l.foldl max acc := by + intro l + induction l with + | nil => + intro acc x hx + cases hx + | cons head tail ih => + intro acc x hx + rw [List.foldl_cons] + rcases List.mem_cons.mp hx with hx | hx + · subst hx + have hbase : max acc x ≤ tail.foldl max (max acc x) := by + have hmono : ∀ (l : List Nat) (a : Nat), a ≤ l.foldl max a := by + intro l + induction l with + | nil => intro a; exact le_rfl + | cons h t iht => + intro a + rw [List.foldl_cons] + exact le_trans (Nat.le_max_left a h) (iht (max a h)) + exact hmono tail (max acc x) + omega + · exact ih (max acc head) x hx + exact hgen problem.orders.toList 0 _ hmem + +/-- Missing-pivot completion rows satisfy every X-adic condition. -/ +theorem missingCompletionRows_approximates (mulCtx : CPolynomial.MulContext F) + (problem : XAdicProblem F) (shift : Array Nat) (rows : PolynomialMatrix F) + {row : PolynomialRow F} + (hrow : row ∈ MatrixRows (missingCompletionRows problem shift rows)) : + RowApproximates mulCtx problem row := by + rw [MatrixRows, missingCompletionRows] at hrow + rw [List.toList_toArray] at hrow + rcases List.mem_filterMap.mp hrow with ⟨i, _hi, hsome⟩ + split at hsome + · cases hsome + · cases hsome + refine rowApproximates_monomialUnitRow mulCtx problem fun j hj ↦ ?_ + exact le_trans (getD_le_maxOrder problem hj) (Nat.le_max_right 1 _) + +/-- Pivot completion preserves the approximant condition. -/ +theorem completeMissingPivotRows_approximates (mulCtx : CPolynomial.MulContext F) + (problem : XAdicProblem F) (shift : Array Nat) {rows : PolynomialMatrix F} + (hrows : ∀ row, row ∈ MatrixRows rows → RowApproximates mulCtx problem row) + {row : PolynomialRow F} + (hrow : row ∈ MatrixRows (completeMissingPivotRows problem shift rows)) : + RowApproximates mulCtx problem row := by + rw [MatrixRows, completeMissingPivotRows, Array.toList_append] at hrow + rcases List.mem_append.mp hrow with hmem | hmem + · exact hrows row hmem + · exact missingCompletionRows_approximates mulCtx problem shift rows hmem + +/-! ## Composition soundness -/ + +/-- Coefficients of the shifted truncation `divXTrunc`. -/ +theorem divXTrunc_coeff (shift order : Nat) (p : CPolynomial F) (t : Nat) : + CPolynomial.coeff (divXTrunc shift order p) t = + if t < order then CPolynomial.coeff p (t + shift) else 0 := by + rw [divXTrunc, CPolynomial.coeff_ofArray] + rcases Nat.lt_or_ge t order with ht | ht + · rw [if_pos ht, Array.getD_eq_getD_getElem?, List.getElem?_toArray, + List.getElem?_map, List.getElem?_range ht, Option.map_some, Option.getD_some] + · rw [if_neg (by omega), Array.getD_eq_getD_getElem?, List.getElem?_toArray, + List.getElem?_eq_none (by simpa using ht), Option.getD_none] + +/-- The row-by-matrix product does not depend on the multiplication context. -/ +theorem rowMulMatrixWith_ctx (ctx₁ ctx₂ : CPolynomial.MulContext F) + (row : PolynomialRow F) (M : PolynomialMatrix F) : + rowMulMatrixWith ctx₁ row M = rowMulMatrixWith ctx₂ row M := by + rw [rowMulMatrixWith, rowMulMatrixWith] + congr 1 + refine List.map_congr_left fun j _hj ↦ ?_ + refine List.foldl_ext _ _ _ fun acc k _hk ↦ ?_ + rw [ctx₁.mul_eq_mul, ctx₂.mul_eq_mul] + +omit [BEq F] [LawfulBEq F] in +/-- `getD` of a mapped natural-number array at an in-bounds index. -/ +theorem natArray_map_getD (f : Nat → Nat) (a : Array Nat) {j : Nat} + (hj : j < a.size) : + (a.map f).getD j 0 = f (a.getD j 0) := by + rw [Array.getD_eq_getD_getElem?, Array.getD_eq_getD_getElem?, + Array.getElem?_map, Array.getElem?_eq_getElem hj] + rfl + +omit [BEq F] [LawfulBEq F] in +/-- A nonempty matrix with uniform row width `n` has `MatrixWidth` `n`. -/ +theorem matrixWidth_eq_of_first_row {M : PolynomialMatrix F} {n : Nat} + (hne : 0 < M.size) + (hsize : ∀ r ∈ MatrixRows M, r.size = n) : + MatrixWidth M = n := by + rw [MatrixWidth, Array.getElem?_eq_getElem hne] + exact hsize M[0] (by rw [MatrixRows]; exact Array.getElem_mem_toList hne) + +omit [BEq F] [LawfulBEq F] in +/-- In-bounds `getD` rows are matrix rows. -/ +theorem getD_mem_matrixRows {M : PolynomialMatrix F} {l : Nat} + (hl : l < M.size) : M.getD l #[] ∈ MatrixRows M := by + rw [MatrixRows, Array.getD_eq_getD_getElem?, Array.getElem?_eq_getElem hl, + Option.getD_some] + exact Array.getElem_mem_toList hl + +/-- Column entries of a doubly composed row product, when the inner basis has +uniform row width. -/ +private theorem rowGet_composed_toPoly (mulCtx : CPolynomial.MulContext F) + {P₁ M : PolynomialMatrix F} + (hsize : ∀ r ∈ MatrixRows P₁, r.size = M.size) + (p₂ : PolynomialRow F) {j : Nat} (hj : j < MatrixWidth M) : + (rowGet (rowMulMatrixWith mulCtx (rowMulMatrixWith mulCtx p₂ P₁) M) j).toPoly = + ∑ l ∈ Finset.range P₁.size, (rowGet p₂ l).toPoly * + (rowGet (rowMulMatrixWith mulCtx (P₁.getD l #[]) M) j).toPoly := by + rcases Nat.eq_zero_or_pos P₁.size with hP₁ | hP₁ + · rw [rowGet_rowMulMatrixWith_toPoly mulCtx _ M hj, hP₁] + rw [Finset.range_zero, Finset.sum_empty] + refine Finset.sum_eq_zero fun k _hk ↦ ?_ + have hzero : rowGet (rowMulMatrixWith mulCtx p₂ P₁) k = 0 := by + have hwidth : MatrixWidth P₁ = 0 := by + rw [MatrixWidth, Array.getElem?_eq_none (by omega)] + rw [rowGet, Array.getD_eq_getD_getElem?, Array.getElem?_eq_none + (by rw [rowMulMatrixWith_size, hwidth]; omega)] + rfl + rw [hzero, CPolynomial.toPoly_zero, zero_mul] + · have hwidth₁ : MatrixWidth P₁ = M.size := + matrixWidth_eq_of_first_row hP₁ hsize + rw [rowGet_rowMulMatrixWith_toPoly mulCtx _ M hj] + have hentry : ∀ k, k < M.size → + (rowGet (rowMulMatrixWith mulCtx p₂ P₁) k).toPoly = + ∑ l ∈ Finset.range P₁.size, + (rowGet p₂ l).toPoly * (rowGet (P₁.getD l #[]) k).toPoly := by + intro k hk + exact rowGet_rowMulMatrixWith_toPoly mulCtx p₂ P₁ (by omega) + calc ∑ k ∈ Finset.range M.size, + (rowGet (rowMulMatrixWith mulCtx p₂ P₁) k).toPoly * + (rowGet (M.getD k #[]) j).toPoly + = ∑ k ∈ Finset.range M.size, ∑ l ∈ Finset.range P₁.size, + (rowGet p₂ l).toPoly * (rowGet (P₁.getD l #[]) k).toPoly * + (rowGet (M.getD k #[]) j).toPoly := by + refine Finset.sum_congr rfl fun k hk ↦ ?_ + rw [hentry k (Finset.mem_range.mp hk), Finset.sum_mul] + _ = ∑ l ∈ Finset.range P₁.size, ∑ k ∈ Finset.range M.size, + (rowGet p₂ l).toPoly * (rowGet (P₁.getD l #[]) k).toPoly * + (rowGet (M.getD k #[]) j).toPoly := Finset.sum_comm + _ = ∑ l ∈ Finset.range P₁.size, (rowGet p₂ l).toPoly * + (rowGet (rowMulMatrixWith mulCtx (P₁.getD l #[]) M) j).toPoly := by + refine Finset.sum_congr rfl fun l _hl ↦ ?_ + rw [rowGet_rowMulMatrixWith_toPoly mulCtx _ M hj, Finset.mul_sum] + refine Finset.sum_congr rfl fun k _hk ↦ ?_ + ring + +/-- Soundness of one PM-basis composition step: a residual-approximant row +times a lower-approximant basis approximates the full problem. -/ +theorem rowApproximates_composed (mulCtx : CPolynomial.MulContext F) + {problem : XAdicProblem F} {d₁ : Nat} + {P₁ Rmat : PolynomialMatrix F} + (hP₁ : ∀ r ∈ MatrixRows P₁, + RowApproximates mulCtx + { orders := lowerOrders problem d₁, matrix := problem.matrix } r ∧ + r.size = problem.matrix.size) + (hRsize : Rmat.size = P₁.size) + (hRwidth : 0 < P₁.size → MatrixWidth problem.matrix ≤ MatrixWidth Rmat) + (hR : ∀ l j, l < P₁.size → j < problem.orders.size → + j < MatrixWidth problem.matrix → + ∀ t, t < problem.orders.getD j 0 - d₁ → + CPolynomial.coeff (rowGet (Rmat.getD l #[]) j) t = + CPolynomial.coeff + (rowGet (rowMulMatrixWith mulCtx (P₁.getD l #[]) problem.matrix) j) + (t + d₁)) + {p₂ : PolynomialRow F} + (hp₂ : RowApproximates mulCtx + { orders := residualOrders problem d₁, matrix := Rmat } p₂) : + RowApproximates mulCtx problem (rowMulMatrixWith mulCtx p₂ P₁) := by + rw [rowApproximates_iff] + intro j hj hjw + rw [← rowGet_rowMulMatrixWith_toPoly mulCtx _ problem.matrix hjw, + rowGet_composed_toPoly mulCtx (fun r hr ↦ (hP₁ r hr).2) p₂ hjw] + set o := problem.orders.getD j 0 with ho + have hP₁dvd : ∀ l, l < P₁.size → + (Polynomial.X : Polynomial F) ^ (min o d₁) ∣ + (rowGet (rowMulMatrixWith mulCtx (P₁.getD l #[]) + problem.matrix) j).toPoly := by + intro l hl + have hmem := getD_mem_matrixRows hl + have happrox := (hP₁ _ hmem).1 j (by simpa [lowerOrders] using hj) + rw [truncateX_eq_zero_iff_X_pow_dvd] at happrox + have horder : (lowerOrders problem d₁).getD j 0 = min o d₁ := by + rw [lowerOrders, natArray_map_getD _ _ hj] + rwa [horder] at happrox + rcases Nat.le_total o d₁ with hod | hod + · refine Finset.dvd_sum fun l hl ↦ ?_ + have hdvd := hP₁dvd l (Finset.mem_range.mp hl) + rw [Nat.min_eq_left hod] at hdvd + exact Dvd.dvd.mul_left hdvd _ + · rcases Nat.eq_zero_or_pos P₁.size with hP₁0 | hP₁0 + · rw [hP₁0, Finset.range_zero, Finset.sum_empty] + exact dvd_zero _ + have hwidthR : j < MatrixWidth Rmat := by + have := hRwidth hP₁0 + omega + have hquot : ∀ l, ∃ sl : Polynomial F, + l < P₁.size → + (rowGet (rowMulMatrixWith mulCtx (P₁.getD l #[]) + problem.matrix) j).toPoly = Polynomial.X ^ d₁ * sl := by + intro l + by_cases hl : l < P₁.size + · have hdvd := hP₁dvd l hl + rw [Nat.min_eq_right (by omega)] at hdvd + rcases hdvd with ⟨sl, hsl⟩ + exact ⟨sl, fun _ ↦ hsl⟩ + · exact ⟨0, fun h ↦ absurd h hl⟩ + choose s hs using hquot + have hcong : ∀ l, l < P₁.size → + (Polynomial.X : Polynomial F) ^ (o - d₁) ∣ + (rowGet (Rmat.getD l #[]) j).toPoly - s l := by + intro l hl + rw [Polynomial.X_pow_dvd_iff] + intro t ht + rw [Polynomial.coeff_sub, ← CPolynomial.coeff_toPoly, + hR l j hl hj hjw t (by omega)] + rw [CPolynomial.coeff_toPoly] + have hcoeff : ((rowGet (rowMulMatrixWith mulCtx (P₁.getD l #[]) + problem.matrix) j).toPoly).coeff (t + d₁) = + (Polynomial.X ^ d₁ * s l).coeff (t + d₁) := by + rw [hs l hl] + rw [Polynomial.coeff_X_pow_mul] at hcoeff + rw [hcoeff] + exact sub_self _ + have hresidual : (Polynomial.X : Polynomial F) ^ (o - d₁) ∣ + ∑ l ∈ Finset.range P₁.size, + (rowGet p₂ l).toPoly * (rowGet (Rmat.getD l #[]) j).toPoly := by + have hjres : j < (residualOrders problem d₁).size := by + rw [residualOrders] + simpa using hj + have happrox := hp₂ j hjres + rw [truncateX_eq_zero_iff_X_pow_dvd, + rowGet_rowMulMatrixWith_toPoly mulCtx p₂ Rmat hwidthR] at happrox + have horder : (residualOrders problem d₁).getD j 0 = o - d₁ := by + rw [residualOrders, natArray_map_getD _ _ hj] + rw [horder, hRsize] at happrox + exact happrox + have hquotsum : (Polynomial.X : Polynomial F) ^ (o - d₁) ∣ + ∑ l ∈ Finset.range P₁.size, (rowGet p₂ l).toPoly * s l := by + have hdiff : (Polynomial.X : Polynomial F) ^ (o - d₁) ∣ + (∑ l ∈ Finset.range P₁.size, + (rowGet p₂ l).toPoly * (rowGet (Rmat.getD l #[]) j).toPoly) - + ∑ l ∈ Finset.range P₁.size, (rowGet p₂ l).toPoly * s l := by + rw [← Finset.sum_sub_distrib] + refine Finset.dvd_sum fun l hl ↦ ?_ + rw [← mul_sub] + exact Dvd.dvd.mul_left (hcong l (Finset.mem_range.mp hl)) _ + have hsub := dvd_sub hresidual hdiff + simpa using hsub + have hsum : ∑ l ∈ Finset.range P₁.size, (rowGet p₂ l).toPoly * + (rowGet (rowMulMatrixWith mulCtx (P₁.getD l #[]) + problem.matrix) j).toPoly = + Polynomial.X ^ d₁ * + ∑ l ∈ Finset.range P₁.size, (rowGet p₂ l).toPoly * s l := by + rw [Finset.mul_sum] + refine Finset.sum_congr rfl fun l hl ↦ ?_ + rw [hs l (Finset.mem_range.mp hl)] + ring + rw [hsum, show o = d₁ + (o - d₁) from by omega, pow_add] + exact mul_dvd_mul_left _ hquotsum + +end Approximant + +end PolynomialMatrix + +end CompPoly diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix/MuldersStorjohannCorrectness/WeakPopovMinimal.lean b/CompPoly/LinearAlgebra/PolynomialMatrix/MuldersStorjohannCorrectness/WeakPopovMinimal.lean new file mode 100644 index 00000000..c1c85e12 --- /dev/null +++ b/CompPoly/LinearAlgebra/PolynomialMatrix/MuldersStorjohannCorrectness/WeakPopovMinimal.lean @@ -0,0 +1,285 @@ +/- +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.LinearAlgebra.PolynomialMatrix.MuldersStorjohannCorrectness.Reduction + +/-! +# Shifted Weak-Popov Least-Row Minimality + +Generalized predictable-degree property: any shifted weak-Popov matrix contains a +row whose shifted degree is a lower bound for the shifted degree of every row-span +member. This is the reducer-independent core of +`muldersStorjohannReduce_least_row_minimal`, stated for an arbitrary well-formed +shifted weak-Popov matrix and without any alignment hypothesis between the shift +size and the matrix width. +-/ + +namespace CompPoly + +namespace PolynomialMatrix + +variable {F : Type*} [Field F] [BEq F] [LawfulBEq F] [DecidableEq F] + +/-- Predictable-degree property of shifted weak-Popov matrices: every row-span +member with a defined shifted degree is bounded below by the shifted degree of +some matrix row. Requires neither `shift.size = MatrixWidth B` nor any other +shift alignment hypothesis. -/ +theorem shiftedWeakPopov_least_row_minimal + (B : PolynomialMatrix F) (shift : Array Nat) (row : PolynomialRow F) + (hB : WellFormed B) + (hpopov : ShiftedWeakPopov B shift) + (hrow : row ∈ RowSpan B) + (hdeg : rowShiftedDegree? row shift ≠ none) : + ∃ outRow outDeg rowDeg, + outRow ∈ MatrixRows B ∧ + rowShiftedDegree? outRow shift = some outDeg ∧ + rowShiftedDegree? row shift = some rowDeg ∧ + outDeg ≤ rowDeg := by + rcases hrow with ⟨coeffs, _hcoeffsSize, hrowEq⟩ + subst row + cases hrowDeg : rowShiftedDegree? (rowLinearCombination coeffs B) shift with + | none => + exact False.elim (hdeg hrowDeg) + | some rowDeg => + let S := rowCombinationTermSupport coeffs B shift + have hS : S.Nonempty := + rowCombinationTermSupport_nonempty_of_combination_degree_some + hB hrowDeg + obtain ⟨imax, himaxS, hmaxDegree⟩ := + Finset.exists_max_image S + (rowCombinationTermDegree coeffs B shift) hS + let D := rowCombinationTermDegree coeffs B shift imax + let T := S.filter fun i ↦ rowCombinationTermDegree coeffs B shift i = D + have hT : T.Nonempty := by + refine ⟨imax, ?_⟩ + exact Finset.mem_filter.mpr ⟨himaxS, rfl⟩ + obtain ⟨i, hiT, hmaxPos⟩ := + Finset.exists_max_image T (rowCombinationTermPosition B shift) hT + have hiS : i ∈ S := (Finset.mem_filter.mp hiT).1 + have hiDegreeEq : + rowCombinationTermDegree coeffs B shift i = D := + (Finset.mem_filter.mp hiT).2 + rcases (Finset.mem_filter.mp hiS) with + ⟨hiRange, hcoeffI, hrowDegI_ne⟩ + have hi : i < B.size := Finset.mem_range.mp hiRange + cases hrowDegI : rowShiftedDegree? (B.getD i #[]) shift with + | none => + exact False.elim (hrowDegI_ne hrowDegI) + | some outDeg => + rcases rowShiftedLeadingPosition?_some_of_degree hrowDegI with + ⟨posI, hposI⟩ + have hposIRow : posI < (B.getD i #[]).size := + rowShiftedLeadingPosition?_lt hposI + have hposIWidth : posI < MatrixWidth B := by + rw [matrix_getD_size_of_wellFormed hB hi] at hposIRow + exact hposIRow + have hentryIData := + shiftedEntryDegree?_eq_some_iff_data.1 + (rowShiftedLeadingPosition?_entry_eq hrowDegI hposI) + rcases hentryIData with ⟨hentryINe, hentryIShift⟩ + let coeffI := coeffs.getD i 0 + let entryI := rowGet (B.getD i #[]) posI + let k := coeffI.natDegree + entryI.natDegree + have hdegreeI_expr : + rowCombinationTermDegree coeffs B shift i = + coeffI.natDegree + outDeg := by + unfold rowCombinationTermDegree coeffI + rw [hrowDegI] + have hD_eq : D = k + shift.getD posI 0 := by + rw [← hiDegreeEq, hdegreeI_expr, ← hentryIShift] + unfold k entryI + omega + have hselectedCoeffNe : + (coeffs.getD i 0 * rowGet (B.getD i #[]) posI).coeff k ≠ 0 := by + simpa [coeffI, entryI, k] using + cpoly_coeff_mul_natDegree_add_ne_zero + (P := coeffI) (Q := entryI) hcoeffI hentryINe + have hotherCoeffZero : + ∀ l, l < B.size → l ≠ i → + (coeffs.getD l 0 * rowGet (B.getD l #[]) posI).coeff k = 0 := by + intro l hl hli + by_contra hcoeffAtK + let coeffL := coeffs.getD l 0 + let entryL := rowGet (B.getD l #[]) posI + have hprodNonzero : coeffL * entryL ≠ 0 := by + intro hprodZero + exact hcoeffAtK (by + have hprodZero' : + coeffs.getD l 0 * rowGet (B.getD l #[]) posI = 0 := by + simpa [coeffL, entryL] using hprodZero + rw [hprodZero'] + exact CPolynomial.coeff_zero k) + have hcoeffL : coeffL ≠ 0 := by + intro hzero + exact hprodNonzero (by simp [coeffL, hzero]) + have hentryL : entryL ≠ 0 := by + intro hzero + exact hprodNonzero (by simp [entryL, hzero]) + have hposIRowL : posI < (B.getD l #[]).size := by + rw [matrix_getD_size_of_wellFormed hB hl] + exact hposIWidth + have hrowDegL_ne : + rowShiftedDegree? (B.getD l #[]) shift ≠ none := by + intro hnone + have hrowZero := + (rowShiftedDegree?_eq_none_iff + (row := B.getD l #[]) (shift := shift)).1 hnone + have hentryZero : rowGet (B.getD l #[]) posI = 0 := by + unfold rowGet + rw [Array.getD_eq_getD_getElem?, + Array.getElem?_eq_getElem hposIRowL] + exact hrowZero (B.getD l #[])[posI] + (by simpa only [Array.mem_def] using + Array.getElem_mem_toList hposIRowL) + exact hentryL (by simpa [entryL] using hentryZero) + have hlS : l ∈ S := + Finset.mem_filter.mpr + ⟨Finset.mem_range.mpr hl, hcoeffL, hrowDegL_ne⟩ + cases hrowDegL : rowShiftedDegree? (B.getD l #[]) shift with + | none => + exact hrowDegL_ne hrowDegL + | some outDegL => + rcases rowShiftedLeadingPosition?_some_of_degree hrowDegL with + ⟨posL, hposL⟩ + have hdegreeL_le_D : + rowCombinationTermDegree coeffs B shift l ≤ D := by + simpa [D] using hmaxDegree l hlS + have hk_le_prod : + k ≤ (coeffL * entryL).natDegree := + CPolynomial.le_natDegree_of_ne_zero hcoeffAtK + have hprodDeg_le : + (coeffL * entryL).natDegree ≤ + coeffL.natDegree + entryL.natDegree := + cpoly_natDegree_mul_le coeffL entryL + have hentryBound : + entryL.natDegree + shift.getD posI 0 ≤ outDegL := by + exact rowShiftedDegree?_entry_bound hrowDegL + hposIRowL (by simpa [entryL] using hentryL) + have hD_le_degreeL : D ≤ coeffL.natDegree + outDegL := by + rw [hD_eq] + omega + have hdegreeL_eq_D : coeffL.natDegree + outDegL = D := by + have hdegreeL_expr : + rowCombinationTermDegree coeffs B shift l = + coeffL.natDegree + outDegL := by + unfold rowCombinationTermDegree coeffL + rw [hrowDegL] + have hle : coeffL.natDegree + outDegL ≤ D := by + simpa [hdegreeL_expr] using hdegreeL_le_D + exact le_antisymm hle hD_le_degreeL + have hD_le_entry : + D ≤ coeffL.natDegree + + (entryL.natDegree + shift.getD posI 0) := by + rw [hD_eq] + omega + have hentryShiftEq : + entryL.natDegree + shift.getD posI 0 = outDegL := by + omega + have hentryShiftL : + shiftedEntryDegree? (B.getD l #[]) shift posI = + some outDegL := by + have hsome := + shiftedEntryDegree?_eq_some_of_rowGet_ne_zero + (row := B.getD l #[]) (shift := shift) (j := posI) + (by simpa [entryL] using hentryL) + have hentryShiftEq' : + (rowGet (B.getD l #[]) posI).natDegree + + shift.getD posI 0 = outDegL := by + simpa [entryL] using hentryShiftEq + rw [hentryShiftEq'] at hsome + exact hsome + have hposI_le_posL : posI ≤ posL := + rowShiftedLeadingPosition?_le_of_entry_eq_degree + hrowDegL hposL hposIRowL hentryShiftL + have hlDegreeEq : + rowCombinationTermDegree coeffs B shift l = D := by + have hdegreeL_expr : + rowCombinationTermDegree coeffs B shift l = + coeffL.natDegree + outDegL := by + unfold rowCombinationTermDegree coeffL + rw [hrowDegL] + rw [hdegreeL_expr, hdegreeL_eq_D] + have hlT : l ∈ T := + Finset.mem_filter.mpr ⟨hlS, hlDegreeEq⟩ + have hposL_le_posI : posL ≤ posI := by + have hle := hmaxPos l hlT + have hposLval : + rowCombinationTermPosition B shift l = posL := by + unfold rowCombinationTermPosition + rw [hposL] + rfl + have hposIval : + rowCombinationTermPosition B shift i = posI := by + unfold rowCombinationTermPosition + rw [hposI] + rfl + rwa [hposLval, hposIval] at hle + have hposEq : posL = posI := + le_antisymm hposL_le_posI hposI_le_posL + have hposNe := + hpopov l i hl hi hli + (by rw [hposL]; simp) + (by rw [hposI]; simp) + exact hposNe (by rw [hposL, hposI, hposEq]) + have hcomboCoeffNe : + (rowGet (rowLinearCombination coeffs B) posI).coeff k ≠ 0 := by + rw [rowGet_rowLinearCombination_coeff] + rw [Finset.sum_eq_single i] + · exact hselectedCoeffNe + · intro l hlRange hli + exact hotherCoeffZero l (Finset.mem_range.mp hlRange) hli + · intro hiNot + exact False.elim (hiNot (Finset.mem_range.mpr hi)) + have hcomboEntryNe : + rowGet (rowLinearCombination coeffs B) posI ≠ 0 := by + intro hzero + exact hcomboCoeffNe (by + rw [hzero] + exact CPolynomial.coeff_zero k) + have hcomboPos : posI < (rowLinearCombination coeffs B).size := by + simpa [rowLinearCombination_size hB coeffs] using hposIWidth + have hcomboEntryBound : + (rowGet (rowLinearCombination coeffs B) posI).natDegree + + shift.getD posI 0 ≤ rowDeg := + rowShiftedDegree?_entry_bound hrowDeg hcomboPos hcomboEntryNe + have hk_le_combo : + k ≤ (rowGet (rowLinearCombination coeffs B) posI).natDegree := + CPolynomial.le_natDegree_of_ne_zero hcomboCoeffNe + have hD_le_rowDeg : D ≤ rowDeg := by + rw [hD_eq] + exact le_trans (Nat.add_le_add_right hk_le_combo _) + hcomboEntryBound + have houtDeg_le_D : outDeg ≤ D := by + rw [← hiDegreeEq, hdegreeI_expr] + omega + refine ⟨B.getD i #[], outDeg, rowDeg, ?_, hrowDegI, rfl, ?_⟩ + · rw [MatrixRows] + rw [Array.getD_eq_getD_getElem?, Array.getElem?_eq_getElem hi] + exact Array.getElem_mem_toList hi + · exact le_trans houtDeg_le_D hD_le_rowDeg + +/-- Least-row minimality of the Mulders-Storjohann reducer, re-derived from the +generalized shifted weak-Popov predictable-degree property. -/ +theorem muldersStorjohannReduce_least_row_minimal_of_weakPopov + (M : PolynomialMatrix F) (shift : Array Nat) (row : PolynomialRow F) + (hM : WellFormed M) (hshift : shift.size = MatrixWidth M) + (hrow : row ∈ RowSpan M) + (hdeg : rowShiftedDegree? row shift ≠ none) : + ∃ outRow outDeg rowDeg, + outRow ∈ MatrixRows (muldersStorjohannReduce M shift) ∧ + rowShiftedDegree? outRow shift = some outDeg ∧ + rowShiftedDegree? row shift = some rowDeg ∧ + outDeg ≤ rowDeg := by + refine shiftedWeakPopov_least_row_minimal + (muldersStorjohannReduce M shift) shift row + (muldersStorjohannReduce_wellFormed M shift hM) + (muldersStorjohannReduce_weakPopov M shift hM hshift) + ?_ hdeg + rwa [muldersStorjohannReduce_rowSpan_eq M shift hM] + +end PolynomialMatrix + +end CompPoly diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix/Operations.lean b/CompPoly/LinearAlgebra/PolynomialMatrix/Operations.lean index ca577fa6..aca6d375 100644 --- a/CompPoly/LinearAlgebra/PolynomialMatrix/Operations.lean +++ b/CompPoly/LinearAlgebra/PolynomialMatrix/Operations.lean @@ -22,12 +22,40 @@ namespace PolynomialMatrix variable {F : Type*} +/-- Keep the coefficients of degree `< order`. -/ +def truncateX [Zero F] [BEq F] [LawfulBEq F] (order : Nat) + (p : CPolynomial F) : CPolynomial F := + CPolynomial.ofArray + ((List.range order).map (fun i ↦ CPolynomial.coeff p i) |>.toArray) + +/-- Coefficients of an `X`-adic truncation. -/ +theorem truncateX_coeff [Semiring F] [BEq F] [LawfulBEq F] (order : Nat) + (p : CPolynomial F) (i : Nat) : + CPolynomial.coeff (truncateX order p) i = + if i < order then CPolynomial.coeff p i else 0 := by + rw [truncateX] + rw [show CPolynomial.coeff (CPolynomial.ofArray + ((List.range order).map (fun i ↦ CPolynomial.coeff p i) |>.toArray)) i = + CPolynomial.Raw.coeff + ((List.range order).map (fun i ↦ CPolynomial.coeff p i) |>.toArray) i from by + rw [CPolynomial.ofArray, CPolynomial.coeff] + rw [CPolynomial.Raw.Trim.coeff_eq_coeff]] + rw [CPolynomial.Raw.coeff, Array.getD_eq_getD_getElem?, List.getElem?_toArray, + List.getElem?_map] + by_cases hi : i < order + · rw [List.getElem?_range hi] + simp [hi] + · rw [List.getElem?_eq_none (by simpa using Nat.le_of_not_lt hi)] + simp [hi] + /-- Polynomial-matrix low-product backend. The full multiplication context is kept beside the low-product operation because recursive PM-basis still needs ordinary basis composition. -/ structure MulLowContext (F : Type*) [Semiring F] [BEq F] [LawfulBEq F] where mulContext : CPolynomial.MulContext F mulLow : Nat → CPolynomial F → CPolynomial F → CPolynomial F + /-- The backend returns exactly the truncated canonical product. -/ + mulLow_eq : ∀ order p q, mulLow order p q = truncateX order (p * q) namespace MulLowContext @@ -38,6 +66,10 @@ def fromMulContext [Semiring F] [BEq F] [LawfulBEq F] mulLow order p q := CPolynomial.ofArray ((List.range order).map (fun i ↦ CPolynomial.coeff (mulCtx.mul p q) i) |>.toArray) + mulLow_eq := by + intro order p q + rw [mulCtx.mul_eq_mul] + rfl /-- Low-product backend backed directly by a raw low-product implementation. -/ def raw [Semiring F] [BEq F] [LawfulBEq F] @@ -45,6 +77,20 @@ def raw [Semiring F] [BEq F] [LawfulBEq F] (rawCtx : CPolynomial.Raw.MulLowContext F) : MulLowContext F where mulContext := mulCtx mulLow order p q := CPolynomial.ofArray (rawCtx.mulLow order p.val q.val) + mulLow_eq := by + intro order p q + apply CPolynomial.eq_iff_coeff.2 + intro i + rw [truncateX_coeff] + rw [show CPolynomial.coeff (CPolynomial.ofArray (rawCtx.mulLow order p.val q.val)) i = + CPolynomial.Raw.coeff (rawCtx.mulLow order p.val q.val) i from by + rw [CPolynomial.ofArray, CPolynomial.coeff] + rw [CPolynomial.Raw.Trim.coeff_eq_coeff]] + rw [CPolynomial.Raw.mulLow_coeff] + by_cases hi : i < order + · simp only [hi, if_true] + rw [CPolynomial.coeff_mul, CPolynomial.Raw.mul_coeff] + · simp [hi] end MulLowContext @@ -180,12 +226,6 @@ def rowMulMatrixEntryCoeffCap [Zero F] [BEq F] (fun acc k ↦ max acc (productCoeffCap (rowGet row k) (rowGet (M.getD k #[]) j))) 0 -/-- Keep the coefficients of degree `< order`. -/ -def truncateX [Zero F] [BEq F] [LawfulBEq F] (order : Nat) - (p : CPolynomial F) : CPolynomial F := - CPolynomial.ofArray - ((List.range order).map (fun i ↦ CPolynomial.coeff p i) |>.toArray) - /-- Truncate one row with independent output-column orders. -/ def rowTruncateColumns [Zero F] [BEq F] [LawfulBEq F] (orders : Array Nat) (row : PolynomialRow F) : PolynomialRow F := diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix/RowSelection.lean b/CompPoly/LinearAlgebra/PolynomialMatrix/RowSelection.lean new file mode 100644 index 00000000..be523cc2 --- /dev/null +++ b/CompPoly/LinearAlgebra/PolynomialMatrix/RowSelection.lean @@ -0,0 +1,396 @@ +/- +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.LinearAlgebra.PolynomialMatrix.Operations + +/-! +# Least-Shifted-Degree Row Selection Helpers + +Correctness facts for the generic least-shifted-degree row scan in +`PolynomialMatrix.Operations`. +-/ + +namespace CompPoly + + + +namespace PolynomialMatrix + + + +variable {F : Type*} [Field F] [BEq F] + +def RowChoiceValid (M : PolynomialMatrix F) (shift : Array Nat) + (choice : RowChoice F) : Prop := + choice.index < M.size ∧ choice.row = M.getD choice.index #[] ∧ + rowShiftedDegree? choice.row shift = some choice.degree + +omit [BEq F] in +private theorem betterRowChoice_true_candidate_degree_le + {candidate current : RowChoice F} + (h : betterRowChoice candidate current = true) : + candidate.degree ≤ current.degree := by + unfold betterRowChoice at h + simp at h + rcases h with hlt | ⟨heq, _hidx⟩ + · omega + · have hdegree : candidate.degree = current.degree := heq + omega +omit [BEq F] in +private theorem betterRowChoice_false_current_degree_le + {candidate current : RowChoice F} + (h : betterRowChoice candidate current = false) : + current.degree ≤ candidate.degree := by + unfold betterRowChoice at h + by_cases hlt : candidate.degree < current.degree + · simp [hlt] at h + · omega + +omit [BEq F] in +private theorem betterRowChoice_not_true_current_degree_le + {candidate current : RowChoice F} + (h : ¬ betterRowChoice candidate current = true) : + current.degree ≤ candidate.degree := + betterRowChoice_false_current_degree_le (Bool.eq_false_iff.mpr h) + + +private theorem leastShiftedDegreeRowStep?_none + {M : PolynomialMatrix F} {shift : Array Nat} + {best : Option (RowChoice F)} {i : Nat} + (h : leastShiftedDegreeRowStep? M shift best i = none) : + best = none ∧ rowShiftedDegree? (M[i]?.getD #[]) shift = none := by + unfold leastShiftedDegreeRowStep? at h + cases hdeg : rowShiftedDegree? (M[i]?.getD #[]) shift with + | none => + have hbest : best = none := by + simpa [leastShiftedDegreeRowStep?, hdeg] using h + exact ⟨hbest, rfl⟩ + | some degree => + cases best with + | none => + simp [hdeg] at h + | some current => + by_cases hbetter : + betterRowChoice { index := i, row := M[i]?.getD #[], degree := degree } current + · simp [hdeg, hbetter] at h + · simp [hdeg, hbetter] at h + + +private theorem leastShiftedDegreeRowStep?_some_of_best_some + {M : PolynomialMatrix F} {shift : Array Nat} + {best : Option (RowChoice F)} {current : RowChoice F} {i : Nat} + (hbest : best = some current) : + ∃ choice, leastShiftedDegreeRowStep? M shift best i = some choice := by + unfold leastShiftedDegreeRowStep? + cases hdeg : rowShiftedDegree? (M[i]?.getD #[]) shift with + | none => + refine ⟨current, ?_⟩ + simp [hbest, hdeg] + | some degree => + by_cases hbetter : + betterRowChoice { index := i, row := M[i]?.getD #[], degree := degree } current + · exact ⟨{ index := i, row := M[i]?.getD #[], degree := degree }, + by simp [hbest, hdeg, hbetter]⟩ + · exact ⟨current, + by simp [hbest, hdeg, hbetter]⟩ + + +private theorem leastShiftedDegreeRowStep?_preserves_degree_le + {M : PolynomialMatrix F} {shift : Array Nat} + {best : Option (RowChoice F)} {choice : RowChoice F} {d i : Nat} + (hbest : ∃ current, best = some current ∧ current.degree ≤ d) + (hstep : leastShiftedDegreeRowStep? M shift best i = some choice) : + choice.degree ≤ d := by + rcases hbest with ⟨current, hbest, hcurrent⟩ + unfold leastShiftedDegreeRowStep? at hstep + cases hdeg : rowShiftedDegree? (M[i]?.getD #[]) shift with + | none => + have hstep' : some current = some choice := by + simpa [leastShiftedDegreeRowStep?, hdeg, hbest] using hstep + have hchoice : choice = current := by + injection hstep' with hEq + exact hEq.symm + rw [hchoice] + exact hcurrent + | some degree => + let candidate : RowChoice F := + { index := i, row := M[i]?.getD #[], degree := degree } + by_cases hbetter : betterRowChoice candidate current + · have hstep' : some candidate = some choice := by + simpa [leastShiftedDegreeRowStep?, hdeg, hbest, candidate, hbetter] using hstep + have hchoice : choice = candidate := by + injection hstep' with hEq + exact hEq.symm + rw [hchoice] + exact le_trans (betterRowChoice_true_candidate_degree_le hbetter) hcurrent + · have hstep' : some current = some choice := by + simpa [leastShiftedDegreeRowStep?, hdeg, hbest, candidate, hbetter] using hstep + have hchoice : choice = current := by + injection hstep' with hEq + exact hEq.symm + rw [hchoice] + exact hcurrent + + +private theorem leastShiftedDegreeRowStep?_degree_le_of_row + {M : PolynomialMatrix F} {shift : Array Nat} + {best : Option (RowChoice F)} {choice : RowChoice F} {d i : Nat} + (hdeg : rowShiftedDegree? (M[i]?.getD #[]) shift = some d) + (hstep : leastShiftedDegreeRowStep? M shift best i = some choice) : + choice.degree ≤ d := by + let candidate : RowChoice F := { index := i, row := M[i]?.getD #[], degree := d } + cases best with + | none => + have hstep' : some candidate = some choice := by + simpa [leastShiftedDegreeRowStep?, hdeg, candidate] using hstep + have hchoice : choice = candidate := by + injection hstep' with hEq + exact hEq.symm + simp [hchoice, candidate] + | some current => + by_cases hbetter : betterRowChoice candidate current + · have hstep' : some candidate = some choice := by + simpa [leastShiftedDegreeRowStep?, hdeg, candidate, hbetter] using hstep + have hchoice : choice = candidate := by + injection hstep' with hEq + exact hEq.symm + simp [hchoice, candidate] + · have hstep' : some current = some choice := by + simpa [leastShiftedDegreeRowStep?, hdeg, candidate, hbetter] using hstep + have hchoice : choice = current := by + injection hstep' with hEq + exact hEq.symm + rw [hchoice] + exact betterRowChoice_not_true_current_degree_le hbetter + + +private theorem leastShiftedDegreeFold_some_of_best_some + {M : PolynomialMatrix F} {shift : Array Nat} + (xs : List Nat) {best : Option (RowChoice F)} {current : RowChoice F} + (hbest : best = some current) : + ∃ choice, + xs.foldl (leastShiftedDegreeRowStep? M shift) best = some choice := by + induction xs generalizing best current with + | nil => + exact ⟨current, hbest⟩ + | cons i xs ih => + rcases leastShiftedDegreeRowStep?_some_of_best_some + (M := M) (shift := shift) (i := i) hbest with + ⟨choice, hstep⟩ + exact ih (best := leastShiftedDegreeRowStep? M shift best i) + (current := choice) hstep + +private theorem leastShiftedDegreeFold_preserves_degree_le + {M : PolynomialMatrix F} {shift : Array Nat} + (xs : List Nat) {best : Option (RowChoice F)} {choice : RowChoice F} {d : Nat} + (hbest : ∃ current, best = some current ∧ current.degree ≤ d) + (hfold : xs.foldl (leastShiftedDegreeRowStep? M shift) best = some choice) : + choice.degree ≤ d := by + induction xs generalizing best with + | nil => + rcases hbest with ⟨current, hbest, hcurrent⟩ + rw [hbest] at hfold + cases hfold + exact hcurrent + | cons i xs ih => + rcases hbest with ⟨current, hbest, hcurrent⟩ + rcases leastShiftedDegreeRowStep?_some_of_best_some + (M := M) (shift := shift) (i := i) hbest with + ⟨stepChoice, hstep⟩ + have hstepBound : stepChoice.degree ≤ d := + leastShiftedDegreeRowStep?_preserves_degree_le + (M := M) (shift := shift) (i := i) + ⟨current, hbest, hcurrent⟩ hstep + exact ih (best := leastShiftedDegreeRowStep? M shift best i) + ⟨stepChoice, hstep, hstepBound⟩ + (by simpa only [List.foldl_cons] using hfold) + +private theorem leastShiftedDegreeFold_degree_le_of_mem + {M : PolynomialMatrix F} {shift : Array Nat} + (xs : List Nat) {best : Option (RowChoice F)} {choice : RowChoice F} + {i d : Nat} + (hfold : xs.foldl (leastShiftedDegreeRowStep? M shift) best = some choice) + (hi : i ∈ xs) + (hdeg : rowShiftedDegree? (M[i]?.getD #[]) shift = some d) : + choice.degree ≤ d := by + induction xs generalizing best with + | nil => + simp at hi + | cons x xs ih => + simp at hi + rcases hi with hix | hi + · subst x + cases hstep : leastShiftedDegreeRowStep? M shift best i with + | none => + rcases leastShiftedDegreeRowStep?_none hstep with ⟨_hbest, hrowNone⟩ + rw [hrowNone] at hdeg + contradiction + | some stepChoice => + have hstepBound : stepChoice.degree ≤ d := + leastShiftedDegreeRowStep?_degree_le_of_row hdeg hstep + exact leastShiftedDegreeFold_preserves_degree_le + (M := M) (shift := shift) xs + ⟨stepChoice, rfl, hstepBound⟩ + (by simpa [hstep] using hfold) + · exact ih (best := leastShiftedDegreeRowStep? M shift best x) + (by simpa only [List.foldl_cons] using hfold) hi + +private theorem leastShiftedDegreeFold_none + {M : PolynomialMatrix F} {shift : Array Nat} + (xs : List Nat) {best : Option (RowChoice F)} + (hfold : xs.foldl (leastShiftedDegreeRowStep? M shift) best = none) : + best = none ∧ + ∀ i, i ∈ xs → rowShiftedDegree? (M[i]?.getD #[]) shift = none := by + induction xs generalizing best with + | nil => + exact ⟨hfold, by simp⟩ + | cons i xs ih => + have htail := ih (best := leastShiftedDegreeRowStep? M shift best i) + (by simpa only [List.foldl_cons] using hfold) + rcases leastShiftedDegreeRowStep?_none htail.1 with ⟨hbest, hrow⟩ + refine ⟨hbest, ?_⟩ + intro j hj + simp at hj + rcases hj with hji | hj + · subst j + exact hrow + · exact htail.2 j hj + + +private theorem leastShiftedDegreeFold_valid + {M : PolynomialMatrix F} {shift : Array Nat} + (xs : List Nat) {best : Option (RowChoice F)} + (hbest : ∀ choice, best = some choice → RowChoiceValid M shift choice) + (hxs : ∀ i, i ∈ xs → i < M.size) : + ∀ choice, + xs.foldl (leastShiftedDegreeRowStep? M shift) best = some choice → + RowChoiceValid M shift choice := by + induction xs generalizing best with + | nil => + intro choice hfold + exact hbest choice hfold + | cons i xs ih => + intro choice hfold + have hstepValid : + ∀ stepChoice, + leastShiftedDegreeRowStep? M shift best i = some stepChoice → + RowChoiceValid M shift stepChoice := by + intro stepChoice hstep + cases hdeg : rowShiftedDegree? (M[i]?.getD #[]) shift with + | none => + have hstep' : best = some stepChoice := by + simpa [leastShiftedDegreeRowStep?, hdeg] using hstep + exact hbest stepChoice hstep' + | some degree => + let candidate : RowChoice F := + { index := i, row := M[i]?.getD #[], degree := degree } + cases best with + | none => + have hstep' : some candidate = some stepChoice := by + simpa [leastShiftedDegreeRowStep?, hdeg, candidate] using hstep + have hstepChoice : stepChoice = candidate := by + injection hstep' with hEq + exact hEq.symm + rw [hstepChoice] + exact ⟨by simpa [candidate] using hxs i (by simp), + by simp [candidate, Array.getD_eq_getD_getElem?], + by simpa [candidate] using hdeg⟩ + | some current => + by_cases hbetter : betterRowChoice candidate current + · have hstep' : some candidate = some stepChoice := by + simpa [leastShiftedDegreeRowStep?, hdeg, candidate, hbetter] using hstep + have hstepChoice : stepChoice = candidate := by + injection hstep' with hEq + exact hEq.symm + rw [hstepChoice] + exact ⟨by simpa [candidate] using hxs i (by simp), + by simp [candidate, Array.getD_eq_getD_getElem?], + by simpa [candidate] using hdeg⟩ + · have hstep' : some current = some stepChoice := by + simpa [leastShiftedDegreeRowStep?, hdeg, candidate, hbetter] using hstep + have hstepChoice : stepChoice = current := by + injection hstep' with hEq + exact hEq.symm + rw [hstepChoice] + exact hbest current rfl + exact ih + (best := leastShiftedDegreeRowStep? M shift best i) + hstepValid + (by + intro j hj + exact hxs j (by simp [hj])) + choice + (by simpa only [List.foldl_cons] using hfold) + + +theorem leastShiftedDegreeChoice?_some_valid + {M : PolynomialMatrix F} {shift : Array Nat} {choice : RowChoice F} + (hchoice : leastShiftedDegreeChoice? M shift = some choice) : + RowChoiceValid M shift choice := by + unfold leastShiftedDegreeChoice? at hchoice + exact leastShiftedDegreeFold_valid + (M := M) (shift := shift) (List.range M.size) + (by intro choice h; cases h) + (by intro i hi; exact List.mem_range.mp hi) + choice hchoice + + +theorem leastShiftedDegreeChoice?_degree_le + {M : PolynomialMatrix F} {shift : Array Nat} + {choice : RowChoice F} {i d : Nat} + (hchoice : leastShiftedDegreeChoice? M shift = some choice) + (hi : i < M.size) + (hdeg : rowShiftedDegree? (M.getD i #[]) shift = some d) : + choice.degree ≤ d := by + unfold leastShiftedDegreeChoice? at hchoice + have hdeg' : rowShiftedDegree? (M[i]?.getD #[]) shift = some d := by + simpa [Array.getD_eq_getD_getElem?] using hdeg + exact leastShiftedDegreeFold_degree_le_of_mem + (M := M) (shift := shift) (List.range M.size) + hchoice (List.mem_range.mpr hi) hdeg' + + +theorem leastShiftedDegreeChoice?_some_of_degree + {M : PolynomialMatrix F} {shift : Array Nat} {i d : Nat} + (hi : i < M.size) + (hdeg : rowShiftedDegree? (M.getD i #[]) shift = some d) : + ∃ choice, leastShiftedDegreeChoice? M shift = some choice ∧ choice.degree ≤ d := by + cases hchoice : leastShiftedDegreeChoice? M shift with + | none => + unfold leastShiftedDegreeChoice? at hchoice + rcases leastShiftedDegreeFold_none + (M := M) (shift := shift) (List.range M.size) hchoice with + ⟨_hbest, hall⟩ + have hnone := hall i (List.mem_range.mpr hi) + have hnone' : rowShiftedDegree? (M.getD i #[]) shift = none := by + simpa [Array.getD_eq_getD_getElem?] using hnone + rw [hnone'] at hdeg + contradiction + | some choice => + refine ⟨choice, rfl, ?_⟩ + exact leastShiftedDegreeChoice?_degree_le hchoice hi hdeg + + +theorem leastShiftedDegreeRow?_some_valid + {M : PolynomialMatrix F} {shift : Array Nat} {row : PolynomialRow F} + (hrow : leastShiftedDegreeRow? M shift = some row) : + ∃ choice, + leastShiftedDegreeChoice? M shift = some choice ∧ + RowChoiceValid M shift choice ∧ choice.row = row := by + unfold leastShiftedDegreeRow? at hrow + cases hchoice : leastShiftedDegreeChoice? M shift with + | none => + simp [hchoice] at hrow + | some choice => + simp [hchoice] at hrow + refine ⟨choice, rfl, leastShiftedDegreeChoice?_some_valid hchoice, ?_⟩ + exact hrow + +end PolynomialMatrix + + + +end CompPoly diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix/StrassenCorrectness.lean b/CompPoly/LinearAlgebra/PolynomialMatrix/StrassenCorrectness.lean new file mode 100644 index 00000000..4c74c6b1 --- /dev/null +++ b/CompPoly/LinearAlgebra/PolynomialMatrix/StrassenCorrectness.lean @@ -0,0 +1,810 @@ +/- +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.LinearAlgebra.PolynomialMatrix.Operations +import Mathlib.Tactic.Abel + +/-! +# Strassen Polynomial-Matrix Product Correctness + +Correctness proofs for the Strassen-style polynomial-matrix products in +`CompPoly.LinearAlgebra.PolynomialMatrix.Operations`: + +* `rowGet_rowMulMatrixWith`: entry semantics of the naive row-by-matrix product. +* `mulBoundedWith_eq_mulWith`: the degree-capped low-product reconstruction equals + the naive product. +* `mulStrassenWith_eq_mulWith` and `mulStrassenWithFuel_eq_mulWith`: the Strassen + recursion computes exactly the naive matrix product, as arrays. +* `mulTruncColumnStrassenWith_eq_truncateColumns` and + `mulTruncColumnStrassenWith_entry`: the column-truncated Strassen recursion + computes exactly the column-truncated naive product. +-/ + +namespace CompPoly + +namespace PolynomialMatrix + +variable {F : Type*} + +/-! ## Generic array and fold helpers -/ + +private theorem getD_of_lt {α : Type*} (a : Array α) (d : α) {i : Nat} + (hi : i < a.size) : a.getD i d = a[i] := by + rw [Array.getD_eq_getD_getElem?, Array.getElem?_eq_getElem hi, Option.getD_some] + +private theorem getD_of_le {α : Type*} (a : Array α) (d : α) {i : Nat} + (hi : a.size ≤ i) : a.getD i d = d := by + rw [Array.getD_eq_getD_getElem?, Array.getElem?_eq_none hi, Option.getD_none] + +private theorem getD_list_range_map {α : Type*} (g : Nat → α) (n j : Nat) (d : α) : + (((List.range n).map g).toArray).getD j d = if j < n then g j else d := by + rw [Array.getD_eq_getD_getElem?, List.getElem?_toArray, List.getElem?_map] + by_cases hj : j < n + · rw [List.getElem?_range hj] + simp [hj] + · rw [List.getElem?_eq_none (by simpa using Nat.le_of_not_lt hj)] + simp [hj] + +private theorem foldl_add_eq_add_sum {M : Type*} [AddCommMonoid M] (g : Nat → M) : + ∀ (n : Nat) (init : M), + (List.range n).foldl (fun acc k ↦ acc + g k) init = + init + ∑ k ∈ Finset.range n, g k + | 0, init => by simp + | n + 1, init => by + rw [List.range_succ, List.foldl_append, foldl_add_eq_add_sum g n init, + List.foldl_cons, List.foldl_nil, Finset.sum_range_succ, add_assoc] + +private theorem foldl_add_eq_sum {M : Type*} [AddCommMonoid M] (g : Nat → M) (n : Nat) : + (List.range n).foldl (fun acc k ↦ acc + g k) 0 = ∑ k ∈ Finset.range n, g k := by + rw [foldl_add_eq_add_sum, zero_add] + +private theorem le_foldl_max_init (g : Nat → Nat) : + ∀ (l : List Nat) (init : Nat), init ≤ l.foldl (fun acc k ↦ max acc (g k)) init + | [], _ => Nat.le_refl _ + | x :: t, init => + Nat.le_trans (Nat.le_max_left init (g x)) (le_foldl_max_init g t (max init (g x))) + +private theorem le_foldl_max (g : Nat → Nat) : + ∀ (l : List Nat) (init : Nat) {k : Nat}, k ∈ l → + g k ≤ l.foldl (fun acc i ↦ max acc (g i)) init + | x :: t, init, k, hk => by + rcases List.mem_cons.1 hk with rfl | hk + · exact Nat.le_trans (Nat.le_max_right init (g k)) (le_foldl_max_init g t _) + · exact le_foldl_max g t _ hk + +/-- The fuel-bounded doubling loop reaches its target when given enough fuel. -/ +private theorem le_nextPowerOfTwoAtLeastLoop (target : Nat) : + ∀ (fuel current : Nat), 1 ≤ current → target ≤ current + fuel → + target ≤ nextPowerOfTwoAtLeastLoop target fuel current + | 0, current, _, h => by + simpa [nextPowerOfTwoAtLeastLoop] using h + | fuel + 1, current, hcur, h => by + rw [nextPowerOfTwoAtLeastLoop] + split + · assumption + · exact le_nextPowerOfTwoAtLeastLoop target fuel (2 * current) (by omega) (by omega) + +/-- `nextPowerOfTwoAtLeast` is at least its target. -/ +theorem le_nextPowerOfTwoAtLeast (target : Nat) : + target ≤ nextPowerOfTwoAtLeast target := + le_nextPowerOfTwoAtLeastLoop target (target + 1) 1 (Nat.le_refl 1) (by omega) + +/-- Entry access for `natArraySlice`. -/ +theorem natArraySlice_getD (values : Array Nat) (start count j : Nat) : + (natArraySlice values start count).getD j 0 = + if j < count then values.getD (start + j) 0 else 0 := by + rw [natArraySlice, getD_list_range_map] + +/-- Entry access for `maxNatArrays`. -/ +theorem maxNatArrays_getD (a b : Array Nat) (j : Nat) : + (maxNatArrays a b).getD j 0 = max (a.getD j 0) (b.getD j 0) := by + rw [maxNatArrays, getD_list_range_map] + split + · rfl + · rename_i h + rw [getD_of_le a 0 (by omega), getD_of_le b 0 (by omega), Nat.max_self] + +/-! ## Row access helpers -/ + +/-- Reading a row past its width yields zero. -/ +theorem rowGet_of_size_le [Zero F] {row : PolynomialRow F} {j : Nat} + (hj : row.size ≤ j) : rowGet row j = 0 := + getD_of_le row 0 hj + +private theorem rowGet_getD_of_size_le [Zero F] {M : PolynomialMatrix F} {i : Nat} + (hi : M.size ≤ i) (j : Nat) : rowGet (M.getD i #[]) j = 0 := by + rw [getD_of_le M #[] hi] + exact rowGet_of_size_le (Nat.zero_le j) + +private theorem rowGet_list_range_map [Zero F] (g : Nat → CPolynomial F) (w j : Nat) : + rowGet (((List.range w).map g).toArray) j = if j < w then g j else 0 := by + rw [rowGet, getD_list_range_map] + +/-! ## `truncateX` algebra -/ + +/-- Truncation of the zero polynomial. -/ +theorem truncateX_zero [Semiring F] [BEq F] [LawfulBEq F] (order : Nat) : + truncateX order (0 : CPolynomial F) = 0 := by + rw [CPolynomial.eq_iff_coeff] + intro i + simp only [truncateX_coeff, CPolynomial.coeff_zero, ite_self] + +/-- Truncation distributes over addition. -/ +theorem truncateX_add [Semiring F] [BEq F] [LawfulBEq F] (order : Nat) + (p q : CPolynomial F) : + truncateX order (p + q) = truncateX order p + truncateX order q := by + rw [CPolynomial.eq_iff_coeff] + intro i + simp only [truncateX_coeff, CPolynomial.coeff_add] + by_cases hi : i < order + · simp only [if_pos hi] + · simp only [if_neg hi, add_zero] + +/-- Truncation distributes over subtraction. -/ +theorem truncateX_sub [Ring F] [BEq F] [LawfulBEq F] (order : Nat) + (p q : CPolynomial F) : + truncateX order (p - q) = truncateX order p - truncateX order q := by + rw [CPolynomial.eq_iff_coeff] + intro i + simp only [truncateX_coeff, CPolynomial.coeff_sub] + by_cases hi : i < order + · simp only [if_pos hi] + · simp only [if_neg hi, sub_zero] + +/-- Nested truncations keep the smaller order. -/ +theorem truncateX_truncateX [Semiring F] [BEq F] [LawfulBEq F] (o o' : Nat) + (p : CPolynomial F) : + truncateX o (truncateX o' p) = truncateX (min o o') p := by + rw [CPolynomial.eq_iff_coeff] + intro i + simp only [truncateX_coeff] + by_cases h₁ : i < o + · by_cases h₂ : i < o' + · rw [if_pos h₁, if_pos h₂, if_pos (by omega)] + · rw [if_pos h₁, if_neg h₂, if_neg (by omega)] + · rw [if_neg h₁, if_neg (by omega)] + +/-- Truncation distributes over finite range sums. -/ +theorem truncateX_sum [Semiring F] [BEq F] [LawfulBEq F] (order n : Nat) + (f : Nat → CPolynomial F) : + truncateX order (∑ k ∈ Finset.range n, f k) = + ∑ k ∈ Finset.range n, truncateX order (f k) := by + rw [CPolynomial.eq_iff_coeff] + intro i + rw [truncateX_coeff, CPolynomial.coeff_finset_sum, CPolynomial.coeff_finset_sum] + simp only [truncateX_coeff] + by_cases hi : i < order + · simp only [if_pos hi] + · simp only [if_neg hi, Finset.sum_const_zero] + +private theorem coeff_eq_zero_of_natDegree_lt [Zero F] [BEq F] [LawfulBEq F] + {p : CPolynomial F} {i : Nat} (h : p.natDegree < i) : + CPolynomial.coeff p i = 0 := by + by_contra hne + exact absurd (CPolynomial.le_natDegree_of_ne_zero hne) (by omega) + +private theorem coeff_mul_eq_zero_of_natDegree_lt [Semiring F] [BEq F] [LawfulBEq F] + {p q : CPolynomial F} {i : Nat} (h : p.natDegree + q.natDegree < i) : + CPolynomial.coeff (p * q) i = 0 := by + rw [CPolynomial.coeff_mul] + refine Finset.sum_eq_zero fun k hk ↦ ?_ + rcases Nat.lt_or_ge p.natDegree k with hk' | hk' + · rw [coeff_eq_zero_of_natDegree_lt hk', zero_mul] + · rw [coeff_eq_zero_of_natDegree_lt (p := q) (by omega), mul_zero] + +/-- A product is unchanged by truncation past its coefficient cap. -/ +theorem truncateX_mul_of_productCoeffCap_le [Semiring F] [BEq F] [LawfulBEq F] + {p q : CPolynomial F} {order : Nat} (h : productCoeffCap p q ≤ order) : + truncateX order (p * q) = p * q := by + by_cases hz : (p == 0 || q == 0) = true + · simp only [Bool.or_eq_true, beq_iff_eq] at hz + rcases hz with hp | hq + · rw [hp, CPolynomial.zero_mul, truncateX_zero] + · rw [hq, CPolynomial.mul_zero, truncateX_zero] + · rw [productCoeffCap, if_neg hz] at h + rw [CPolynomial.eq_iff_coeff] + intro i + rw [truncateX_coeff] + split + · rfl + · exact (coeff_mul_eq_zero_of_natDegree_lt (by omega)).symm + +/-! ## `ofFn` access toolkit -/ + +/-- Row count of `ofFn`. -/ +theorem ofFn_size [Zero F] (rows width : Nat) (entry : Nat → Nat → CPolynomial F) : + (ofFn rows width entry).size = rows := by + simp only [ofFn, List.size_toArray, List.length_map, List.length_range] + +private theorem getElem_ofFn [Zero F] {rows width : Nat} + {entry : Nat → Nat → CPolynomial F} {i : Nat} + (hi : i < (ofFn rows width entry).size) : + (ofFn rows width entry)[i] = ((List.range width).map (entry i)).toArray := by + simp only [ofFn, List.getElem_toArray, List.getElem_map, List.getElem_range] + +/-- Row access for `ofFn` with zero defaults. -/ +theorem getD_ofFn [Zero F] (rows width : Nat) (entry : Nat → Nat → CPolynomial F) + (i : Nat) : + (ofFn rows width entry).getD i #[] = + if i < rows then ((List.range width).map (entry i)).toArray else #[] := by + rw [ofFn, getD_list_range_map] + +/-- Entry access for `ofFn` with zero defaults. -/ +theorem rowGet_ofFn [Zero F] (rows width : Nat) (entry : Nat → Nat → CPolynomial F) + (i j : Nat) : + rowGet ((ofFn rows width entry).getD i #[]) j = + if i < rows ∧ j < width then entry i j else 0 := by + rw [getD_ofFn] + by_cases hi : i < rows + · rw [if_pos hi, rowGet_list_range_map] + by_cases hj : j < width + · rw [if_pos hj, if_pos ⟨hi, hj⟩] + · rw [if_neg hj, if_neg (fun hc ↦ hj hc.2)] + · rw [if_neg hi, if_neg (fun hc ↦ hi hc.1)] + exact rowGet_of_size_le (Nat.zero_le j) + +private theorem matrixWidth_eq_getD_size [Zero F] (M : PolynomialMatrix F) : + MatrixWidth M = (M.getD 0 #[]).size := by + unfold MatrixWidth + rw [Array.getD_eq_getD_getElem?] + cases M[0]? <;> rfl + +/-- Width of `ofFn`. -/ +theorem MatrixWidth_ofFn [Zero F] (rows width : Nat) + (entry : Nat → Nat → CPolynomial F) : + MatrixWidth (ofFn rows width entry) = if rows = 0 then 0 else width := by + rw [matrixWidth_eq_getD_size, getD_ofFn] + by_cases h : rows = 0 + · subst h + rw [if_neg (by omega), if_pos rfl] + rfl + · rw [if_pos (by omega), if_neg h] + simp only [List.size_toArray, List.length_map, List.length_range] + +/-- Width of a square `ofFn`. -/ +theorem MatrixWidth_ofFn_square [Zero F] (n : Nat) + (entry : Nat → Nat → CPolynomial F) : + MatrixWidth (ofFn n n entry) = n := by + rw [MatrixWidth_ofFn] + split <;> omega + +/-- Two `ofFn` matrices with entrywise-equal in-range entries are equal. -/ +theorem ofFn_congr [Zero F] {rows width : Nat} {f g : Nat → Nat → CPolynomial F} + (h : ∀ i < rows, ∀ j < width, f i j = g i j) : + ofFn rows width f = ofFn rows width g := by + rw [ofFn, ofFn] + refine congrArg List.toArray (List.map_congr_left fun i hi ↦ ?_) + refine congrArg List.toArray (List.map_congr_left fun j hj ↦ ?_) + exact h i (List.mem_range.mp hi) j (List.mem_range.mp hj) + +private theorem map_eq_ofFn [Zero F] {M : PolynomialMatrix F} {w : Nat} + {f : PolynomialRow F → PolynomialRow F} {entry : Nat → Nat → CPolynomial F} + (hf : ∀ i, i < M.size → + f (M.getD i #[]) = ((List.range w).map (entry i)).toArray) : + M.map f = ofFn M.size w entry := by + refine Array.ext (by rw [Array.size_map, ofFn_size]) fun i hi₁ hi₂ ↦ ?_ + rw [Array.size_map] at hi₁ + rw [Array.getElem_map, getElem_ofFn, ← getD_of_lt M #[] hi₁] + exact hf i hi₁ + +/-! ## Naive product semantics -/ + +/-- Width of a naive row-by-matrix product. -/ +theorem rowMulMatrixWith_size [Semiring F] [BEq F] [LawfulBEq F] + (mulCtx : CPolynomial.MulContext F) (row : PolynomialRow F) + (M : PolynomialMatrix F) : + (rowMulMatrixWith mulCtx row M).size = MatrixWidth M := by + simp only [rowMulMatrixWith, List.size_toArray, List.length_map, List.length_range] + +/-- Entry semantics of the naive row-by-matrix product. -/ +theorem rowGet_rowMulMatrixWith [Semiring F] [BEq F] [LawfulBEq F] + (mulCtx : CPolynomial.MulContext F) (row : PolynomialRow F) + (M : PolynomialMatrix F) {j : Nat} (hj : j < MatrixWidth M) : + rowGet (rowMulMatrixWith mulCtx row M) j = + ∑ k ∈ Finset.range row.size, rowGet row k * rowGet (M.getD k #[]) j := by + rw [rowMulMatrixWith, rowGet_list_range_map, if_pos hj, foldl_add_eq_sum] + simp only [mulCtx.mul_eq_mul] + +/-- The naive row-by-matrix product is zero past the matrix width. -/ +theorem rowGet_rowMulMatrixWith_of_width_le [Semiring F] [BEq F] [LawfulBEq F] + (mulCtx : CPolynomial.MulContext F) (row : PolynomialRow F) + (M : PolynomialMatrix F) {j : Nat} (hj : MatrixWidth M ≤ j) : + rowGet (rowMulMatrixWith mulCtx row M) j = 0 := + rowGet_of_size_le (by rw [rowMulMatrixWith_size]; exact hj) + +private theorem sum_entry_eq_of_matrix_le [Semiring F] [BEq F] [LawfulBEq F] + (row : PolynomialRow F) (B : PolynomialMatrix F) (j : Nat) {n : Nat} + (hn : B.size ≤ n) : + ∑ k ∈ Finset.range n, rowGet row k * rowGet (B.getD k #[]) j = + ∑ k ∈ Finset.range B.size, rowGet row k * rowGet (B.getD k #[]) j := + Finset.eventually_constant_sum + (fun k hk ↦ by rw [rowGet_getD_of_size_le hk, CPolynomial.mul_zero]) hn + +private theorem sum_entry_eq_of_row_le [Semiring F] [BEq F] [LawfulBEq F] + (row : PolynomialRow F) (B : PolynomialMatrix F) (j : Nat) {n : Nat} + (hn : row.size ≤ n) : + ∑ k ∈ Finset.range n, rowGet row k * rowGet (B.getD k #[]) j = + ∑ k ∈ Finset.range row.size, rowGet row k * rowGet (B.getD k #[]) j := + Finset.eventually_constant_sum + (fun k hk ↦ by rw [rowGet_of_size_le hk, CPolynomial.zero_mul]) hn + +private theorem sum_entry_row_size_eq [Semiring F] [BEq F] [LawfulBEq F] + (row : PolynomialRow F) (B : PolynomialMatrix F) (j : Nat) : + ∑ k ∈ Finset.range row.size, rowGet row k * rowGet (B.getD k #[]) j = + ∑ k ∈ Finset.range B.size, rowGet row k * rowGet (B.getD k #[]) j := by + rw [← sum_entry_eq_of_row_le row B j (Nat.le_max_left row.size B.size), + sum_entry_eq_of_matrix_le row B j (Nat.le_max_right row.size B.size)] + +/-- The naive matrix product as an `ofFn` matrix of convolution sums. -/ +theorem mulWith_eq_ofFn [Semiring F] [BEq F] [LawfulBEq F] + (mulCtx : CPolynomial.MulContext F) (A B : PolynomialMatrix F) : + mulWith mulCtx A B = + ofFn A.size (MatrixWidth B) (fun i j ↦ + ∑ k ∈ Finset.range B.size, + rowGet (A.getD i #[]) k * rowGet (B.getD k #[]) j) := by + refine map_eq_ofFn fun i hi ↦ ?_ + rw [rowMulMatrixWith] + refine congrArg List.toArray (List.map_congr_left fun j hj ↦ ?_) + rw [foldl_add_eq_sum] + simp only [mulCtx.mul_eq_mul] + exact sum_entry_row_size_eq (A.getD i #[]) B j + +/-- Row count of the naive matrix product. -/ +theorem mulWith_size [Semiring F] [BEq F] [LawfulBEq F] + (mulCtx : CPolynomial.MulContext F) (A B : PolynomialMatrix F) : + (mulWith mulCtx A B).size = A.size := by + rw [mulWith, Array.size_map] + +/-- Rows of the naive matrix product. -/ +theorem mulWith_getD [Semiring F] [BEq F] [LawfulBEq F] + (mulCtx : CPolynomial.MulContext F) (A B : PolynomialMatrix F) {i : Nat} + (hi : i < A.size) : + (mulWith mulCtx A B).getD i #[] = rowMulMatrixWith mulCtx (A.getD i #[]) B := by + rw [mulWith, Array.getD_map_of_lt _ _ _ hi, getD_of_lt A #[] hi] + +/-- Row list of the naive matrix product. -/ +theorem matrixRows_mulWith [Semiring F] [BEq F] [LawfulBEq F] + (mulCtx : CPolynomial.MulContext F) (A B : PolynomialMatrix F) : + MatrixRows (mulWith mulCtx A B) = + (MatrixRows A).map fun row ↦ rowMulMatrixWith mulCtx row B := by + rw [MatrixRows, MatrixRows, mulWith, Array.toList_map] + +/-! ## Bounded product correctness -/ + +/-- The degree-capped row product equals the naive row product. -/ +theorem rowMulMatrixBoundedWith_eq_rowMulMatrixWith [Semiring F] [BEq F] [LawfulBEq F] + (lowCtx : MulLowContext F) (row : PolynomialRow F) (M : PolynomialMatrix F) : + rowMulMatrixBoundedWith lowCtx row M = + rowMulMatrixWith lowCtx.mulContext row M := by + simp only [rowMulMatrixBoundedWith, rowMulMatrixWith] + refine congrArg List.toArray (List.map_congr_left fun j hj ↦ ?_) + rw [foldl_add_eq_sum, foldl_add_eq_sum] + refine Finset.sum_congr rfl fun k hk ↦ ?_ + rw [mulLowXWith, lowCtx.mulLow_eq, lowCtx.mulContext.mul_eq_mul] + refine truncateX_mul_of_productCoeffCap_le ?_ + rw [rowMulMatrixEntryCoeffCap] + exact le_foldl_max + (fun k ↦ productCoeffCap (rowGet row k) (rowGet (M.getD k #[]) j)) _ _ + (List.mem_range.mpr (Finset.mem_range.mp hk)) + +/-- The degree-capped matrix product equals the naive matrix product. -/ +theorem mulBoundedWith_eq_mulWith [Semiring F] [BEq F] [LawfulBEq F] + (lowCtx : MulLowContext F) (A B : PolynomialMatrix F) : + mulBoundedWith lowCtx A B = mulWith lowCtx.mulContext A B := by + rw [mulBoundedWith, mulWith] + exact congrArg (fun f ↦ Array.map f A) + (funext fun row ↦ rowMulMatrixBoundedWith_eq_rowMulMatrixWith lowCtx row B) + +/-! ## Column-truncated naive product semantics -/ + +/-- Entry access for column truncation of a row. -/ +theorem rowGet_rowTruncateColumns [Semiring F] [BEq F] [LawfulBEq F] + (orders : Array Nat) (row : PolynomialRow F) (j : Nat) : + rowGet (rowTruncateColumns orders row) j = + truncateX (orders.getD j 0) (rowGet row j) := by + rw [rowTruncateColumns, rowGet_list_range_map] + split + · rfl + · rename_i h + rw [rowGet_of_size_le (Nat.le_of_not_lt h), truncateX_zero] + +/-- Row count of a column-truncated matrix. -/ +theorem truncateColumns_size [Semiring F] [BEq F] [LawfulBEq F] + (orders : Array Nat) (M : PolynomialMatrix F) : + (truncateColumns orders M).size = M.size := by + rw [truncateColumns, Array.size_map] + +/-- Rows of a column-truncated matrix. -/ +theorem truncateColumns_getD [Semiring F] [BEq F] [LawfulBEq F] + (orders : Array Nat) (M : PolynomialMatrix F) {i : Nat} (hi : i < M.size) : + (truncateColumns orders M).getD i #[] = + rowTruncateColumns orders (M.getD i #[]) := by + rw [truncateColumns, Array.getD_map_of_lt _ _ _ hi, getD_of_lt M #[] hi] + +/-- The truncated row product is the truncation of the naive row product. -/ +theorem rowMulMatrixTruncColumnWith_eq [Semiring F] [BEq F] [LawfulBEq F] + (lowCtx : MulLowContext F) (orders : Array Nat) (row : PolynomialRow F) + (M : PolynomialMatrix F) : + rowMulMatrixTruncColumnWith lowCtx orders row M = + rowTruncateColumns orders (rowMulMatrixWith lowCtx.mulContext row M) := by + simp only [rowMulMatrixTruncColumnWith, rowTruncateColumns, rowMulMatrixWith_size] + refine congrArg List.toArray (List.map_congr_left fun j hj ↦ ?_) + rw [foldl_add_eq_sum, + rowGet_rowMulMatrixWith lowCtx.mulContext row M (List.mem_range.mp hj), + truncateX_sum] + refine Finset.sum_congr rfl fun k _ ↦ ?_ + rw [mulLowXWith, lowCtx.mulLow_eq] + +/-- The column-truncated product is the truncation of the naive product. -/ +theorem mulTruncColumnWith_eq [Semiring F] [BEq F] [LawfulBEq F] + (lowCtx : MulLowContext F) (orders : Array Nat) (A B : PolynomialMatrix F) : + mulTruncColumnWith lowCtx orders A B = + truncateColumns orders (mulWith lowCtx.mulContext A B) := by + rw [mulTruncColumnWith, truncateColumns, mulWith, Array.map_map] + refine congrArg (fun f ↦ Array.map f A) (funext fun row ↦ ?_) + simp only [Function.comp_apply] + exact rowMulMatrixTruncColumnWith_eq lowCtx orders row B + +/-! ## Structural `ofFn` rewrites for the block combinators -/ + +/-- Pointwise addition of equally shaped `ofFn` matrices. -/ +theorem add_ofFn [Semiring F] [BEq F] [LawfulBEq F] (rows width : Nat) + (f g : Nat → Nat → CPolynomial F) : + add (ofFn rows width f) (ofFn rows width g) = + ofFn rows width (fun i j ↦ f i j + g i j) := by + by_cases h : rows = 0 + · subst h + simp [add, ofFn] + · simp only [add, ofFn_size, MatrixWidth_ofFn, if_neg h, Nat.max_self] + refine ofFn_congr fun i hi j hj ↦ ?_ + rw [rowGet_ofFn, rowGet_ofFn, if_pos ⟨hi, hj⟩, if_pos ⟨hi, hj⟩] + +/-- Pointwise subtraction of equally shaped `ofFn` matrices. -/ +theorem sub_ofFn [Ring F] [BEq F] [LawfulBEq F] (rows width : Nat) + (f g : Nat → Nat → CPolynomial F) : + sub (ofFn rows width f) (ofFn rows width g) = + ofFn rows width (fun i j ↦ f i j - g i j) := by + by_cases h : rows = 0 + · subst h + simp [sub, ofFn] + · simp only [sub, ofFn_size, MatrixWidth_ofFn, if_neg h, Nat.max_self] + refine ofFn_congr fun i hi j hj ↦ ?_ + rw [rowGet_ofFn, rowGet_ofFn, if_pos ⟨hi, hj⟩, if_pos ⟨hi, hj⟩] + +/-- The naive product of square `ofFn` matrices. -/ +theorem mulWith_ofFn_ofFn [Semiring F] [BEq F] [LawfulBEq F] + (mulCtx : CPolynomial.MulContext F) (n : Nat) + (f g : Nat → Nat → CPolynomial F) : + mulWith mulCtx (ofFn n n f) (ofFn n n g) = + ofFn n n (fun i j ↦ ∑ k ∈ Finset.range n, f i k * g k j) := by + rw [mulWith_eq_ofFn, ofFn_size, MatrixWidth_ofFn_square] + refine ofFn_congr fun i hi j hj ↦ ?_ + rw [ofFn_size] + refine Finset.sum_congr rfl fun k hk ↦ ?_ + have hk' := Finset.mem_range.mp hk + rw [rowGet_ofFn, rowGet_ofFn, if_pos ⟨hi, hk'⟩, if_pos ⟨hk', hj⟩] + +/-- Column truncation of an `ofFn` matrix. -/ +theorem truncateColumns_ofFn [Semiring F] [BEq F] [LawfulBEq F] (orders : Array Nat) + (rows width : Nat) (entry : Nat → Nat → CPolynomial F) : + truncateColumns orders (ofFn rows width entry) = + ofFn rows width (fun i j ↦ truncateX (orders.getD j 0) (entry i j)) := by + rw [truncateColumns] + have h : ∀ i, i < (ofFn rows width entry).size → + rowTruncateColumns orders ((ofFn rows width entry).getD i #[]) = + ((List.range width).map + (fun j ↦ truncateX (orders.getD j 0) (entry i j))).toArray := by + intro i hi + rw [ofFn_size] at hi + rw [getD_ofFn, if_pos hi, rowTruncateColumns] + simp only [List.size_toArray, List.length_map, List.length_range] + refine congrArg List.toArray (List.map_congr_left fun j hj ↦ ?_) + rw [rowGet_list_range_map, if_pos (List.mem_range.mp hj)] + rw [map_eq_ofFn h, ofFn_size] + +/-! ## Strassen seven-product sum identities -/ + +private theorem mul_neg' [Ring F] [BEq F] [LawfulBEq F] (p q : CPolynomial F) : + p * -q = -(p * q) := + eq_neg_of_add_eq_zero_left + (by rw [← CPolynomial.mul_add, CPolynomial.neg_add_cancel, CPolynomial.mul_zero]) + +private theorem neg_mul' [Ring F] [BEq F] [LawfulBEq F] (p q : CPolynomial F) : + (-p) * q = -(p * q) := + eq_neg_of_add_eq_zero_left + (by rw [← CPolynomial.add_mul, CPolynomial.neg_add_cancel, CPolynomial.zero_mul]) + +private theorem mul_sub' [Ring F] [BEq F] [LawfulBEq F] (p q r : CPolynomial F) : + p * (q - r) = p * q - p * r := by + rw [sub_eq_add_neg, CPolynomial.mul_add, mul_neg', ← sub_eq_add_neg] + +private theorem sub_mul' [Ring F] [BEq F] [LawfulBEq F] (p q r : CPolynomial F) : + (p - q) * r = p * r - q * r := by + rw [sub_eq_add_neg, CPolynomial.add_mul, neg_mul', ← sub_eq_add_neg] + +private theorem strassen_sum₁₁ [Ring F] [BEq F] [LawfulBEq F] (h : Nat) + (x₁ x₂ x₄ y₁ y₃ y₄ : Nat → CPolynomial F) : + ((∑ k ∈ Finset.range h, (x₁ k + x₄ k) * (y₁ k + y₄ k)) + + (∑ k ∈ Finset.range h, x₄ k * (y₃ k - y₁ k)) - + ∑ k ∈ Finset.range h, (x₁ k + x₂ k) * y₄ k) + + ∑ k ∈ Finset.range h, (x₂ k - x₄ k) * (y₃ k + y₄ k) = + (∑ k ∈ Finset.range h, x₁ k * y₁ k) + ∑ k ∈ Finset.range h, x₂ k * y₃ k := by + simp only [← Finset.sum_add_distrib, ← Finset.sum_sub_distrib] + refine Finset.sum_congr rfl fun k _ ↦ ?_ + simp only [CPolynomial.mul_add, CPolynomial.add_mul, mul_sub', sub_mul'] + abel + +private theorem strassen_sum₁₂ [Ring F] [BEq F] [LawfulBEq F] (h : Nat) + (x₁ x₂ y₂ y₄ : Nat → CPolynomial F) : + (∑ k ∈ Finset.range h, x₁ k * (y₂ k - y₄ k)) + + ∑ k ∈ Finset.range h, (x₁ k + x₂ k) * y₄ k = + (∑ k ∈ Finset.range h, x₁ k * y₂ k) + ∑ k ∈ Finset.range h, x₂ k * y₄ k := by + simp only [← Finset.sum_add_distrib] + refine Finset.sum_congr rfl fun k _ ↦ ?_ + simp only [CPolynomial.add_mul, mul_sub'] + abel + +private theorem strassen_sum₂₁ [Ring F] [BEq F] [LawfulBEq F] (h : Nat) + (x₃ x₄ y₁ y₃ : Nat → CPolynomial F) : + (∑ k ∈ Finset.range h, (x₃ k + x₄ k) * y₁ k) + + ∑ k ∈ Finset.range h, x₄ k * (y₃ k - y₁ k) = + (∑ k ∈ Finset.range h, x₃ k * y₁ k) + ∑ k ∈ Finset.range h, x₄ k * y₃ k := by + simp only [← Finset.sum_add_distrib] + refine Finset.sum_congr rfl fun k _ ↦ ?_ + simp only [CPolynomial.add_mul, mul_sub'] + abel + +private theorem strassen_sum₂₂ [Ring F] [BEq F] [LawfulBEq F] (h : Nat) + (x₁ x₃ x₄ y₁ y₂ y₄ : Nat → CPolynomial F) : + ((∑ k ∈ Finset.range h, (x₁ k + x₄ k) * (y₁ k + y₄ k)) + + (∑ k ∈ Finset.range h, x₁ k * (y₂ k - y₄ k)) - + ∑ k ∈ Finset.range h, (x₃ k + x₄ k) * y₁ k) + + ∑ k ∈ Finset.range h, (x₃ k - x₁ k) * (y₁ k + y₂ k) = + (∑ k ∈ Finset.range h, x₃ k * y₂ k) + ∑ k ∈ Finset.range h, x₄ k * y₄ k := by + simp only [← Finset.sum_add_distrib, ← Finset.sum_sub_distrib] + refine Finset.sum_congr rfl fun k _ ↦ ?_ + simp only [CPolynomial.mul_add, CPolynomial.add_mul, mul_sub', sub_mul'] + abel + +/-! ## Padding step -/ + +private theorem multiplicationDimension_bounds [Zero F] (A B : PolynomialMatrix F) : + A.size ≤ multiplicationDimension A B ∧ B.size ≤ multiplicationDimension A B ∧ + MatrixWidth B ≤ multiplicationDimension A B := by + unfold multiplicationDimension + omega + +private theorem pad_step [Semiring F] [BEq F] [LawfulBEq F] + (mulCtx : CPolynomial.MulContext F) (A B : PolynomialMatrix F) {s : Nat} + (hA : A.size ≤ s) (hBs : B.size ≤ s) (hBw : MatrixWidth B ≤ s) : + trimShape A.size (MatrixWidth B) (mulWith mulCtx (padSquare s A) (padSquare s B)) = + mulWith mulCtx A B := by + conv_rhs => rw [mulWith_eq_ofFn] + simp only [padSquare, trimShape, block, Nat.zero_add, mulWith_ofFn_ofFn] + refine ofFn_congr fun i hi j hj ↦ ?_ + rw [rowGet_ofFn, if_pos ⟨lt_of_lt_of_le hi hA, lt_of_lt_of_le hj hBw⟩] + exact sum_entry_eq_of_matrix_le (A.getD i #[]) B j hBs + +private theorem trunc_pad_step [Semiring F] [BEq F] [LawfulBEq F] + (mulCtx : CPolynomial.MulContext F) (orders : Array Nat) + (A B : PolynomialMatrix F) {s : Nat} + (hA : A.size ≤ s) (hBs : B.size ≤ s) (hBw : MatrixWidth B ≤ s) : + trimShape A.size (MatrixWidth B) + (truncateColumns (natArraySlice orders 0 s) + (mulWith mulCtx (padSquare s A) (padSquare s B))) = + truncateColumns orders (mulWith mulCtx A B) := by + conv_rhs => rw [mulWith_eq_ofFn, truncateColumns_ofFn] + simp only [padSquare, trimShape, block, Nat.zero_add, mulWith_ofFn_ofFn, + truncateColumns_ofFn] + refine ofFn_congr fun i hi j hj ↦ ?_ + rw [rowGet_ofFn, if_pos ⟨lt_of_lt_of_le hi hA, lt_of_lt_of_le hj hBw⟩, + natArraySlice_getD, if_pos (lt_of_lt_of_le hj hBw), Nat.zero_add] + exact congrArg (truncateX (orders.getD j 0)) + (sum_entry_eq_of_matrix_le (A.getD i #[]) B j hBs) + +/-! ## Full Strassen correctness -/ + +/-- The fuel-bounded Strassen product equals the naive matrix product. -/ +theorem mulStrassenWithFuel_eq_mulWith [Ring F] [BEq F] [LawfulBEq F] + (lowCtx : MulLowContext F) (leafCutoff fuel : Nat) (A B : PolynomialMatrix F) : + mulStrassenWithFuel lowCtx leafCutoff fuel A B = mulWith lowCtx.mulContext A B := by + induction fuel generalizing A B with + | zero => + simp only [mulStrassenWithFuel] + exact mulBoundedWith_eq_mulWith lowCtx A B + | succ fuel ih => + simp only [mulStrassenWithFuel] + split + · exact mulBoundedWith_eq_mulWith lowCtx A B + split + · rename_i hguard + simp only [Bool.and_eq_true, beq_iff_eq] at hguard + obtain ⟨⟨⟨_hAw, hBs⟩, hBw⟩, heven⟩ := hguard + simp only [ih] + generalize hh : A.size / 2 = h + have hAs : A.size = h + h := by omega + conv_rhs => rw [mulWith_eq_ofFn] + simp only [hBw, hBs, hAs] + simp only [block, joinSquareBlocks, two_mul, Nat.zero_add, add_ofFn, sub_ofFn, + mulWith_ofFn_ofFn] + refine ofFn_congr fun i hi j hj ↦ ?_ + by_cases hi' : i < h <;> by_cases hj' : j < h + · rw [if_pos hi', if_pos hj', rowGet_ofFn, if_pos ⟨hi', hj'⟩, + Finset.sum_range_add] + exact strassen_sum₁₁ h _ _ _ _ _ _ + · obtain ⟨j', rfl⟩ : ∃ j', j = h + j' := ⟨j - h, by omega⟩ + have hj'' : j' < h := by omega + rw [if_pos hi', if_neg hj', Nat.add_sub_cancel_left, rowGet_ofFn, + if_pos ⟨hi', hj''⟩, Finset.sum_range_add] + exact strassen_sum₁₂ h _ _ _ _ + · obtain ⟨i', rfl⟩ : ∃ i', i = h + i' := ⟨i - h, by omega⟩ + have hi'' : i' < h := by omega + rw [if_neg hi', Nat.add_sub_cancel_left, if_pos hj', rowGet_ofFn, + if_pos ⟨hi'', hj'⟩, Finset.sum_range_add] + exact strassen_sum₂₁ h _ _ _ _ + · obtain ⟨i', rfl⟩ : ∃ i', i = h + i' := ⟨i - h, by omega⟩ + obtain ⟨j', rfl⟩ : ∃ j', j = h + j' := ⟨j - h, by omega⟩ + have hi'' : i' < h := by omega + have hj'' : j' < h := by omega + rw [if_neg hi', if_neg hj', Nat.add_sub_cancel_left, + Nat.add_sub_cancel_left, rowGet_ofFn, if_pos ⟨hi'', hj''⟩, + Finset.sum_range_add] + exact strassen_sum₂₂ h _ _ _ _ _ _ + · rw [ih] + obtain ⟨hdA, hdB, hdW⟩ := multiplicationDimension_bounds A B + have hs := le_nextPowerOfTwoAtLeast (multiplicationDimension A B) + exact pad_step lowCtx.mulContext A B (hdA.trans hs) (hdB.trans hs) + (hdW.trans hs) + +/-- The Strassen product equals the naive matrix product. -/ +theorem mulStrassenWith_eq_mulWith [Ring F] [BEq F] [LawfulBEq F] + (lowCtx : MulLowContext F) (leafCutoff : Nat) (A B : PolynomialMatrix F) : + mulStrassenWith lowCtx leafCutoff A B = mulWith lowCtx.mulContext A B := + mulStrassenWithFuel_eq_mulWith lowCtx leafCutoff (multiplicationDimension A B + 1) A B + +/-- Row count of the Strassen product. -/ +theorem mulStrassenWith_size [Ring F] [BEq F] [LawfulBEq F] + (lowCtx : MulLowContext F) (leafCutoff : Nat) (A B : PolynomialMatrix F) : + (mulStrassenWith lowCtx leafCutoff A B).size = A.size := by + rw [mulStrassenWith_eq_mulWith, mulWith_size] + +/-- Rows of the Strassen product are the naive row-by-matrix products. -/ +theorem mulStrassenWith_getD [Ring F] [BEq F] [LawfulBEq F] + (lowCtx : MulLowContext F) (leafCutoff : Nat) (A B : PolynomialMatrix F) + {i : Nat} (hi : i < A.size) : + (mulStrassenWith lowCtx leafCutoff A B).getD i #[] = + rowMulMatrixWith lowCtx.mulContext (A.getD i #[]) B := by + rw [mulStrassenWith_eq_mulWith, mulWith_getD lowCtx.mulContext A B hi] + +/-- Row list of the Strassen product. -/ +theorem matrixRows_mulStrassenWith [Ring F] [BEq F] [LawfulBEq F] + (lowCtx : MulLowContext F) (leafCutoff : Nat) (A B : PolynomialMatrix F) : + MatrixRows (mulStrassenWith lowCtx leafCutoff A B) = + (MatrixRows A).map fun row ↦ rowMulMatrixWith lowCtx.mulContext row B := by + rw [mulStrassenWith_eq_mulWith, matrixRows_mulWith] + +/-! ## Column-truncated Strassen correctness -/ + +/-- The fuel-bounded column-truncated Strassen product equals the +column-truncated naive matrix product. -/ +theorem mulTruncColumnStrassenWithFuel_eq_truncateColumns [Ring F] [BEq F] [LawfulBEq F] + (lowCtx : MulLowContext F) (leafCutoff fuel : Nat) (orders : Array Nat) + (A B : PolynomialMatrix F) : + mulTruncColumnStrassenWithFuel lowCtx leafCutoff fuel orders A B = + truncateColumns orders (mulWith lowCtx.mulContext A B) := by + induction fuel generalizing orders A B with + | zero => + simp only [mulTruncColumnStrassenWithFuel] + exact mulTruncColumnWith_eq lowCtx orders A B + | succ fuel ih => + simp only [mulTruncColumnStrassenWithFuel] + split + · exact mulTruncColumnWith_eq lowCtx orders A B + split + · rename_i hguard + simp only [Bool.and_eq_true, beq_iff_eq] at hguard + obtain ⟨⟨⟨_hAw, hBs⟩, hBw⟩, heven⟩ := hguard + simp only [ih] + generalize hh : A.size / 2 = h + have hAs : A.size = h + h := by omega + conv_rhs => rw [mulWith_eq_ofFn, truncateColumns_ofFn] + simp only [hBw, hBs, hAs] + simp only [block, joinSquareBlocks, two_mul, Nat.zero_add, add_ofFn, sub_ofFn, + mulWith_ofFn_ofFn, truncateColumns_ofFn] + refine ofFn_congr fun i hi j hj ↦ ?_ + by_cases hi' : i < h <;> by_cases hj' : j < h + · rw [if_pos hi', if_pos hj', rowGet_ofFn, if_pos ⟨hi', hj'⟩] + simp only [natArraySlice_getD, maxNatArrays_getD, if_pos hj', Nat.zero_add] + have hmin₁ : min (orders.getD j 0) + (max (orders.getD j 0) (orders.getD (h + j) 0)) = orders.getD j 0 := + Nat.min_eq_left (Nat.le_max_left _ _) + simp only [truncateX_add, truncateX_sub, truncateX_truncateX, hmin₁, + Nat.min_self] + simp only [← truncateX_add, ← truncateX_sub] + rw [Finset.sum_range_add] + exact congrArg (truncateX (orders.getD j 0)) (strassen_sum₁₁ h _ _ _ _ _ _) + · obtain ⟨j', rfl⟩ : ∃ j', j = h + j' := ⟨j - h, by omega⟩ + have hj'' : j' < h := by omega + rw [if_pos hi', if_neg hj', Nat.add_sub_cancel_left, rowGet_ofFn, + if_pos ⟨hi', hj''⟩] + simp only [natArraySlice_getD, maxNatArrays_getD, if_pos hj'', Nat.zero_add] + have hmin₁ : min (orders.getD (h + j') 0) + (max (orders.getD j' 0) (orders.getD (h + j') 0)) = + orders.getD (h + j') 0 := + Nat.min_eq_left (Nat.le_max_right _ _) + simp only [truncateX_add, truncateX_truncateX, hmin₁, Nat.min_self] + simp only [← truncateX_add] + rw [Finset.sum_range_add] + exact congrArg (truncateX (orders.getD (h + j') 0)) + (strassen_sum₁₂ h _ _ _ _) + · obtain ⟨i', rfl⟩ : ∃ i', i = h + i' := ⟨i - h, by omega⟩ + have hi'' : i' < h := by omega + rw [if_neg hi', Nat.add_sub_cancel_left, if_pos hj', rowGet_ofFn, + if_pos ⟨hi'', hj'⟩] + simp only [natArraySlice_getD, maxNatArrays_getD, if_pos hj', Nat.zero_add] + have hmin₁ : min (orders.getD j 0) + (max (orders.getD j 0) (orders.getD (h + j) 0)) = orders.getD j 0 := + Nat.min_eq_left (Nat.le_max_left _ _) + simp only [truncateX_add, truncateX_truncateX, hmin₁, Nat.min_self] + simp only [← truncateX_add] + rw [Finset.sum_range_add] + exact congrArg (truncateX (orders.getD j 0)) (strassen_sum₂₁ h _ _ _ _) + · obtain ⟨i', rfl⟩ : ∃ i', i = h + i' := ⟨i - h, by omega⟩ + obtain ⟨j', rfl⟩ : ∃ j', j = h + j' := ⟨j - h, by omega⟩ + have hi'' : i' < h := by omega + have hj'' : j' < h := by omega + rw [if_neg hi', if_neg hj', Nat.add_sub_cancel_left, + Nat.add_sub_cancel_left, rowGet_ofFn, if_pos ⟨hi'', hj''⟩] + simp only [natArraySlice_getD, maxNatArrays_getD, if_pos hj'', Nat.zero_add] + have hmin₁ : min (orders.getD (h + j') 0) + (max (orders.getD j' 0) (orders.getD (h + j') 0)) = + orders.getD (h + j') 0 := + Nat.min_eq_left (Nat.le_max_right _ _) + simp only [truncateX_add, truncateX_sub, truncateX_truncateX, hmin₁, + Nat.min_self] + simp only [← truncateX_add, ← truncateX_sub] + rw [Finset.sum_range_add] + exact congrArg (truncateX (orders.getD (h + j') 0)) + (strassen_sum₂₂ h _ _ _ _ _ _) + · rw [ih] + obtain ⟨hdA, hdB, hdW⟩ := multiplicationDimension_bounds A B + have hs := le_nextPowerOfTwoAtLeast (multiplicationDimension A B) + exact trunc_pad_step lowCtx.mulContext orders A B (hdA.trans hs) + (hdB.trans hs) (hdW.trans hs) + +/-- The column-truncated Strassen product equals the column-truncated naive +matrix product. -/ +theorem mulTruncColumnStrassenWith_eq_truncateColumns [Ring F] [BEq F] [LawfulBEq F] + (lowCtx : MulLowContext F) (leafCutoff : Nat) (orders : Array Nat) + (A B : PolynomialMatrix F) : + mulTruncColumnStrassenWith lowCtx leafCutoff orders A B = + truncateColumns orders (mulWith lowCtx.mulContext A B) := + mulTruncColumnStrassenWithFuel_eq_truncateColumns lowCtx leafCutoff + (multiplicationDimension A B + 1) orders A B + +/-- Row count of the column-truncated Strassen product. -/ +theorem mulTruncColumnStrassenWith_size [Ring F] [BEq F] [LawfulBEq F] + (lowCtx : MulLowContext F) (leafCutoff : Nat) (orders : Array Nat) + (A B : PolynomialMatrix F) : + (mulTruncColumnStrassenWith lowCtx leafCutoff orders A B).size = A.size := by + rw [mulTruncColumnStrassenWith_eq_truncateColumns, truncateColumns_size, + mulWith_size] + +/-- Entries of the column-truncated Strassen product are the order-truncated +naive product entries. -/ +theorem mulTruncColumnStrassenWith_entry [Ring F] [BEq F] [LawfulBEq F] + (lowCtx : MulLowContext F) (leafCutoff : Nat) (orders : Array Nat) + (A B : PolynomialMatrix F) {i : Nat} (hi : i < A.size) (j : Nat) : + rowGet ((mulTruncColumnStrassenWith lowCtx leafCutoff orders A B).getD i #[]) j = + truncateX (orders.getD j 0) + (rowGet (rowMulMatrixWith lowCtx.mulContext (A.getD i #[]) B) j) := by + rw [mulTruncColumnStrassenWith_eq_truncateColumns, + truncateColumns_getD orders _ (by rw [mulWith_size]; exact hi), + mulWith_getD lowCtx.mulContext A B hi, rowGet_rowTruncateColumns] + +end PolynomialMatrix + +end CompPoly From 944d6fef55c78d35f036560310d8fb4d477b3548 Mon Sep 17 00:00:00 2001 From: Valerii Huhnin Date: Thu, 11 Jun 2026 14:09:33 +0000 Subject: [PATCH 05/10] Approximant interpolation performance improvement --- .../ApproximantBasis/Algorithm.lean | 1 + .../ApproximantBasis/Correctness.lean | 25 +- .../Approximant/Correctness.lean | 30 +- .../Approximant/ModularEquation.lean | 316 +++++++++++------- .../Approximant/ModularEquation/Basic.lean | 223 +++++++----- .../ModularEquation/Completeness.lean | 23 +- .../PolynomialMatrix/Approximant.lean | 2 +- 7 files changed, 382 insertions(+), 238 deletions(-) diff --git a/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Algorithm.lean b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Algorithm.lean index 18cdc94c..a99516e9 100644 --- a/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Algorithm.lean +++ b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Algorithm.lean @@ -44,6 +44,7 @@ def approximantBasisPositiveInterpolate let R := CPolynomial.interpolateCoefficientFormWithVanishing E G points let data := buildGSModularDataWithRG solver.mulContext solver.modContext R G params let basis := solver.solutionBasis (modularEquation data) data.shift + (some params.weightedDegreeBound) match leastShiftedDegreeChoice? basis data.shift with | none => none | some choice => diff --git a/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Correctness.lean b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Correctness.lean index c87cac7a..a50587ac 100644 --- a/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Correctness.lean +++ b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Correctness.lean @@ -244,7 +244,7 @@ theorem approximantBasisInterpolate_sound set data := buildGSModularDataWithRG solver.mulContext solver.modContext R G params with hdata set basis := solver.solutionBasis (modularEquation data) data.shift - with hbasis + (some params.weightedDegreeBound) with hbasis change (match leastShiftedDegreeChoice? basis data.shift with | none => none | some choice => @@ -271,7 +271,7 @@ theorem approximantBasisInterpolate_sound Array.getElem?_eq_getElem hindex, Option.getD_some] exact Array.getElem_mem_toList hindex have hsat := solver.sound (modularEquation data) data.shift - choice.row hmem + (some params.weightedDegreeBound) choice.row hmem -- Truncate the chosen row to the interpolation width. set width := interpolationWidth params with hwidthdef set row' := CBivariate.toCoeffRow width @@ -465,7 +465,7 @@ theorem approximantBasisInterpolate_complete set data := buildGSModularDataWithRG solver.mulContext solver.modContext R G params with hdata set basis := solver.solutionBasis (modularEquation data) data.shift - with hbasis + (some params.weightedDegreeBound) with hbasis set width := interpolationWidth params with hwidthdef -- The witness coefficient row. set row₀ := CBivariate.toCoeffRow width Q₀ with hrow₀ @@ -543,12 +543,19 @@ theorem approximantBasisInterpolate_complete CBivariate.weightedDegreeShift (yWeight params) width := rfl rw [hdatashift, hsolwidth, CBivariate.weightedDegreeShift] simp - -- Apply the solver completeness/minimality contract. - rcases solver.complete_minimal (modularEquation data) data.shift row₀ hmonic hcols - hshiftsize hsat₀ hnz₀ (by omega) with - ⟨hbasisWidth, basisRow, bDeg, hbMem, hbDeg, hbMin⟩ - have hbDegLe : bDeg ≤ params.weightedDegreeBound := - le_trans (hbMin d₀ hd₀) hd₀le + -- Apply the solver completeness/minimality contract with the GS + -- weighted-degree bound: the witness row is within the bound, so the + -- returned basis contains a row whose shifted degree meets it. + rcases solver.complete_minimal (modularEquation data) data.shift + (some params.weightedDegreeBound) row₀ d₀ hmonic hcols + hshiftsize hsat₀ hnz₀ (by omega) hd₀ + (fun bound hbound ↦ by + obtain rfl : params.weightedDegreeBound = bound := + Option.some.inj hbound + exact hd₀le) with + ⟨hbasisWidth, basisRow, bDeg, hbMem, hbDeg, hbLe⟩ + have hbDegLe : bDeg ≤ params.weightedDegreeBound := by + rwa [Option.getD_some] at hbLe -- Select the least shifted-degree basis row. rcases List.getElem_of_mem hbMem with ⟨bIdx, hbIdxList, hbGet⟩ have hbIdx : bIdx < basis.size := by diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/Correctness.lean b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/Correctness.lean index cb157c30..de76cf08 100644 --- a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/Correctness.lean +++ b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/Correctness.lean @@ -25,18 +25,20 @@ variable {F : Type*} [Field F] [BEq F] [LawfulBEq F] equation. -/ theorem modularSolutionBasis_sound (ctx : ModularSolutionBasisContext F) (equation : ModularEquation F) - (shift : Array Nat) {row : PolynomialRow F} - (hrow : row ∈ MatrixRows (ctx.solutionBasis equation shift)) : + (shift : Array Nat) (degreeBound? : Option Nat) {row : PolynomialRow F} + (hrow : row ∈ MatrixRows (ctx.solutionBasis equation shift degreeBound?)) : rowSatisfiesModularBool ctx.mulContext ctx.modContext row equation.matrix equation.moduli = true := - ctx.sound equation shift row hrow + ctx.sound equation shift degreeBound? row hrow -/-- Solution-basis completeness/minimality contract. The final Popov-strength -minimality facts are represented by the context field while proofs are -developed. -/ +/-- Solution-basis completeness/minimality contract, relative to the +caller-supplied degree bound: a solution row within the bound (vacuous for +`none`) is matched by a returned basis row whose shifted degree does not +exceed the bound — the solution's own degree when no bound is supplied. -/ theorem modularSolutionBasis_complete_minimal (ctx : ModularSolutionBasisContext F) (equation : ModularEquation F) - (shift : Array Nat) {row : PolynomialRow F} + (shift : Array Nat) (degreeBound? : Option Nat) {row : PolynomialRow F} + {rowDegree : Nat} (hmonic : ∀ b, b < equation.moduli.size → (equation.moduli.getD b 0).monic) (hcols : equation.moduli.size ≤ MatrixWidth equation.matrix) (hshift : shift.size = equation.solutionWidth) @@ -44,16 +46,18 @@ theorem modularSolutionBasis_complete_minimal rowSatisfiesModularBool ctx.mulContext ctx.modContext row equation.matrix equation.moduli = true) (hnonzero : rowIsZero row = false) - (hwidth : row.size ≤ equation.solutionWidth) : + (hwidth : row.size ≤ equation.solutionWidth) + (hdegree : rowShiftedDegree? row shift = some rowDegree) + (hbound : ∀ bound, degreeBound? = some bound → rowDegree ≤ bound) : (∀ basisRow, - basisRow ∈ MatrixRows (ctx.solutionBasis equation shift) → + basisRow ∈ MatrixRows (ctx.solutionBasis equation shift degreeBound?) → basisRow.size ≤ equation.solutionWidth) ∧ ∃ basisRow degree, - basisRow ∈ MatrixRows (ctx.solutionBasis equation shift) ∧ + basisRow ∈ MatrixRows (ctx.solutionBasis equation shift degreeBound?) ∧ rowShiftedDegree? basisRow shift = some degree ∧ - ∀ rowDegree, rowShiftedDegree? row shift = some rowDegree → - degree ≤ rowDegree := - ctx.complete_minimal equation shift row hmonic hcols hshift hrow hnonzero hwidth + degree ≤ degreeBound?.getD rowDegree := + ctx.complete_minimal equation shift degreeBound? row rowDegree hmonic hcols + hshift hrow hnonzero hwidth hdegree hbound end Approximant diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation.lean b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation.lean index f557d825..a1243ed7 100644 --- a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation.lean +++ b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation.lean @@ -31,145 +31,205 @@ def modularSolutionBasisContextViaPMBasis modContext := modCtx solutionBasis := filteredSolutionBasisViaPMBasis mulCtx modCtx pmCtx sound := by - intro equation shift row hrow + intro equation shift degreeBound? row hrow exact filteredSolutionBasisViaPMBasis_sound hrow complete_minimal := by - -- Certified verification window: solutions at or above the best adaptive - -- degree are dominated by the adaptive rows of `combined`; solutions - -- strictly below it (or with no adaptive row at all) are dominated by a - -- verification row through `me_verification_dominates`, with the fallback - -- solution `e_p * prod(moduli)` covering degrees beyond the saturated - -- window. - intro equation shift row hmonic hcols hshift hsat hnz hwidth + -- With the degree gate passed, the adaptive best itself meets the bound, + -- so the adaptive candidate set suffices. Otherwise the certified gated + -- window takes over: solutions at or above the best adaptive degree are + -- dominated by the adaptive rows of `combined`; solutions inside the + -- window are dominated by a verification row through + -- `me_verification_dominates`, with the fallback solution + -- `e_p * prod(moduli)` covering degrees beyond the saturated window. + intro equation shift degreeBound? row d hmonic hcols hshift hsat hnz hwidth + hd hdbound classical - obtain ⟨d, hd⟩ := me_rowShiftedDegree_isSome (shift := shift) hnz obtain ⟨j0, hj0, hj0ne⟩ := exists_nonzero_entry_of_rowIsZero_false hnz have hpos : 0 < equation.solutionWidth := by omega - -- Main existence in the combined candidate set. - have hmain : ∃ basisRow degree, - basisRow ∈ MatrixRows - ((adaptiveSolutionBasis mulCtx modCtx pmCtx equation shift).filtered ++ - filterModularSolutionRows mulCtx modCtx equation - (verificationSolutionBasisViaPMBasis modCtx pmCtx equation shift - (leastSolutionRowDegree? - (adaptiveSolutionBasis mulCtx modCtx pmCtx equation - shift).filtered shift))) ∧ - rowShiftedDegree? basisRow shift = some degree ∧ degree ≤ d := by - by_cases hcase : ∃ B, leastSolutionRowDegree? - (adaptiveSolutionBasis mulCtx modCtx pmCtx equation shift).filtered - shift = some B ∧ B ≤ d - · -- An adaptive row already dominates. - obtain ⟨B, hB, hBd⟩ := hcase - rw [leastSolutionRowDegree?] at hB - rcases Option.map_eq_some_iff.mp hB with ⟨choice, hchoice, hchoicedeg⟩ + cases hgate : degreeGatePassed degreeBound? + (leastSolutionRowDegree? + (adaptiveSolutionBasis mulCtx modCtx pmCtx equation shift degreeBound?).filtered + shift) with + | true => + -- Gate passed: the adaptive candidate set is returned, and its least + -- row meets the caller's bound. + obtain ⟨bound, best, hBound, hBest, hle⟩ := + degreeGatePassed_eq_true_iff.mp hgate + have hresult : filteredSolutionBasisViaPMBasis mulCtx modCtx pmCtx + equation shift degreeBound? = + (adaptiveSolutionBasis mulCtx modCtx pmCtx equation shift degreeBound?).filtered := by + simp only [filteredSolutionBasisViaPMBasis] + rw [if_pos hgate] + rw [leastSolutionRowDegree?] at hBest + rcases Option.map_eq_some_iff.mp hBest with ⟨choice, hchoice, hchoicedeg⟩ obtain ⟨hidx, hrowEq, hcdeg⟩ := leastShiftedDegreeChoice?_some_valid hchoice - refine ⟨choice.row, choice.degree, ?_, hcdeg, by omega⟩ - rw [MatrixRows, Array.toList_append] - refine List.mem_append.mpr (Or.inl ?_) - rw [hrowEq] - exact me_getD_mem_toList #[] hidx - · -- Route through the certified verification window. - push Not at hcase - have hwindow : ∃ (rowStar : PolynomialRow F) (e : Nat), - rowSatisfiesModularBool mulCtx modCtx rowStar equation.matrix - equation.moduli = true ∧ - rowIsZero rowStar = false ∧ - rowStar.size ≤ equation.solutionWidth ∧ - rowShiftedDegree? rowStar shift = some e ∧ - e ≤ verificationWindowBound equation shift - (leastSolutionRowDegree? - (adaptiveSolutionBasis mulCtx modCtx pmCtx equation - shift).filtered shift) ∧ - e ≤ d := by - by_cases hdbound : d ≤ verificationWindowBound equation shift - (leastSolutionRowDegree? - (adaptiveSolutionBasis mulCtx modCtx pmCtx equation - shift).filtered shift) - · exact ⟨row, d, hsat, hnz, hwidth, hd, hdbound, le_refl d⟩ - · -- The window is saturated at the full pivot window. - have hfull : verificationWindowBound equation shift - (leastSolutionRowDegree? - (adaptiveSolutionBasis mulCtx modCtx pmCtx equation - shift).filtered shift) = - pivotWindowCap equation + maxShiftDegree shift := by - cases hbest : leastSolutionRowDegree? - (adaptiveSolutionBasis mulCtx modCtx pmCtx equation - shift).filtered shift with - | none => - simp [verificationWindowBound, fullWindowDegreeBound] - | some B => - have hdB : d < B := hcase B hbest - rw [hbest] at hdbound - simp only [verificationWindowBound] at hdbound ⊢ - omega - obtain ⟨prow, e, hsatP, hnzP, hsizeP, hdegP, heP⟩ := - me_prodRow_facts mulCtx modCtx equation shift (p := j0) hmonic - hcols (by omega) - exact ⟨prow, e, hsatP, hnzP, le_of_eq hsizeP, hdegP, by omega, - by omega⟩ - obtain ⟨rowStar, e, hsatS, hnzS, hwidthS, hdegS, heB, heD⟩ := hwindow - obtain ⟨bRow, degB, hmem, hdegB, hle⟩ := me_verification_dominates - mulCtx modCtx pmCtx equation shift - (verificationWindowBound equation shift - (leastSolutionRowDegree? - (adaptiveSolutionBasis mulCtx modCtx pmCtx equation - shift).filtered shift)) - hmonic hcols hshift hpos hsatS hnzS hwidthS hdegS heB - refine ⟨bRow, degB, ?_, hdegB, by omega⟩ - rw [MatrixRows, Array.toList_append] - refine List.mem_append.mpr (Or.inr ?_) - exact hmem - obtain ⟨bRow, degB, hmemC, hdegB, hled⟩ := hmain - -- The combined candidate set is nonempty, so the repair branch is skipped. - have hsizepos : 0 < - ((adaptiveSolutionBasis mulCtx modCtx pmCtx equation shift).filtered ++ - filterModularSolutionRows mulCtx modCtx equation - (verificationSolutionBasisViaPMBasis modCtx pmCtx equation shift - (leastSolutionRowDegree? - (adaptiveSolutionBasis mulCtx modCtx pmCtx equation - shift).filtered shift))).size := by - rcases List.getElem_of_mem hmemC with ⟨i, hi, _⟩ - rw [MatrixRows, Array.length_toList] at hi - omega - have hresult : filteredSolutionBasisViaPMBasis mulCtx modCtx pmCtx equation - shift = - (adaptiveSolutionBasis mulCtx modCtx pmCtx equation shift).filtered ++ - filterModularSolutionRows mulCtx modCtx equation - (verificationSolutionBasisViaPMBasis modCtx pmCtx equation shift - (leastSolutionRowDegree? - (adaptiveSolutionBasis mulCtx modCtx pmCtx equation - shift).filtered shift)) := by - simp only [filteredSolutionBasisViaPMBasis] - rw [if_neg (by simp only [beq_iff_eq]; omega)] - constructor - · -- Width discipline of the returned rows. - intro basisRow hbasisRow - rw [hresult, MatrixRows, Array.toList_append, List.mem_append] at hbasisRow - rcases hbasisRow with h | h - · exact le_of_eq (me_adaptiveBasis_width basisRow h) - · have hsub := me_filterModularSolutionRows_subset h - have hsub' : basisRow ∈ MatrixRows (compactNonzeroRows - (principalSolutionRows equation.solutionWidth - (pmCtx.basis - (fullWindowExactNullspaceProblem modCtx equation + refine ⟨?_, choice.row, choice.degree, ?_, hcdeg, ?_⟩ + · intro basisRow hbasisRow + rw [hresult] at hbasisRow + exact le_of_eq (me_adaptiveBasis_width basisRow hbasisRow) + · rw [hresult, MatrixRows, hrowEq] + exact me_getD_mem_toList #[] hidx + · rw [hBound, Option.getD_some] + omega + | false => + -- Gate failed or no bound supplied: the certified union is returned. + have hmain : ∃ basisRow degree, + basisRow ∈ MatrixRows + ((adaptiveSolutionBasis mulCtx modCtx pmCtx equation shift degreeBound?).filtered ++ + filterModularSolutionRows mulCtx modCtx equation + (windowedSolutionBasisViaPMBasis modCtx pmCtx equation shift + (gatedWindowBound equation shift degreeBound? + (leastSolutionRowDegree? + (adaptiveSolutionBasis mulCtx modCtx pmCtx equation + shift degreeBound?).filtered shift)))) ∧ + rowShiftedDegree? basisRow shift = some degree ∧ + degree ≤ degreeBound?.getD d := by + cases hbnd : degreeBound? with + | some bound => + -- Bounded window `min(bound, cap) + maxShift`. + have hdb : d ≤ bound := hdbound bound hbnd + simp only [gatedWindowBound, Option.getD_some] + by_cases hdw : d ≤ + min bound (pivotWindowCap equation) + maxShiftDegree shift + · -- The solution row itself fits the gated window. + obtain ⟨bRow, degB, hmem, hdegB, hleB⟩ := me_verification_dominates + mulCtx modCtx pmCtx equation shift + (min bound (pivotWindowCap equation) + maxShiftDegree shift) + hmonic hcols hshift hpos hsat hnz hwidth hd hdw + refine ⟨bRow, degB, ?_, hdegB, by omega⟩ + rw [MatrixRows, Array.toList_append] + exact List.mem_append.mpr (Or.inr hmem) + · -- Beyond the window the bound forces saturation at the full + -- pivot window, where the fallback row dominates. + obtain ⟨prow, e, hsatP, hnzP, hsizeP, hdegP, heP⟩ := + me_prodRow_facts mulCtx modCtx equation shift (p := j0) hmonic + hcols (by omega) + obtain ⟨bRow, degB, hmem, hdegB, hleB⟩ := me_verification_dominates + mulCtx modCtx pmCtx equation shift + (min bound (pivotWindowCap equation) + maxShiftDegree shift) + hmonic hcols hshift hpos hsatP hnzP (le_of_eq hsizeP) hdegP + (by omega) + refine ⟨bRow, degB, ?_, hdegB, by omega⟩ + rw [MatrixRows, Array.toList_append] + exact List.mem_append.mpr (Or.inr hmem) + | none => + simp only [gatedWindowBound, Option.getD_none] + by_cases hcase : ∃ B, leastSolutionRowDegree? + (adaptiveSolutionBasis mulCtx modCtx pmCtx equation shift none).filtered + shift = some B ∧ B ≤ d + · -- An adaptive row already dominates. + obtain ⟨B, hB, hBd⟩ := hcase + rw [leastSolutionRowDegree?] at hB + rcases Option.map_eq_some_iff.mp hB with ⟨choice, hchoice, hchoicedeg⟩ + obtain ⟨hidx, hrowEq, hcdeg⟩ := leastShiftedDegreeChoice?_some_valid hchoice + refine ⟨choice.row, choice.degree, ?_, hcdeg, by omega⟩ + rw [MatrixRows, Array.toList_append] + refine List.mem_append.mpr (Or.inl ?_) + rw [hrowEq] + exact me_getD_mem_toList #[] hidx + · -- Route through the certified verification window. + push Not at hcase + have hwindow : ∃ (rowStar : PolynomialRow F) (e : Nat), + rowSatisfiesModularBool mulCtx modCtx rowStar equation.matrix + equation.moduli = true ∧ + rowIsZero rowStar = false ∧ + rowStar.size ≤ equation.solutionWidth ∧ + rowShiftedDegree? rowStar shift = some e ∧ + e ≤ verificationWindowBound equation shift + (leastSolutionRowDegree? + (adaptiveSolutionBasis mulCtx modCtx pmCtx equation + shift none).filtered shift) ∧ + e ≤ d := by + by_cases hdwindow : d ≤ verificationWindowBound equation shift + (leastSolutionRowDegree? + (adaptiveSolutionBasis mulCtx modCtx pmCtx equation + shift none).filtered shift) + · exact ⟨row, d, hsat, hnz, hwidth, hd, hdwindow, le_refl d⟩ + · -- The window is saturated at the full pivot window. + have hfull : verificationWindowBound equation shift + (leastSolutionRowDegree? + (adaptiveSolutionBasis mulCtx modCtx pmCtx equation + shift none).filtered shift) = + pivotWindowCap equation + maxShiftDegree shift := by + cases hbest : leastSolutionRowDegree? + (adaptiveSolutionBasis mulCtx modCtx pmCtx equation + shift none).filtered shift with + | none => + simp [verificationWindowBound, fullWindowDegreeBound] + | some B => + have hdB : d < B := hcase B hbest + rw [hbest] at hdwindow + simp only [verificationWindowBound] at hdwindow ⊢ + omega + obtain ⟨prow, e, hsatP, hnzP, hsizeP, hdegP, heP⟩ := + me_prodRow_facts mulCtx modCtx equation shift (p := j0) hmonic + hcols (by omega) + exact ⟨prow, e, hsatP, hnzP, le_of_eq hsizeP, hdegP, by omega, + by omega⟩ + obtain ⟨rowStar, e, hsatS, hnzS, hwidthS, hdegS, heB, heD⟩ := hwindow + obtain ⟨bRow, degB, hmem, hdegB, hleB⟩ := me_verification_dominates + mulCtx modCtx pmCtx equation shift (verificationWindowBound equation shift (leastSolutionRowDegree? (adaptiveSolutionBasis mulCtx modCtx pmCtx equation - shift).filtered shift))) - (exactNullspaceShift shift equation.modularWidth - (verificationWindowBound equation shift + shift none).filtered shift)) + hmonic hcols hshift hpos hsatS hnzS hwidthS hdegS heB + refine ⟨bRow, degB, ?_, hdegB, by omega⟩ + rw [MatrixRows, Array.toList_append] + refine List.mem_append.mpr (Or.inr ?_) + exact hmem + obtain ⟨bRow, degB, hmemC, hdegB, hled⟩ := hmain + -- The combined candidate set is nonempty, so the repair branch is + -- skipped. + have hsizepos : 0 < + ((adaptiveSolutionBasis mulCtx modCtx pmCtx equation shift degreeBound?).filtered ++ + filterModularSolutionRows mulCtx modCtx equation + (windowedSolutionBasisViaPMBasis modCtx pmCtx equation shift + (gatedWindowBound equation shift degreeBound? + (leastSolutionRowDegree? + (adaptiveSolutionBasis mulCtx modCtx pmCtx equation + shift degreeBound?).filtered shift)))).size := by + rcases List.getElem_of_mem hmemC with ⟨i, hi, _⟩ + rw [MatrixRows, Array.length_toList] at hi + omega + have hresult : filteredSolutionBasisViaPMBasis mulCtx modCtx pmCtx equation + shift degreeBound? = + (adaptiveSolutionBasis mulCtx modCtx pmCtx equation shift degreeBound?).filtered ++ + filterModularSolutionRows mulCtx modCtx equation + (windowedSolutionBasisViaPMBasis modCtx pmCtx equation shift + (gatedWindowBound equation shift degreeBound? (leastSolutionRowDegree? (adaptiveSolutionBasis mulCtx modCtx pmCtx equation - shift).filtered shift)))))) := hsub - exact le_of_eq - (me_principalSolutionRows_width (compactNonzeroRows_subset hsub')) - · refine ⟨bRow, degB, ?_, hdegB, ?_⟩ - · rw [hresult] - exact hmemC - · intro rowDegree hrowDegree - rw [hd] at hrowDegree - have hEq := Option.some.inj hrowDegree - omega + shift degreeBound?).filtered shift))) := by + simp only [filteredSolutionBasisViaPMBasis] + rw [hgate] + simp only [Bool.false_eq_true, if_false] + rw [if_neg (by simp only [beq_iff_eq]; omega)] + constructor + · -- Width discipline of the returned rows. + intro basisRow hbasisRow + rw [hresult, MatrixRows, Array.toList_append, List.mem_append] at hbasisRow + rcases hbasisRow with h | h + · exact le_of_eq (me_adaptiveBasis_width basisRow h) + · have hsub := me_filterModularSolutionRows_subset h + have hsub' : basisRow ∈ MatrixRows (compactNonzeroRows + (principalSolutionRows equation.solutionWidth + (pmCtx.basis + (fullWindowExactNullspaceProblem modCtx equation + (gatedWindowBound equation shift degreeBound? + (leastSolutionRowDegree? + (adaptiveSolutionBasis mulCtx modCtx pmCtx equation + shift degreeBound?).filtered shift))) + (exactNullspaceShift shift equation.modularWidth + (gatedWindowBound equation shift degreeBound? + (leastSolutionRowDegree? + (adaptiveSolutionBasis mulCtx modCtx pmCtx equation + shift degreeBound?).filtered shift)))))) := hsub + exact le_of_eq + (me_principalSolutionRows_width (compactNonzeroRows_subset hsub')) + · refine ⟨bRow, degB, ?_, hdegB, hled⟩ + rw [hresult] + exact hmemC end Approximant diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation/Basic.lean b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation/Basic.lean index a2425237..3d90529e 100644 --- a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation/Basic.lean +++ b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation/Basic.lean @@ -26,12 +26,17 @@ logarithmic, preserving the `~O(m^(omega-1) * sigma)` solver target. The chunked X-adic problems are relaxations: their orders certify exactness only for rows whose chunk coefficients stay below the chunk size, and the relaxed kernel module also contains uncertified rows that can crowd exact -solutions out of the adaptive rounds. The solver therefore always runs a -certified verification solve over the window shrunk to the best exact degree -already found (`verificationSolutionBasisViaPMBasis`) and returns the union: +solutions out of the adaptive rounds. The solver therefore backs the adaptive +loop with a certified verification solve (`windowedSolutionBasisViaPMBasis`): solutions at or above the adaptive best are dominated by the adaptive rows, and solutions below it lie inside the certified window, whose orders rule out uncertified rows. + +Callers that only need a witness under a known degree bound pass it as +`degreeBound?`; when the adaptive best already meets the bound the certified +solve is skipped entirely, and when it does not the certified window is shrunk +to the bound. Passing `none` requests a degree-minimal answer and always runs +the verification solve over the best-degree-shrunk window. -/ namespace CompPoly @@ -322,21 +327,58 @@ def verificationWindowBound [Zero F] (equation : ModularEquation F) | none => fullWindowDegreeBound equation shift | some best => min best (pivotWindowCap equation) + maxShiftDegree shift -/-- Certified verification solve over the best-degree-shrunk window: an -unchunked exact-nullspace problem whose orders certify exactness for every -row dominated by an in-window solution, with the window sized by -`verificationWindowBound`. Its order mass is `sigma + s * bound` instead of -the full window's `(s + 1) * sigma`, so when the adaptive loop already found a -near-minimal row this costs about one extra cheap round. -/ -def verificationSolutionBasisViaPMBasis (modCtx : CPolynomial.ModContext F) +/-- Certified solve over an explicit coefficient-degree window: an unchunked +exact-nullspace problem whose orders certify exactness for every row dominated +by an in-window solution. Its order mass is `sigma + s * bound` instead of +the full window's `(s + 1) * sigma`, so when the window is shrunk to a +near-minimal degree this costs about one extra cheap round. -/ +def windowedSolutionBasisViaPMBasis (modCtx : CPolynomial.ModContext F) (pmCtx : PMBasisContext F) (equation : ModularEquation F) - (shift : Array Nat) (bestDegree? : Option Nat) : PolynomialMatrix F := - let bound := verificationWindowBound equation shift bestDegree? - let problem := fullWindowExactNullspaceProblem modCtx equation bound - let expandedShift := exactNullspaceShift shift equation.modularWidth bound + (shift : Array Nat) (bound : Nat) : PolynomialMatrix F := compactNonzeroRows (principalSolutionRows equation.solutionWidth - (pmCtx.basis problem expandedShift)) + (pmCtx.basis (fullWindowExactNullspaceProblem modCtx equation bound) + (exactNullspaceShift shift equation.modularWidth bound))) + +/-- Certified verification solve over the best-degree-shrunk window sized by +`verificationWindowBound`. -/ +def verificationSolutionBasisViaPMBasis (modCtx : CPolynomial.ModContext F) + (pmCtx : PMBasisContext F) (equation : ModularEquation F) + (shift : Array Nat) (bestDegree? : Option Nat) : PolynomialMatrix F := + windowedSolutionBasisViaPMBasis modCtx pmCtx equation shift + (verificationWindowBound equation shift bestDegree?) + +/-- Whether a caller-supplied degree bound certifies the adaptive result: the +gate passes when the best exact degree already found does not exceed the +bound, in which case any solution within the bound is dominated by the +adaptive rows up to the bound itself and the certified verification solve is +unnecessary. -/ +def degreeGatePassed (degreeBound? bestDegree? : Option Nat) : Bool := + match degreeBound?, bestDegree? with + | some bound, some best => best ≤ bound + | _, _ => false + +/-- Verification window for a gated solve. With a caller-supplied degree +bound the window only has to cover solutions within the bound, so it is +`min(bound, cap) + maxShift`; without one it falls back to the best-degree +window of `verificationWindowBound`. -/ +def gatedWindowBound [Zero F] (equation : ModularEquation F) + (shift : Array Nat) (degreeBound? bestDegree? : Option Nat) : Nat := + match degreeBound? with + | some bound => min bound (pivotWindowCap equation) + maxShiftDegree shift + | none => verificationWindowBound equation shift bestDegree? + +/-- The degree gate passes exactly for a bound certified by an adaptive best. -/ +theorem degreeGatePassed_eq_true_iff {degreeBound? bestDegree? : Option Nat} : + degreeGatePassed degreeBound? bestDegree? = true ↔ + ∃ bound best, degreeBound? = some bound ∧ bestDegree? = some best ∧ + best ≤ bound := by + cases degreeBound? with + | none => simp [degreeGatePassed] + | some bound => + cases bestDegree? with + | none => simp [degreeGatePassed] + | some best => simp [degreeGatePassed] /-- Pivot-degree assignment for one adaptive round: discovered coordinates use their observed pivot degrees, undiscovered coordinates use the current @@ -418,13 +460,14 @@ def adaptiveSolutionRound filtered := state.filtered ++ filtered raw := rows } -/-- Fuel-bounded adaptive window-escalation loop. Rounds stop as soon as every +/-- Fuel-bounded adaptive window-escalation loop. Rounds stop as soon as the +caller's degree bound is already met by the best exact row found, or every principal coordinate is settled: discovered, saturated, or unable to beat the best solution row already in hand. -/ def adaptiveSolutionLoop (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) (pmCtx : PMBasisContext F) (equation : ModularEquation F) - (shift : Array Nat) (cap : Nat) : + (shift : Array Nat) (degreeBound? : Option Nat) (cap : Nat) : Nat → AdaptiveSolveState F → AdaptiveSolveState F | 0, state => state | fuel + 1, state => @@ -437,6 +480,11 @@ def adaptiveSolutionLoop -- so stop here and let the caller run the certified full-window -- fallback instead. next + else if degreeGatePassed degreeBound? bestDegree? then + -- The best exact row already meets the caller's degree bound, so the + -- gated solver returns the accumulated candidate set as-is; further + -- escalation rounds cannot change anything the caller observes. + next else if allCoordinatesSettled next.profile next.budgets cap bestDegree? shift equation.solutionWidth then next @@ -446,21 +494,22 @@ def adaptiveSolutionLoop if escalated == next.budgets then next else - adaptiveSolutionLoop mulCtx modCtx pmCtx equation shift cap fuel - { next with budgets := escalated } + adaptiveSolutionLoop mulCtx modCtx pmCtx equation shift degreeBound? cap + fuel { next with budgets := escalated } /-- Run the adaptive degree-first solver: discover the shifted pivot-degree profile with geometrically growing per-coordinate windows, where the final round doubles as the known-degree reconstruction for all discovered -coordinates. The initial window is one chunk per coordinate. -/ +coordinates. The initial window is one chunk per coordinate. A caller with +a degree bound stops the escalation as soon as the bound is met. -/ def adaptiveSolutionBasis (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) (pmCtx : PMBasisContext F) (equation : ModularEquation F) - (shift : Array Nat) : AdaptiveSolveState F := + (shift : Array Nat) (degreeBound? : Option Nat) : AdaptiveSolveState F := let delta := chunkDelta equation.solutionWidth equation.moduli let cap := max delta (pivotWindowCap equation) let fuel := Nat.log2 (max 1 cap) + 2 - adaptiveSolutionLoop mulCtx modCtx pmCtx equation shift cap fuel + adaptiveSolutionLoop mulCtx modCtx pmCtx equation shift degreeBound? cap fuel { profile := emptyPivotDegreeProfile equation.solutionWidth budgets := Array.replicate equation.solutionWidth (max 1 (delta - 1)) filtered := #[] @@ -471,7 +520,7 @@ def discoverPivotDegreeProfileViaPMBasis (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) (pmCtx : PMBasisContext F) (equation : ModularEquation F) (shift : Array Nat) : PivotDegreeProfile := - (adaptiveSolutionBasis mulCtx modCtx pmCtx equation shift).profile + (adaptiveSolutionBasis mulCtx modCtx pmCtx equation shift none).profile /-- One residual reconstruction pass. If compressed rows `B` are not themselves exact modular solutions, solve for polynomial combinations `C` such that @@ -492,7 +541,7 @@ def repairSolutionRowsViaPMBasis { moduli := equation.moduli matrix := modularResidualRows mulCtx modCtx equation reduced } let repairState := adaptiveSolutionBasis mulCtx modCtx pmCtx residualEquation - (candidateRowShift reduced shift) + (candidateRowShift reduced shift) none PolynomialMatrix.mulStrassenWith pmCtx.runtime.lowMulContext pmCtx.runtime.leafCutoff repairState.filtered reduced @@ -525,33 +574,42 @@ def knownDegreeFilteredSolutionBasisViaPMBasis /-- Solver exposed through the modular-equation context: run the adaptive degree-first window-escalation loop, whose final round is the known-degree -reconstruction for every discovered coordinate, then always run a certified -verification solve over the window shrunk to the best exact degree found (the -full pivot window when the chunked loop found nothing), and return the union -of both filtered candidate sets. Solutions at or above the adaptive best are -dominated by the adaptive rows; solutions strictly below it lie inside the -certified verification window, whose orders rule out uncertified relaxed -rows — so the union always contains an exact row of minimal shifted degree, -including in the partial-masking regime where uncertified chunked-kernel rows -crowd lower exact solutions out of every adaptive round. The adaptive rows -come first, so ties keep the adaptive choice. The residual repair pass -remains as a guard when both candidate sets are empty. The filter, -verification, and repair are semantic guards around the exact-nullspace -bridge; they do not call any alternate interpolation backend. -/ +reconstruction for every discovered coordinate, then back it with a certified +verification solve. When the caller supplies a degree bound and the best +exact degree already found meets it, the adaptive candidate set is returned +as-is: any solution within the bound is then matched by an adaptive row up to +the bound, so the certified solve adds nothing the caller can observe. +Otherwise a certified verification solve runs over the gated window — +`min(bound, cap) + maxShift` with a bound, the best-degree-shrunk window +without one — and the union of both filtered candidate sets is returned: +solutions at or above the adaptive best are dominated by the adaptive rows; +solutions strictly below it lie inside the certified window, whose orders rule +out uncertified relaxed rows — so the union always contains an exact row of +minimal shifted degree, including in the partial-masking regime where +uncertified chunked-kernel rows crowd lower exact solutions out of every +adaptive round. The adaptive rows come first, so ties keep the adaptive +choice. The residual repair pass remains as a guard when both candidate sets +are empty. The filter, verification, and repair are semantic guards around +the exact-nullspace bridge; they do not call any alternate interpolation +backend. -/ def filteredSolutionBasisViaPMBasis (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) (pmCtx : PMBasisContext F) (equation : ModularEquation F) - (shift : Array Nat) : PolynomialMatrix F := - let final := adaptiveSolutionBasis mulCtx modCtx pmCtx equation shift - let verification := filterModularSolutionRows mulCtx modCtx equation - (verificationSolutionBasisViaPMBasis modCtx pmCtx equation shift - (leastSolutionRowDegree? final.filtered shift)) - let combined := final.filtered ++ verification - if combined.size == 0 then - filterModularSolutionRows mulCtx modCtx equation - (repairSolutionRowsViaPMBasis mulCtx modCtx pmCtx equation shift final.raw) + (shift : Array Nat) (degreeBound? : Option Nat) : PolynomialMatrix F := + let final := adaptiveSolutionBasis mulCtx modCtx pmCtx equation shift degreeBound? + let best? := leastSolutionRowDegree? final.filtered shift + if degreeGatePassed degreeBound? best? then + final.filtered else - combined + let verification := filterModularSolutionRows mulCtx modCtx equation + (windowedSolutionBasisViaPMBasis modCtx pmCtx equation shift + (gatedWindowBound equation shift degreeBound? best?)) + let combined := final.filtered ++ verification + if combined.size == 0 then + filterModularSolutionRows mulCtx modCtx equation + (repairSolutionRowsViaPMBasis mulCtx modCtx pmCtx equation shift final.raw) + else + combined /-- Rows kept by the modular solution filter satisfy the modular predicate. -/ theorem rowSatisfiesModularBool_of_mem_filterModularSolutionRows @@ -589,12 +647,13 @@ filtered rows. -/ theorem adaptiveSolutionLoop_filtered_sound {mulCtx : CPolynomial.MulContext F} {modCtx : CPolynomial.ModContext F} {pmCtx : PMBasisContext F} {equation : ModularEquation F} - {shift : Array Nat} {cap : Nat} : + {shift : Array Nat} {degreeBound? : Option Nat} {cap : Nat} : ∀ (fuel : Nat) (state : AdaptiveSolveState F), (∀ row ∈ MatrixRows state.filtered, rowSatisfiesModularBool mulCtx modCtx row equation.matrix equation.moduli = true) → ∀ row ∈ MatrixRows - (adaptiveSolutionLoop mulCtx modCtx pmCtx equation shift cap fuel state).filtered, + (adaptiveSolutionLoop mulCtx modCtx pmCtx equation shift degreeBound? cap + fuel state).filtered, rowSatisfiesModularBool mulCtx modCtx row equation.matrix equation.moduli = true := by intro fuel induction fuel with @@ -610,20 +669,23 @@ theorem adaptiveSolutionLoop_filtered_sound · exact hnext row hrow · split at hrow · exact hnext row hrow - · dsimp only [] at hrow - split at hrow + · split at hrow · exact hnext row hrow - · refine ih _ ?_ row hrow - intro r hr - exact hnext r hr + · dsimp only [] at hrow + split at hrow + · exact hnext row hrow + · refine ih _ ?_ row hrow + intro r hr + exact hnext r hr /-- Rows accumulated by the adaptive solver satisfy the modular predicate. -/ theorem adaptiveSolutionBasis_filtered_sound {mulCtx : CPolynomial.MulContext F} {modCtx : CPolynomial.ModContext F} {pmCtx : PMBasisContext F} {equation : ModularEquation F} - {shift : Array Nat} : + {shift : Array Nat} {degreeBound? : Option Nat} : ∀ row ∈ MatrixRows - (adaptiveSolutionBasis mulCtx modCtx pmCtx equation shift).filtered, + (adaptiveSolutionBasis mulCtx modCtx pmCtx equation shift + degreeBound?).filtered, rowSatisfiesModularBool mulCtx modCtx row equation.matrix equation.moduli = true := by rw [adaptiveSolutionBasis] exact adaptiveSolutionLoop_filtered_sound _ _ @@ -634,36 +696,42 @@ original diagonal modular equation. -/ theorem filteredSolutionBasisViaPMBasis_sound {mulCtx : CPolynomial.MulContext F} {modCtx : CPolynomial.ModContext F} {pmCtx : PMBasisContext F} {equation : ModularEquation F} - {shift : Array Nat} {row : PolynomialRow F} + {shift : Array Nat} {degreeBound? : Option Nat} {row : PolynomialRow F} (hrow : row ∈ MatrixRows - (filteredSolutionBasisViaPMBasis mulCtx modCtx pmCtx equation shift)) : + (filteredSolutionBasisViaPMBasis mulCtx modCtx pmCtx equation shift + degreeBound?)) : rowSatisfiesModularBool mulCtx modCtx row equation.matrix equation.moduli = true := by simp only [filteredSolutionBasisViaPMBasis] at hrow split at hrow - · exact rowSatisfiesModularBool_of_mem_filterModularSolutionRows hrow - · rw [MatrixRows, Array.toList_append, List.mem_append] at hrow - rcases hrow with hmem | hmem - · exact adaptiveSolutionBasis_filtered_sound row hmem - · exact rowSatisfiesModularBool_of_mem_filterModularSolutionRows hmem + · exact adaptiveSolutionBasis_filtered_sound row hrow + · split at hrow + · exact rowSatisfiesModularBool_of_mem_filterModularSolutionRows hrow + · rw [MatrixRows, Array.toList_append, List.mem_append] at hrow + rcases hrow with hmem | hmem + · exact adaptiveSolutionBasis_filtered_sound row hmem + · exact rowSatisfiesModularBool_of_mem_filterModularSolutionRows hmem /-- Modular-equation solution-basis context with theorem fields. -The completeness/minimality contract states that, for any nonzero in-width -solution row of the diagonal modular equation, the returned basis stays inside -the principal width and contains a row whose shifted degree does not exceed -the given solution's. The contract assumes monic moduli, a relation matrix -wide enough to expose every modular column to the executable row predicate, -and a shift aligned with the principal solution width. -/ +The solver takes an optional caller-supplied degree bound. The +completeness/minimality contract is bound-relative: for any nonzero in-width +solution row of the diagonal modular equation whose shifted degree fits the +bound (vacuous for `none`), the returned basis stays inside the principal +width and contains a row whose shifted degree does not exceed the bound — the +given solution's own degree when no bound is supplied. The contract assumes +monic moduli, a relation matrix wide enough to expose every modular column to +the executable row predicate, and a shift aligned with the principal solution +width. -/ structure ModularSolutionBasisContext (F : Type*) [Field F] [BEq F] [LawfulBEq F] where mulContext : CPolynomial.MulContext F modContext : CPolynomial.ModContext F - solutionBasis : ModularEquation F → Array Nat → PolynomialMatrix F + solutionBasis : ModularEquation F → Array Nat → Option Nat → PolynomialMatrix F sound : - ∀ equation shift row, - row ∈ MatrixRows (solutionBasis equation shift) → + ∀ equation shift degreeBound? row, + row ∈ MatrixRows (solutionBasis equation shift degreeBound?) → rowSatisfiesModularBool mulContext modContext row equation.matrix equation.moduli = true complete_minimal : - ∀ equation shift row, + ∀ equation shift degreeBound? row rowDegree, (∀ b, b < equation.moduli.size → (equation.moduli.getD b 0).monic) → equation.moduli.size ≤ MatrixWidth equation.matrix → shift.size = equation.solutionWidth → @@ -671,14 +739,15 @@ structure ModularSolutionBasisContext (F : Type*) [Field F] [BEq F] [LawfulBEq F equation.moduli = true → rowIsZero row = false → row.size ≤ equation.solutionWidth → + rowShiftedDegree? row shift = some rowDegree → + (∀ bound, degreeBound? = some bound → rowDegree ≤ bound) → (∀ basisRow, - basisRow ∈ MatrixRows (solutionBasis equation shift) → + basisRow ∈ MatrixRows (solutionBasis equation shift degreeBound?) → basisRow.size ≤ equation.solutionWidth) ∧ ∃ basisRow degree, - basisRow ∈ MatrixRows (solutionBasis equation shift) ∧ + basisRow ∈ MatrixRows (solutionBasis equation shift degreeBound?) ∧ rowShiftedDegree? basisRow shift = some degree ∧ - ∀ rowDegree, rowShiftedDegree? row shift = some rowDegree → - degree ≤ rowDegree + degree ≤ degreeBound?.getD rowDegree end Approximant diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation/Completeness.lean b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation/Completeness.lean index 64b637a0..349121bf 100644 --- a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation/Completeness.lean +++ b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation/Completeness.lean @@ -495,12 +495,12 @@ private theorem me_adaptiveRound_width private theorem me_adaptiveLoop_width {mulCtx : CPolynomial.MulContext F} {modCtx : CPolynomial.ModContext F} {pmCtx : PMBasisContext F} {equation : ModularEquation F} - {shift : Array Nat} {cap : Nat} : + {shift : Array Nat} {degreeBound? : Option Nat} {cap : Nat} : ∀ (fuel : Nat) (state : AdaptiveSolveState F), (∀ r ∈ MatrixRows state.filtered, r.size = equation.solutionWidth) → ∀ r ∈ MatrixRows - (adaptiveSolutionLoop mulCtx modCtx pmCtx equation shift cap fuel - state).filtered, + (adaptiveSolutionLoop mulCtx modCtx pmCtx equation shift degreeBound? + cap fuel state).filtered, r.size = equation.solutionWidth := by intro fuel induction fuel with @@ -516,20 +516,23 @@ private theorem me_adaptiveLoop_width · exact hnext r hr · split at hr · exact hnext r hr - · dsimp only [] at hr - split at hr + · split at hr · exact hnext r hr - · refine ih _ ?_ r hr - intro r' hr' - exact hnext r' hr' + · dsimp only [] at hr + split at hr + · exact hnext r hr + · refine ih _ ?_ r hr + intro r' hr' + exact hnext r' hr' /-- Every adaptive solution-basis row has the linearized width. -/ theorem me_adaptiveBasis_width {mulCtx : CPolynomial.MulContext F} {modCtx : CPolynomial.ModContext F} {pmCtx : PMBasisContext F} {equation : ModularEquation F} - {shift : Array Nat} : + {shift : Array Nat} {degreeBound? : Option Nat} : ∀ r ∈ MatrixRows - (adaptiveSolutionBasis mulCtx modCtx pmCtx equation shift).filtered, + (adaptiveSolutionBasis mulCtx modCtx pmCtx equation shift + degreeBound?).filtered, r.size = equation.solutionWidth := by rw [adaptiveSolutionBasis] exact me_adaptiveLoop_width _ _ (by intro r hr; simp [MatrixRows] at hr) diff --git a/tests/CompPolyTests/LinearAlgebra/PolynomialMatrix/Approximant.lean b/tests/CompPolyTests/LinearAlgebra/PolynomialMatrix/Approximant.lean index 4ac40e26..2d29fa15 100644 --- a/tests/CompPolyTests/LinearAlgebra/PolynomialMatrix/Approximant.lean +++ b/tests/CompPolyTests/LinearAlgebra/PolynomialMatrix/Approximant.lean @@ -161,7 +161,7 @@ private def knownDegreeFilteredRows : PolynomialMatrix F3 := private def productionRows : PolynomialMatrix F3 := filteredSolutionBasisViaPMBasis CPolynomial.MulContext.naive - CPolynomial.ModContext.remainderOnly productionPMCtx equation #[0, 5] + CPolynomial.ModContext.remainderOnly productionPMCtx equation #[0, 5] none private def debugUnchunkedRows : PolynomialMatrix F3 := debugUnchunkedFilteredSolutionBasisViaPMBasis CPolynomial.MulContext.naive From cf09670ca44e0f6aa82c16cbdd190fa0d0ebb055 Mon Sep 17 00:00:00 2001 From: Valerii Huhnin Date: Fri, 12 Jun 2026 09:59:54 +0000 Subject: [PATCH 06/10] Faste divisibility check, larger bench to compare Lee and approximant --- CompPoly.lean | 2 + CompPoly/Bivariate/Deriv.lean | 23 + .../Interpolation/WitnessDivisibility.lean | 112 ++++ .../WitnessDivisibilityCorrectness.lean | 503 ++++++++++++++++++ .../Bivariate/GuruswamiSudan.lean | 108 ++-- .../Bivariate/GuruswamiSudan/Core.lean | 144 ++--- .../GuruswamiSudan/ReceivedWord.lean | 208 +++++--- .../Bivariate/GuruswamiSudan/Shared.lean | 60 ++- 8 files changed, 955 insertions(+), 205 deletions(-) create mode 100644 CompPoly/Bivariate/GuruswamiSudan/Interpolation/WitnessDivisibility.lean create mode 100644 CompPoly/Bivariate/GuruswamiSudan/Interpolation/WitnessDivisibilityCorrectness.lean diff --git a/CompPoly.lean b/CompPoly.lean index cdc76bb8..57145e70 100644 --- a/CompPoly.lean +++ b/CompPoly.lean @@ -47,6 +47,8 @@ import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Correctness. import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Correctness.Rows import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Correctness.Selection import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Correctness.Soundness +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.WitnessDivisibility +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.WitnessDivisibilityCorrectness import CompPoly.Bivariate.GuruswamiSudan.Monomials import CompPoly.Bivariate.GuruswamiSudan.Polynomial import CompPoly.Bivariate.GuruswamiSudan.PolynomialCorrectness diff --git a/CompPoly/Bivariate/Deriv.lean b/CompPoly/Bivariate/Deriv.lean index f8bb9d38..4201a658 100644 --- a/CompPoly/Bivariate/Deriv.lean +++ b/CompPoly/Bivariate/Deriv.lean @@ -345,6 +345,29 @@ theorem hasMultiplicity_succ [CommSemiring R] [BEq R] [LawfulBEq R] [Nontrivial intro h i j hij exact h i j (by omega) +/-- `toPoly` is injective on canonical bivariate polynomials. -/ +theorem toPoly_injective [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] + [DecidableEq R] {P Q : CBivariate R} (h : toPoly P = toPoly Q) : P = Q := by + rw [← toPoly_ofPoly P, ← toPoly_ofPoly Q, h] + +/-- The generic Taylor shift is additive. -/ +theorem shiftC_add [CommSemiring R] [BEq R] [LawfulBEq R] [Nontrivial R] [DecidableEq R] + (a b : R) (P Q : CBivariate R) : + shiftC a b (P + Q) = shiftC a b P + shiftC a b Q := by + apply toPoly_injective + rw [toPoly_add, shiftC_toPoly, shiftC_toPoly, shiftC_toPoly, toPoly_add] + unfold Polynomial.Bivariate.shift + rw [Polynomial.add_comp, Polynomial.map_add] + +/-- The generic Taylor shift is multiplicative. -/ +theorem shiftC_mul [CommSemiring R] [BEq R] [LawfulBEq R] [Nontrivial R] + [DecidableEq R] (a b : R) (P Q : CBivariate R) : + shiftC a b (P * Q) = shiftC a b P * shiftC a b Q := by + apply toPoly_injective + rw [toPoly_mul, shiftC_toPoly, shiftC_toPoly, shiftC_toPoly, toPoly_mul] + unfold Polynomial.Bivariate.shift + rw [Polynomial.mul_comp, Polynomial.map_mul] + /-- The decidable check agrees with the propositional multiplicity. -/ theorem hasMultiplicity_iff_check [CommSemiring R] [BEq R] [LawfulBEq R] [Nontrivial R] [DecidableEq R] (Q : CBivariate R) (r : ℕ) (a b : R) : diff --git a/CompPoly/Bivariate/GuruswamiSudan/Interpolation/WitnessDivisibility.lean b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/WitnessDivisibility.lean new file mode 100644 index 00000000..d8b27493 --- /dev/null +++ b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/WitnessDivisibility.lean @@ -0,0 +1,112 @@ +/- +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.Interpolation.Basic +import CompPoly.Univariate.CoefficientInterpolation +import CompPoly.Univariate.Context +import CompPoly.Univariate.Vanishing + +/-! +# Divisibility-Based Interpolation Witness Checking + +A fast full recognizer for the Guruswami-Sudan multiplicity constraints. + +The pointwise check (`CBivariate.satisfiesMultiplicityConstraintsBool`) +evaluates every Hasse derivative of order below `m` at every interpolation +point, costing `O(n * m^2 * terms(Q))` field operations. For point sets with +distinct `x`-coordinates the same property has a module-theoretic +characterization: writing `Q = Σ_t c_t * (Y - R)^t` for the base-`(Y - R)` +expansion of `Q`, where `R` interpolates the points, `Q` has a zero of +multiplicity at least `m` at every point if and only if +`G^(m - t) ∣ c_t` for all `t < m`, with `G = Π_i (X - x_i)` the vanishing +polynomial of the `x`-coordinates. + +`satisfiesMultiplicityConstraintsViaDivisibilityBool` checks the right-hand +side directly: it peels `m` digits off `Q` by synthetic division by `Y - R` +and tests each digit for divisibility by the corresponding power of `G` with +a monic remainder. With NTT-backed multiplication and remainder contexts the +whole check is quasi-linear in the input size, with no per-point loop. + +`interpolationWitnessIsValidViaDivisibilityBool` combines the divisibility +check with the nonzero and weighted-degree witness conditions, mirroring +`interpolationWitnessIsValidBool`. +-/ + +namespace CompPoly + +namespace CBivariate + +/-- `divByLinearY` with the inner univariate products supplied by an explicit +multiplication context, for callers that want NTT-backed Horner steps. -/ +def divByLinearYWith {R : Type*} [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] + (M : CPolynomial.MulContext R) (Q : CBivariate R) (f : CPolynomial R) : + CBivariate R × CPolynomial R := + let n := natDegreeY Q + if n = 0 then (0, CPolynomial.coeff Q 0) + else + let a : ℕ → CPolynomial R := fun j => CPolynomial.coeff Q j + let b_init := a n + let (b_last, coeffs) := (List.range (n - 1)).foldl + (fun (b, acc) k => + let bj := a (n - 1 - k) + M.mul f b + (bj, acc.push bj)) + (b_init, #[b_init]) + let rem := a 0 + M.mul f b_last + let quotArr : CPolynomial.Raw (CPolynomial R) := coeffs.reverse + (⟨quotArr.trim, CPolynomial.Raw.Trim.isCanonical_trim quotArr⟩, rem) + +/-- The context-parametric synthetic division agrees with `divByLinearY`. -/ +theorem divByLinearYWith_eq_divByLinearY {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] [Nontrivial R] + (M : CPolynomial.MulContext R) (Q : CBivariate R) (f : CPolynomial R) : + divByLinearYWith M Q f = divByLinearY Q f := by + unfold divByLinearYWith divByLinearY + simp only [M.mul_eq_mul] + +end CBivariate + +namespace GuruswamiSudan + +/-- Divisibility form of the multiplicity constraints: the digit `c_t` of the +base-`(Y - R)` expansion of `Q` must be divisible by `G^(m - t)` for every +`t < m`. The recursion peels one digit per step by synthetic division, so the +step at recursion depth `t` checks `G^(m - t) ∣ c_t` on the running quotient. + +For `G` the vanishing polynomial of the (distinct) `x`-coordinates of a point +set and `R` its interpolant, this is equivalent to +`CBivariate.satisfiesMultiplicityConstraintsBool` on the same point set; see +`satisfiesMultiplicityConstraintsViaDivisibilityBool_eq`. -/ +def satisfiesMultiplicityConstraintsViaDivisibilityBool {F : Type*} + [Field F] [BEq F] [LawfulBEq F] + (Mul : CPolynomial.MulContext F) (Mod : CPolynomial.ModContext F) + (G R : CPolynomial F) : Nat → CBivariate F → Bool + | 0, _ => true + | m + 1, Q => + let step := CBivariate.divByLinearYWith Mul Q R + Mod.modByMonic step.2 (G ^ (m + 1)) == 0 && + satisfiesMultiplicityConstraintsViaDivisibilityBool Mul Mod G R m step.1 + +/-- Divisibility-based recognizer for the semantic interpolation witness +contract, agreeing with `interpolationWitnessIsValidBool` on point sets with +distinct `x`-coordinates but avoiding its per-point Hasse derivative loop. -/ +def interpolationWitnessIsValidViaDivisibilityBool {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (V : CPolynomial.VanishingPolynomialContext F) + (E : CPolynomial.BatchEvalContext F) + (Mul : CPolynomial.MulContext F) (Mod : CPolynomial.ModContext F) + (points : Array (F × F)) (params : GSInterpParams) (Q : CBivariate F) : Bool := + let G := V.vanishingPolynomial (points.map fun point ↦ point.1) + let R := CPolynomial.interpolateCoefficientFormWithVanishing E G points + !(Q == 0) && + decide + (CBivariate.natWeightedDegree Q 1 (yWeight params) ≤ + params.weightedDegreeBound) && + satisfiesMultiplicityConstraintsViaDivisibilityBool Mul Mod G R + params.multiplicity Q + +end GuruswamiSudan + +end CompPoly diff --git a/CompPoly/Bivariate/GuruswamiSudan/Interpolation/WitnessDivisibilityCorrectness.lean b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/WitnessDivisibilityCorrectness.lean new file mode 100644 index 00000000..436f7460 --- /dev/null +++ b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/WitnessDivisibilityCorrectness.lean @@ -0,0 +1,503 @@ +/- +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.Interpolation.WitnessDivisibility +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Koetter.Correctness.Combinations +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Correctness.Basis +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Correctness.Divisibility + +/-! +# Correctness of Divisibility-Based Witness Checking + +`satisfiesMultiplicityConstraintsViaDivisibilityBool` agrees with the +pointwise Hasse checker `CBivariate.satisfiesMultiplicityConstraintsBool` +on point sets with distinct `x`-coordinates, and consequently +`interpolationWitnessIsValidViaDivisibilityBool` agrees with +`interpolationWitnessIsValidBool`. + +The proof peels one base-`(Y - R)` digit per multiplicity level. + +* Soundness: from `Q = C c + (Y - R) * Q'` with `c = W * G^(m+1)`, the digit + term gains multiplicity `m + 1` from the `G`-power + (`hasMultiplicityAtLeast_ofYConstant_pow_mul_eval_zero`), the quotient term + gains one from `Y - R` (`hasMultiplicityAtLeast_linearYDivisor_mul`) on top + of the inductive hypothesis, and multiplicity is closed under addition. + +* Completeness: the quotient `Q'` keeps multiplicity `m` at every point + (`witness_hasMultiplicity_quot`, by a strong induction on the + `X`-coefficients of the Taylor-shifted division identity), so the digit + `C c = Q - (Y - R) * Q'` has multiplicity `m + 1` with `Y`-degree zero, and + `coeffY_dvd_vanishingPolynomial_pow_of_multiplicity` forces + `G^(m+1) ∣ c`. +-/ + +namespace CompPoly + +namespace GuruswamiSudan + +open CBivariate + +variable {F : Type*} [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + +/-! ### Generic helpers -/ + +private theorem witness_cpoly_eq_of_toPoly_eq {R : Type*} + [Semiring R] [BEq R] [LawfulBEq R] {p q : CPolynomial R} + (h : p.toPoly = q.toPoly) : p = q := by + rw [CPolynomial.eq_iff_coeff] + intro i + rw [CPolynomial.coeff_toPoly, CPolynomial.coeff_toPoly, h] + +omit [Nontrivial F] [DecidableEq F] in +private theorem witness_linearFactor_toPoly (x : F) : + (CPolynomial.linearFactor x).toPoly = + (Polynomial.X - Polynomial.C x : Polynomial F) := by + rw [CPolynomial.linearFactor, CPolynomial.toPoly_add, CPolynomial.C_toPoly, + CPolynomial.X_toPoly, Polynomial.C_neg] + ring + +omit [Nontrivial F] [DecidableEq F] in +private theorem witness_foldl_linearFactor_monic (l : List F) (acc : CPolynomial F) + (hacc : acc.toPoly.Monic) : + (l.foldl (fun acc x ↦ acc * CPolynomial.linearFactor x) acc).toPoly.Monic := by + induction l generalizing acc with + | nil => simpa using hacc + | cons x xs ih => + simp only [List.foldl_cons] + apply ih + rw [CPolynomial.toPoly_mul, witness_linearFactor_toPoly] + exact hacc.mul (Polynomial.monic_X_sub_C x) + +omit [DecidableEq F] in +/-- The vanishing polynomial of a node array is monic. -/ +private theorem witness_vanishing_monic (xs : Array F) : + (CPolynomial.vanishingPolynomialArray xs).monic := by + rw [CPolynomial.monic_toPoly_iff, CPolynomial.vanishingPolynomialArray, + ← Array.foldl_toList] + exact witness_foldl_linearFactor_monic xs.toList 1 + (by rw [CPolynomial.toPoly_one]; exact Polynomial.monic_one) + +omit [DecidableEq F] in +private theorem witness_monic_pow {g : CPolynomial F} (hg : g.monic) (k : Nat) : + (g ^ k).monic := by + rw [CPolynomial.monic_toPoly_iff] at hg ⊢ + rw [CPolynomial.toPoly_pow] + exact hg.pow k + +omit [DecidableEq F] in +/-- Remainder by a monic divisor vanishes exactly on multiples. -/ +private theorem witness_modByMonic_eq_zero_iff {c g : CPolynomial F} + (hg : g.monic) : + CPolynomial.modByMonic c g = 0 ↔ ∃ W : CPolynomial F, c = W * g := by + have hgPoly : g.toPoly.Monic := (CPolynomial.monic_toPoly_iff g).mp hg + constructor + · intro h + have hmod : c.toPoly %ₘ g.toPoly = 0 := by + rw [← CPolynomial.modByMonic_toPoly_eq_modByMonic c g hg, h] + exact (CPolynomial.toPoly_eq_zero_iff _).mpr rfl + rcases (Polynomial.modByMonic_eq_zero_iff_dvd hgPoly).mp hmod with ⟨w, hw⟩ + refine ⟨⟨w.toImpl, CPolynomial.Raw.isCanonical_toImpl w⟩, ?_⟩ + apply witness_cpoly_eq_of_toPoly_eq + rw [CPolynomial.toPoly_mul] + have hWto : CPolynomial.toPoly + (⟨w.toImpl, CPolynomial.Raw.isCanonical_toImpl w⟩ : CPolynomial F) = w := + CPolynomial.Raw.toPoly_toImpl + rw [hWto, hw] + ring + · rintro ⟨W, rfl⟩ + have hmod : (W * g).toPoly %ₘ g.toPoly = 0 := + (Polynomial.modByMonic_eq_zero_iff_dvd hgPoly).mpr + ⟨W.toPoly, by rw [CPolynomial.toPoly_mul]; ring⟩ + apply witness_cpoly_eq_of_toPoly_eq + rw [CPolynomial.modByMonic_toPoly_eq_modByMonic _ _ hg, hmod] + exact ((CPolynomial.toPoly_eq_zero_iff _).mpr rfl).symm + +/-! ### The division identity -/ + +/-- Euclidean identity of `divByLinearY` at the `CBivariate` level. -/ +private theorem witness_divByLinearY_decomposition (Q : CBivariate F) + (R : CPolynomial F) : + Q = CBivariate.ofYConstant (CBivariate.divByLinearY Q R).2 + + CBivariate.linearYDivisor R * (CBivariate.divByLinearY Q R).1 := by + apply witness_cpoly_eq_of_toPoly_eq (R := CPolynomial F) + erw [CPolynomial.toPoly_add, CPolynomial.toPoly_mul] + exact (CBivariate.divByLinearY_euclid_toPoly Q R).symm + +/-! ### The shifted division identity -/ + +private theorem witness_taylor_neg (x : F) (u : CPolynomial F) : + CPolynomial.taylor x (-u) = -CPolynomial.taylor x u := by + apply witness_cpoly_eq_of_toPoly_eq + rw [CPolynomial.taylor_toPoly, CPolynomial.toPoly_neg, CPolynomial.toPoly_neg, + CPolynomial.taylor_toPoly, map_neg] + +private theorem witness_taylor_one (x : F) : + CPolynomial.taylor x (1 : CPolynomial F) = 1 := by + apply witness_cpoly_eq_of_toPoly_eq + simp [CPolynomial.taylor_toPoly, CPolynomial.toPoly_one] + +private theorem witness_taylor_sub_C (x y : F) (R : CPolynomial F) : + CPolynomial.taylor x (R - CPolynomial.C y) = + CPolynomial.taylor x R - CPolynomial.C y := by + apply witness_cpoly_eq_of_toPoly_eq + rw [CPolynomial.taylor_toPoly, CPolynomial.toPoly_sub, CPolynomial.toPoly_sub, + CPolynomial.taylor_toPoly, CPolynomial.C_toPoly, map_sub, Polynomial.taylor_C] + +/-- Outer (`Y`) coefficients of the linear divisor `Y - u`. -/ +private theorem witness_coeff_linearYDivisor (u : CPolynomial F) (j : Nat) : + CPolynomial.coeff (CBivariate.linearYDivisor u) j = + if j = 0 then -u else if j = 1 then (1 : CPolynomial F) else 0 := by + rw [CPolynomial.coeff_toPoly, CBivariate.linearYDivisor_toPoly, + Polynomial.coeff_sub, Polynomial.coeff_C] + rcases j with _ | _ | j <;> + simp [Polynomial.coeff_X] + +/-- The outer (`Y`) shift maps `Y - R` to `Y - (R - C y)`. -/ +private theorem witness_shiftY_linearYDivisor (y : F) (R : CPolynomial F) : + CBivariate.shiftY y (CBivariate.linearYDivisor R) = + CBivariate.linearYDivisor (R - CPolynomial.C y) := by + apply witness_cpoly_eq_of_toPoly_eq (R := CPolynomial F) + rw [CBivariate.shiftY, CPolynomial.taylor_toPoly, + CBivariate.linearYDivisor_toPoly, CBivariate.linearYDivisor_toPoly, + map_sub, Polynomial.taylor_X, Polynomial.taylor_C, map_sub] + ring + +/-- The inner (`X`) shift maps `Y - u` to `Y - taylor x u`. -/ +private theorem witness_shiftX_linearYDivisor (x : F) (u : CPolynomial F) : + CBivariate.shiftX x (CBivariate.linearYDivisor u) = + CBivariate.linearYDivisor (CPolynomial.taylor x u) := by + refine (CPolynomial.eq_iff_coeff (R := CPolynomial F)).mpr fun j ↦ ?_ + rw [CBivariate.outerCoeff_shiftX, witness_coeff_linearYDivisor, + witness_coeff_linearYDivisor] + rcases j with _ | _ | j + · simpa using witness_taylor_neg x u + · simpa using witness_taylor_one x + · simpa using CPolynomial.taylor_zero x + +/-- The full Taylor shift maps `Y - R` to `Y - (taylor x R - C y)`. -/ +private theorem witness_shiftC_linearYDivisor (x y : F) (R : CPolynomial F) : + CBivariate.shiftC x y (CBivariate.linearYDivisor R) = + CBivariate.linearYDivisor (CPolynomial.taylor x R - CPolynomial.C y) := by + show CBivariate.shiftX x (CBivariate.shiftY y (CBivariate.linearYDivisor R)) = _ + rw [witness_shiftY_linearYDivisor, witness_shiftX_linearYDivisor, + witness_taylor_sub_C] + +/-- Positive-`Y` outer coefficients of the shift of a `Y`-constant vanish. -/ +private theorem witness_outerCoeff_shiftC_C (x y : F) (c : CPolynomial F) + (j : Nat) : + CPolynomial.coeff (CBivariate.shiftC x y (CBivariate.ofYConstant c)) + (j + 1) = 0 := by + have hY : CBivariate.shiftY y (CBivariate.ofYConstant c) = + CBivariate.ofYConstant c := by + apply witness_cpoly_eq_of_toPoly_eq (R := CPolynomial F) + show CPolynomial.toPoly + (CPolynomial.taylor (CPolynomial.C y) (CPolynomial.C c)) = + CPolynomial.toPoly (CPolynomial.C c) + rw [CPolynomial.taylor_toPoly, CPolynomial.C_toPoly, Polynomial.taylor_C] + show CPolynomial.coeff + (CBivariate.shiftX x (CBivariate.shiftY y (CBivariate.ofYConstant c))) + (j + 1) = 0 + rw [hY] + show CPolynomial.coeff + (CBivariate.shiftX x (CPolynomial.C c)) (j + 1) = 0 + rw [CBivariate.outerCoeff_shiftX, CPolynomial.coeff_C] + simp [CPolynomial.taylor_zero] + +/-! ### The quotient keeps multiplicity -/ + +omit [Nontrivial F] [DecidableEq F] in +/-- Univariate coefficients of a difference. -/ +private theorem witness_coeff_sub (p q : CPolynomial F) (i : Nat) : + CPolynomial.coeff (p - q) i = + CPolynomial.coeff p i - CPolynomial.coeff q i := by + rw [CPolynomial.coeff_toPoly, CPolynomial.coeff_toPoly, CPolynomial.coeff_toPoly, + CPolynomial.toPoly_sub, Polynomial.coeff_sub] + +/-- Outer (`Y`) coefficients of a product with the linear divisor `Y - u`. -/ +private theorem witness_outerCoeff_linearYDivisor_mul (u : CPolynomial F) + (P : CBivariate F) (j : Nat) : + CPolynomial.coeff (CBivariate.linearYDivisor u * P) (j + 1) = + CPolynomial.coeff P j - u * CPolynomial.coeff P (j + 1) := by + rw [CPolynomial.coeff_toPoly] + erw [CPolynomial.toPoly_mul] + rw [CBivariate.linearYDivisor_toPoly, mul_comm, + Polynomial.coeff_mul_X_sub_C, CPolynomial.coeff_toPoly, + CPolynomial.coeff_toPoly] + ring + +/-- Core coefficient induction: if every positive-`Y`-row low coefficient of +`(Y - u) * P` vanishes and `u` has no constant term, then every low +coefficient of `P` vanishes. -/ +private theorem witness_coeff_low_vanish + {u : CPolynomial F} (hu : CPolynomial.coeff u 0 = 0) + {P : CBivariate F} {m : Nat} + (h : ∀ i j, i + j + 1 < m + 1 → + CBivariate.coeff (CBivariate.linearYDivisor u * P) i (j + 1) = 0) : + ∀ i j, i + j < m → CBivariate.coeff P i j = 0 := by + intro i + induction i using Nat.strong_induction_on with + | _ i ih => + intro j hij + have hprod := h i j (by omega) + rw [CBivariate.coeff_eq_coeff_coeff, witness_outerCoeff_linearYDivisor_mul, + witness_coeff_sub] at hprod + have hmul : CPolynomial.coeff (u * CPolynomial.coeff P (j + 1)) i = 0 := by + rw [CPolynomial.coeff_mul] + apply Finset.sum_eq_zero + intro k hk + have hkle : k ≤ i := by + have := Finset.mem_range.mp hk + omega + rcases Nat.eq_zero_or_pos k with rfl | hkpos + · rw [hu, zero_mul] + · have hcoeff : CPolynomial.coeff (CPolynomial.coeff P (j + 1)) (i - k) = 0 := by + have hlow := ih (i - k) (by omega) (j + 1) (by omega) + rw [CBivariate.coeff_eq_coeff_coeff] at hlow + exact hlow + rw [hcoeff, mul_zero] + rw [hmul, sub_zero] at hprod + rw [CBivariate.coeff_eq_coeff_coeff] + exact hprod + +/-- Dividing by `Y - R` through a point of multiplicity `m + 1` leaves a +quotient of multiplicity `m` there. -/ +private theorem witness_hasMultiplicity_quot + {Q : CBivariate F} {R : CPolynomial F} {x y : F} {m : Nat} + (hxy : CPolynomial.eval x R = y) + (hQ : CBivariate.hasMultiplicity Q (m + 1) x y) : + CBivariate.hasMultiplicity (CBivariate.divByLinearY Q R).1 m x y := by + set c := (CBivariate.divByLinearY Q R).2 with hc + set Q' := (CBivariate.divByLinearY Q R).1 with hq' + have hu0 : CPolynomial.coeff (CPolynomial.taylor x R - CPolynomial.C y) 0 = 0 := by + rw [witness_coeff_sub, CPolynomial.taylor_coeff_zero, CPolynomial.coeff_C, + hxy] + simp + have hshift : CBivariate.shiftC x y Q = + CBivariate.shiftC x y (CBivariate.ofYConstant c) + + CBivariate.linearYDivisor (CPolynomial.taylor x R - CPolynomial.C y) * + CBivariate.shiftC x y Q' := by + conv_lhs => rw [witness_divByLinearY_decomposition Q R] + rw [CBivariate.shiftC_add, CBivariate.shiftC_mul, + witness_shiftC_linearYDivisor] + intro i j hij + refine witness_coeff_low_vanish hu0 (P := CBivariate.shiftC x y Q') ?_ i j hij + intro i' j' hij' + have h0 : CBivariate.coeff (CBivariate.shiftC x y Q) i' (j' + 1) = 0 := + hQ i' (j' + 1) (by omega) + rw [hshift, CBivariate.coeff_add] at h0 + have hCc : CBivariate.coeff + (CBivariate.shiftC x y (CBivariate.ofYConstant c)) i' (j' + 1) = 0 := by + rw [CBivariate.coeff_eq_coeff_coeff, witness_outerCoeff_shiftC_C] + exact CPolynomial.coeff_zero i' + rw [hCc, zero_add] at h0 + exact h0 + +/-! ### Multiplicity closure helpers -/ + +omit [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] in +private theorem witness_hasMultiplicityAtLeast_zero (P : CBivariate F) (x y : F) : + CBivariate.HasMultiplicityAtLeast P x y 0 := + fun _ _ hab ↦ absurd hab (by omega) + +private theorem witness_hasMultiplicityAtLeast_add + {P S : CBivariate F} {x y : F} {m : Nat} + (hP : CBivariate.HasMultiplicityAtLeast P x y m) + (hS : CBivariate.HasMultiplicityAtLeast S x y m) : + CBivariate.HasMultiplicityAtLeast (P + S) x y m := by + intro a b hab + rw [CBivariate.hasseDerivativeEval_add, hP a b hab, hS a b hab, add_zero] + +private theorem witness_hasMultiplicityAtLeast_sub + {P S : CBivariate F} {x y : F} {m : Nat} + (hP : CBivariate.HasMultiplicityAtLeast P x y m) + (hS : CBivariate.HasMultiplicityAtLeast S x y m) : + CBivariate.HasMultiplicityAtLeast (P - S) x y m := by + intro a b hab + rw [CBivariate.hasseDerivativeEval_sub, hP a b hab, hS a b hab, sub_zero] + +omit [DecidableEq F] in +private theorem witness_ofYConstant_pow (G : CPolynomial F) (k : Nat) : + CBivariate.ofYConstant (G ^ k) = (CBivariate.ofYConstant G) ^ k := by + induction k with + | zero => + rw [pow_zero, pow_zero] + apply witness_cpoly_eq_of_toPoly_eq (R := CPolynomial F) + show (CPolynomial.C (1 : CPolynomial F)).toPoly = + CPolynomial.toPoly (1 : CPolynomial (CPolynomial F)) + rw [CPolynomial.C_toPoly, CPolynomial.toPoly_one, Polynomial.C_1] + | succ k ih => + rw [pow_succ, pow_succ, ofYConstant_mul, ih] + +/-- A multiple of `G^k` viewed as a `Y`-constant has multiplicity `k` at every +point whose `x`-coordinate is a node of `G`. -/ +private theorem witness_hasMultiplicityAtLeast_C_of_dvd + {points : Array (F × F)} {point : F × F} (hpoint : point ∈ points.toList) + (W : CPolynomial F) (k : Nat) : + CBivariate.HasMultiplicityAtLeast + (CBivariate.ofYConstant (W * + (CPolynomial.vanishingPolynomialArray (points.map fun p ↦ p.1)) ^ k)) + point.1 point.2 k := by + set G := CPolynomial.vanishingPolynomialArray (points.map fun p ↦ p.1) with hG + have hEval : CPolynomial.eval point.1 G = 0 := by + apply CPolynomial.eval_vanishingPolynomialArray_eq_zero_of_mem + rw [Array.toList_map] + exact List.mem_map.mpr ⟨point, hpoint, rfl⟩ + have hsplit : CBivariate.ofYConstant (W * G ^ k) = + (CBivariate.ofYConstant G) ^ k * CBivariate.ofYConstant W := by + rw [ofYConstant_mul, witness_ofYConstant_pow] + ring + rw [hsplit] + have := LeeOSullivan.hasMultiplicityAtLeast_ofYConstant_pow_mul_eval_zero + (F := F) G k (P := CBivariate.ofYConstant W) (x := point.1) (y := point.2) + (m := 0) hEval (witness_hasMultiplicityAtLeast_zero _ _ _) + simpa using this + +/-! ### Main equivalence -/ + +/-- The digit-divisibility recursion recognizes exactly the multiplicity +constraints, for `G` the vanishing polynomial of the distinct `x`-coordinates +and `R` any interpolant of the points. -/ +private theorem witness_viaDivisibility_iff + (Mul : CPolynomial.MulContext F) (Mod : CPolynomial.ModContext F) + {points : Array (F × F)} (R : CPolynomial F) + (hdistinct : DistinctXCoordinates points) + (hR : ∀ point, point ∈ points.toList → CPolynomial.eval point.1 R = point.2) + (m : Nat) (Q : CBivariate F) : + satisfiesMultiplicityConstraintsViaDivisibilityBool Mul Mod + (CPolynomial.vanishingPolynomialArray (points.map fun p ↦ p.1)) R m Q + = true ↔ + CBivariate.SatisfiesMultiplicityConstraints Q points m := by + set G := CPolynomial.vanishingPolynomialArray (points.map fun p ↦ p.1) with hG + induction m generalizing Q with + | zero => + simp only [satisfiesMultiplicityConstraintsViaDivisibilityBool] + constructor + · intro _ point _ + exact witness_hasMultiplicityAtLeast_zero Q point.1 point.2 + · intro _ + trivial + | succ m ih => + have hGpow : (G ^ (m + 1)).monic := + witness_monic_pow (by simpa [hG] using + witness_vanishing_monic (points.map fun p ↦ p.1)) (m + 1) + set c := (CBivariate.divByLinearY Q R).2 with hc + set Q' := (CBivariate.divByLinearY Q R).1 with hq' + have hstep : satisfiesMultiplicityConstraintsViaDivisibilityBool Mul Mod + G R (m + 1) Q = + ((CPolynomial.modByMonic c (G ^ (m + 1)) == 0) && + satisfiesMultiplicityConstraintsViaDivisibilityBool Mul Mod + G R m Q') := by + simp only [satisfiesMultiplicityConstraintsViaDivisibilityBool, + CBivariate.divByLinearYWith_eq_divByLinearY, + Mod.modByMonic_eq_modByMonic, hc, hq'] + rw [hstep, Bool.and_eq_true, beq_iff_eq, + witness_modByMonic_eq_zero_iff hGpow] + constructor + · rintro ⟨⟨W, hW⟩, hrest⟩ + have hQ' : CBivariate.SatisfiesMultiplicityConstraints Q' points m := + (ih Q').mp hrest + intro point hpoint + have hCc : CBivariate.HasMultiplicityAtLeast + (CBivariate.ofYConstant c) point.1 point.2 (m + 1) := by + rw [hW, hG] + exact witness_hasMultiplicityAtLeast_C_of_dvd hpoint W (m + 1) + have hLin : CBivariate.HasMultiplicityAtLeast + (CBivariate.linearYDivisor R * Q') point.1 point.2 (m + 1) := + LeeOSullivan.hasMultiplicityAtLeast_linearYDivisor_mul R + (hR point hpoint) (hQ' point hpoint) + have hsum := witness_hasMultiplicityAtLeast_add hCc hLin + rwa [← witness_divByLinearY_decomposition Q R] at hsum + · intro hQ + have hQ' : CBivariate.SatisfiesMultiplicityConstraints Q' points m := by + intro point hpoint + have hquot := witness_hasMultiplicity_quot (hR point hpoint) + ((CBivariate.hasMultiplicity_iff_hasMultiplicityAtLeast Q (m + 1) + point.1 point.2).mpr (hQ point hpoint)) + exact (CBivariate.hasMultiplicity_iff_hasMultiplicityAtLeast Q' m + point.1 point.2).mp hquot + refine ⟨?_, (ih Q').mpr hQ'⟩ + have hCc : CBivariate.SatisfiesMultiplicityConstraints + (CBivariate.ofYConstant c) points (m + 1) := by + intro point hpoint + have hLin : CBivariate.HasMultiplicityAtLeast + (CBivariate.linearYDivisor R * Q') point.1 point.2 (m + 1) := + LeeOSullivan.hasMultiplicityAtLeast_linearYDivisor_mul R + (hR point hpoint) (hQ' point hpoint) + have hsub := witness_hasMultiplicityAtLeast_sub + (hQ point hpoint) hLin + have hdec : Q - CBivariate.linearYDivisor R * Q' = + CBivariate.ofYConstant c := by + conv_lhs => rw [witness_divByLinearY_decomposition Q R] + ring + rwa [hdec] at hsub + have hY : ∀ j, 0 < j → + (CBivariate.ofYConstant c).val.coeff j = 0 := by + intro j hj + have hcoeffj : CPolynomial.coeff (CBivariate.ofYConstant c) j = 0 := by + show CPolynomial.coeff (CPolynomial.C c) j = 0 + rw [CPolynomial.coeff_C] + simp [Nat.pos_iff_ne_zero.mp hj] + exact hcoeffj + have hdvd := LeeOSullivan.coeffY_dvd_vanishingPolynomial_pow_of_multiplicity + (CPolynomial.VanishingPolynomialContext.direct (F := F)) + (P := CBivariate.ofYConstant c) (m := m + 1) (n := 0) + hdistinct (by omega) hY hCc + rcases hdvd with ⟨W, hW⟩ + refine ⟨W, ?_⟩ + have hcoeff0 : (CBivariate.ofYConstant c).val.coeff 0 = c := by + have hcoeffc : CPolynomial.coeff (CBivariate.ofYConstant c) 0 = c := by + show CPolynomial.coeff (CPolynomial.C c) 0 = c + rw [CPolynomial.coeff_C] + simp + exact hcoeffc + rw [hcoeff0] at hW + simpa [hG, Nat.sub_zero] using hW + +/-- The divisibility-based multiplicity checker agrees with the pointwise +Hasse checker on point sets with distinct `x`-coordinates. -/ +theorem satisfiesMultiplicityConstraintsViaDivisibilityBool_eq + (Mul : CPolynomial.MulContext F) (Mod : CPolynomial.ModContext F) + {points : Array (F × F)} (R : CPolynomial F) + (hdistinct : DistinctXCoordinates points) + (hR : ∀ point, point ∈ points.toList → CPolynomial.eval point.1 R = point.2) + (m : Nat) (Q : CBivariate F) : + satisfiesMultiplicityConstraintsViaDivisibilityBool Mul Mod + (CPolynomial.vanishingPolynomialArray (points.map fun p ↦ p.1)) R m Q = + CBivariate.satisfiesMultiplicityConstraintsBool Q points m := by + rw [Bool.eq_iff_iff, witness_viaDivisibility_iff Mul Mod R hdistinct hR m Q, + CBivariate.satisfiesMultiplicityConstraintsBool_iff_hasMultiplicity, + CBivariate.satisfiesMultiplicityConstraints_iff_hasMultiplicity] + +/-- The divisibility-based witness recognizer agrees with +`interpolationWitnessIsValidBool` on point sets with distinct +`x`-coordinates. -/ +theorem interpolationWitnessIsValidViaDivisibilityBool_eq + (V : CPolynomial.VanishingPolynomialContext F) + (E : CPolynomial.BatchEvalContext F) + (Mul : CPolynomial.MulContext F) (Mod : CPolynomial.ModContext F) + {points : Array (F × F)} (params : GSInterpParams) + (hdistinct : DistinctXCoordinates points) (Q : CBivariate F) : + interpolationWitnessIsValidViaDivisibilityBool V E Mul Mod points params Q = + interpolationWitnessIsValidBool points params Q := by + have hR : ∀ point, point ∈ points.toList → + CPolynomial.eval point.1 + (CPolynomial.interpolateCoefficientFormWithVanishing E + (CPolynomial.vanishingPolynomialArray + (points.map fun point ↦ point.1)) points) = + point.2 := by + intro point hpoint + have heval := + CPolynomial.interpolateCoefficientForm_eval_point V E hdistinct hpoint + rw [CPolynomial.interpolateCoefficientForm, V.correct] at heval + exact heval + simp only [interpolationWitnessIsValidViaDivisibilityBool, + interpolationWitnessIsValidBool, V.correct] + rw [satisfiesMultiplicityConstraintsViaDivisibilityBool_eq Mul Mod _ + hdistinct hR params.multiplicity Q (points := points)] + +end GuruswamiSudan + +end CompPoly diff --git a/bench/CompPolyBench/Bivariate/GuruswamiSudan.lean b/bench/CompPolyBench/Bivariate/GuruswamiSudan.lean index 7b975cbc..e0ee19e1 100644 --- a/bench/CompPolyBench/Bivariate/GuruswamiSudan.lean +++ b/bench/CompPolyBench/Bivariate/GuruswamiSudan.lean @@ -210,13 +210,13 @@ private def runGsInterpolationSmallKoala (preset : BenchPreset) (gen : StdGen) : ] }, gen) -private def runGsInterpolationLargeKoala (preset : BenchPreset) (gen : StdGen) : +private def runGsInterpolationMediumKoala (preset : BenchPreset) (gen : StdGen) : IO (Prod BenchGroup StdGen) := do - let (coeffs, gen) := (koalaBearArray gsLargeInterpMessageDegree false).run gen + let (coeffs, gen) := (koalaBearArray gsMediumInterpMessageDegree false).run gen let message := cpolyOfArray coeffs let fastMessage := cpolyOfArray (koalaBearFastArray coeffs) - let points := gsLargeBenchmarkPoints message - let fastPoints := gsLargeBenchmarkPoints fastMessage + let points := gsMediumBenchmarkPoints message + let fastPoints := gsMediumBenchmarkPoints fastMessage let warmup := gsWarmupIterations preset let koetterMeasured := preset.selectNat 1 1 1 let leeMeasured := preset.selectNat 10 1 1 @@ -234,84 +234,84 @@ private def runGsInterpolationLargeKoala (preset : BenchPreset) (gen : StdGen) : let koetterRow <- runTimed "guruswami-sudan-interp-koetter" "CBivariate" "Koetter" - "KoalaBear.Field" gsLargeInterpInputShape preset warmup koetterMeasured - (fun _ ↦ koetterInterpolate points gsLargeInterpParams) - (checksumInterpolationValidityOption points gsLargeInterpParams) + "KoalaBear.Field" gsMediumInterpInputShape preset warmup koetterMeasured + (fun _ ↦ koetterInterpolate points gsMediumInterpParams) + (checksumInterpolationValidityOption points gsMediumInterpParams) checksumIterations let leeDirectRow <- runTimed "guruswami-sudan-interp-lee-direct" "CBivariate" "Lee-O'Sullivan direct" - "KoalaBear.Field" gsLargeInterpInputShape preset warmup leeMeasured - (fun _ ↦ koalaBearLeeDirectInterpContext.interpolate points gsLargeInterpParams) - (checksumInterpolationValidityOption points gsLargeInterpParams) + "KoalaBear.Field" gsMediumInterpInputShape preset warmup leeMeasured + (fun _ ↦ koalaBearLeeDirectInterpContext.interpolate points gsMediumInterpParams) + (checksumInterpolationValidityOption points gsMediumInterpParams) checksumIterations let leeSubproductRow <- runTimed "guruswami-sudan-interp-lee-subproduct" "CBivariate" "Lee-O'Sullivan subproduct" - "KoalaBear.Field" gsLargeInterpInputShape preset warmup leeMeasured - (fun _ ↦ koalaBearLeeSubproductInterpContext.interpolate points gsLargeInterpParams) - (checksumInterpolationValidityOption points gsLargeInterpParams) + "KoalaBear.Field" gsMediumInterpInputShape preset warmup leeMeasured + (fun _ ↦ koalaBearLeeSubproductInterpContext.interpolate points gsMediumInterpParams) + (checksumInterpolationValidityOption points gsMediumInterpParams) checksumIterations let approximantDirectRow <- runTimed "guruswami-sudan-interp-approximant-direct" "CBivariate" "Approximant basis direct" - "KoalaBear.Field" gsLargeInterpInputShape preset warmup approximantDirectMeasured + "KoalaBear.Field" gsMediumInterpInputShape preset warmup approximantDirectMeasured (fun _ ↦ koalaBearApproximantBasisDirectInterpContext.interpolate points - gsLargeInterpParams) - (checksumInterpolationValidityOption points gsLargeInterpParams) + gsMediumInterpParams) + (checksumInterpolationValidityOption points gsMediumInterpParams) checksumIterations let approximantSubproductRow <- runTimed "guruswami-sudan-interp-approximant-subproduct" "CBivariate" "Approximant basis subproduct" - "KoalaBear.Field" gsLargeInterpInputShape preset warmup approximantSubproductMeasured + "KoalaBear.Field" gsMediumInterpInputShape preset warmup approximantSubproductMeasured (fun _ ↦ koalaBearApproximantBasisSubproductInterpContext.interpolate points - gsLargeInterpParams) - (checksumInterpolationValidityOption points gsLargeInterpParams) + gsMediumInterpParams) + (checksumInterpolationValidityOption points gsMediumInterpParams) checksumIterations let fastKoetterRow <- runTimed "guruswami-sudan-interp-koetter-fast" "CBivariate" "Koetter" - "KoalaBear.Fast.Field" gsLargeInterpInputShape preset warmup fastKoetterMeasured - (fun _ ↦ koetterInterpolate fastPoints gsLargeInterpParams) - (checksumInterpolationValidityOption fastPoints gsLargeInterpParams) + "KoalaBear.Fast.Field" gsMediumInterpInputShape preset warmup fastKoetterMeasured + (fun _ ↦ koetterInterpolate fastPoints gsMediumInterpParams) + (checksumInterpolationValidityOption fastPoints gsMediumInterpParams) checksumIterations let fastLeeDirectRow <- runTimed "guruswami-sudan-interp-lee-direct-fast" "CBivariate" "Lee-O'Sullivan direct" - "KoalaBear.Fast.Field" gsLargeInterpInputShape preset warmup fastLeeMeasured + "KoalaBear.Fast.Field" gsMediumInterpInputShape preset warmup fastLeeMeasured (fun _ ↦ fastKoalaBearLeeDirectInterpContext.interpolate fastPoints - gsLargeInterpParams) - (checksumInterpolationValidityOption fastPoints gsLargeInterpParams) + gsMediumInterpParams) + (checksumInterpolationValidityOption fastPoints gsMediumInterpParams) checksumIterations let fastLeeSubproductRow <- runTimed "guruswami-sudan-interp-lee-subproduct-fast" "CBivariate" "Lee-O'Sullivan subproduct" - "KoalaBear.Fast.Field" gsLargeInterpInputShape preset warmup fastLeeMeasured + "KoalaBear.Fast.Field" gsMediumInterpInputShape preset warmup fastLeeMeasured (fun _ ↦ fastKoalaBearLeeSubproductInterpContext.interpolate fastPoints - gsLargeInterpParams) - (checksumInterpolationValidityOption fastPoints gsLargeInterpParams) + gsMediumInterpParams) + (checksumInterpolationValidityOption fastPoints gsMediumInterpParams) checksumIterations let fastApproximantDirectRow <- runTimed "guruswami-sudan-interp-approximant-direct-fast" "CBivariate" "Approximant basis direct" - "KoalaBear.Fast.Field" gsLargeInterpInputShape preset warmup + "KoalaBear.Fast.Field" gsMediumInterpInputShape preset warmup fastApproximantDirectMeasured (fun _ ↦ fastKoalaBearApproximantBasisDirectInterpContext.interpolate fastPoints - gsLargeInterpParams) - (checksumInterpolationValidityOption fastPoints gsLargeInterpParams) + gsMediumInterpParams) + (checksumInterpolationValidityOption fastPoints gsMediumInterpParams) checksumIterations let fastApproximantSubproductRow <- runTimed "guruswami-sudan-interp-approximant-subproduct-fast" "CBivariate" "Approximant basis subproduct" - "KoalaBear.Fast.Field" gsLargeInterpInputShape preset warmup + "KoalaBear.Fast.Field" gsMediumInterpInputShape preset warmup fastApproximantSubproductMeasured (fun _ ↦ fastKoalaBearApproximantBasisSubproductInterpContext.interpolate fastPoints - gsLargeInterpParams) - (checksumInterpolationValidityOption fastPoints gsLargeInterpParams) + gsMediumInterpParams) + (checksumInterpolationValidityOption fastPoints gsMediumInterpParams) checksumIterations pure ({ - groupKey := "guruswami-sudan-interp-large-koalabear", - title := "Guruswami-Sudan interpolation, large (KoalaBear)", + groupKey := "guruswami-sudan-interp-medium-koalabear", + title := "Guruswami-Sudan interpolation, medium (KoalaBear)", records := #[ koetterRow, leeDirectRow, leeSubproductRow, approximantDirectRow, approximantSubproductRow, @@ -320,13 +320,13 @@ private def runGsInterpolationLargeKoala (preset : BenchPreset) (gen : StdGen) : ] }, gen) -private def runGsLeeSetupLargeKoala (preset : BenchPreset) (gen : StdGen) : +private def runGsLeeSetupMediumKoala (preset : BenchPreset) (gen : StdGen) : IO (Prod BenchGroup StdGen) := do - let (coeffs, gen) := (koalaBearArray gsLargeInterpMessageDegree false).run gen + let (coeffs, gen) := (koalaBearArray gsMediumInterpMessageDegree false).run gen let message := cpolyOfArray coeffs let fastMessage := cpolyOfArray (koalaBearFastArray coeffs) - let points := gsLargeBenchmarkPoints message - let fastPoints := gsLargeBenchmarkPoints fastMessage + let points := gsMediumBenchmarkPoints message + let fastPoints := gsMediumBenchmarkPoints fastMessage let directV : CPolynomial.VanishingPolynomialContext KoalaBear.Field := CPolynomial.VanishingPolynomialContext.direct let subproductV : CPolynomial.VanishingPolynomialContext KoalaBear.Field := @@ -352,32 +352,32 @@ private def runGsLeeSetupLargeKoala (preset : BenchPreset) (gen : StdGen) : let directRow <- runTimed "guruswami-sudan-lee-setup-direct" "PolynomialMatrix" "Lee-O'Sullivan basis setup (direct vanishing)" - "KoalaBear.Field" gsLargeInterpInputShape preset warmup measured - (fun _ ↦ leeOSullivanBasisRows directV hornerE points gsLargeInterpParams) + "KoalaBear.Field" gsMediumInterpInputShape preset warmup measured + (fun _ ↦ leeOSullivanBasisRows directV hornerE points gsMediumInterpParams) (checksumPolynomialMatrix checksumKoalaBear) checksumIterations let subproductRow <- runTimed "guruswami-sudan-lee-setup-subproduct" "PolynomialMatrix" "Lee-O'Sullivan basis setup (subproduct vanishing)" - "KoalaBear.Field" gsLargeInterpInputShape preset warmup measured - (fun _ ↦ leeOSullivanBasisRows subproductV subproductE points gsLargeInterpParams) + "KoalaBear.Field" gsMediumInterpInputShape preset warmup measured + (fun _ ↦ leeOSullivanBasisRows subproductV subproductE points gsMediumInterpParams) (checksumPolynomialMatrix checksumKoalaBear) checksumIterations let fastDirectRow <- runTimed "guruswami-sudan-lee-setup-direct-fast" "PolynomialMatrix" "Lee-O'Sullivan basis setup (direct vanishing)" - "KoalaBear.Fast.Field" gsLargeInterpInputShape preset warmup fastMeasured + "KoalaBear.Fast.Field" gsMediumInterpInputShape preset warmup fastMeasured (fun _ ↦ leeOSullivanBasisRows fastDirectV fastHornerE fastPoints - gsLargeInterpParams) + gsMediumInterpParams) (checksumPolynomialMatrix checksumKoalaBearFast) checksumIterations let fastSubproductRow <- runTimed "guruswami-sudan-lee-setup-subproduct-fast" "PolynomialMatrix" "Lee-O'Sullivan basis setup (subproduct vanishing)" - "KoalaBear.Fast.Field" gsLargeInterpInputShape preset warmup fastMeasured + "KoalaBear.Fast.Field" gsMediumInterpInputShape preset warmup fastMeasured (fun _ ↦ leeOSullivanBasisRows fastSubproductV fastSubproductE fastPoints - gsLargeInterpParams) + gsMediumInterpParams) (checksumPolynomialMatrix checksumKoalaBearFast) checksumIterations pure ({ - groupKey := "guruswami-sudan-lee-setup-large-koalabear", - title := "Guruswami-Sudan Lee-O'Sullivan setup, large (KoalaBear)", + groupKey := "guruswami-sudan-lee-setup-medium-koalabear", + title := "Guruswami-Sudan Lee-O'Sullivan setup, medium (KoalaBear)", records := #[directRow, subproductRow, fastDirectRow, fastSubproductRow] }, gen) @@ -596,9 +596,9 @@ def guruswamiSudanTasks : List BenchTask := [ BenchTask.fromGroupRunner (guruswamiSudanGroupInfos.getD 2 ⟨"guruswami-sudan-interp-small-koalabear", ""⟩) runGsInterpolationSmallKoala, BenchTask.fromGroupRunner (guruswamiSudanGroupInfos.getD 3 - ⟨"guruswami-sudan-interp-large-koalabear", ""⟩) runGsInterpolationLargeKoala, + ⟨"guruswami-sudan-interp-medium-koalabear", ""⟩) runGsInterpolationMediumKoala, BenchTask.fromGroupRunner (guruswamiSudanGroupInfos.getD 4 - ⟨"guruswami-sudan-lee-setup-large-koalabear", ""⟩) runGsLeeSetupLargeKoala, + ⟨"guruswami-sudan-lee-setup-medium-koalabear", ""⟩) runGsLeeSetupMediumKoala, BenchTask.fromGroupRunner (guruswamiSudanGroupInfos.getD 5 ⟨"guruswami-sudan-hasse-koalabear", ""⟩) runGsHasseKoala, BenchTask.fromGroupRunner (guruswamiSudanGroupInfos.getD 6 @@ -608,13 +608,13 @@ def guruswamiSudanTasks : List BenchTask := [ BenchTask.fromGroupRunner (guruswamiSudanGroupInfos.getD 8 ⟨"guruswami-sudan-core-small-koalabear", ""⟩) runGsCoreSmallKoala, BenchTask.fromGroupRunner (guruswamiSudanGroupInfos.getD 9 - ⟨"guruswami-sudan-core-large-koalabear", ""⟩) runGsCoreLargeKoala, + ⟨"guruswami-sudan-core-medium-koalabear", ""⟩) runGsCoreMediumKoala, BenchTask.fromGroupRunner (guruswamiSudanGroupInfos.getD 10 ⟨"guruswami-sudan-packed-filter-koalabear", ""⟩) runGsPackedFilterKoala, BenchTask.fromGroupRunner (guruswamiSudanGroupInfos.getD 11 ⟨"guruswami-sudan-filtered-core-small-koalabear", ""⟩) runGsFilteredCoreSmallKoala, BenchTask.fromGroupRunner (guruswamiSudanGroupInfos.getD 12 - ⟨"guruswami-sudan-filtered-core-large-koalabear", ""⟩) runGsFilteredCoreLargeKoala + ⟨"guruswami-sudan-filtered-core-medium-koalabear", ""⟩) runGsFilteredCoreMediumKoala ] ++ guruswamiSudanReceivedWordTasks end CompPolyBench diff --git a/bench/CompPolyBench/Bivariate/GuruswamiSudan/Core.lean b/bench/CompPolyBench/Bivariate/GuruswamiSudan/Core.lean index a8192043..a4d36e75 100644 --- a/bench/CompPolyBench/Bivariate/GuruswamiSudan/Core.lean +++ b/bench/CompPolyBench/Bivariate/GuruswamiSudan/Core.lean @@ -249,13 +249,13 @@ def runGsCoreSmallKoala (preset : BenchPreset) (gen : StdGen) : ] }, gen) -def runGsCoreLargeKoala (preset : BenchPreset) (gen : StdGen) : +def runGsCoreMediumKoala (preset : BenchPreset) (gen : StdGen) : IO (Prod BenchGroup StdGen) := do - let (coeffs, gen) := (koalaBearArray gsLargeInterpMessageDegree false).run gen + let (coeffs, gen) := (koalaBearArray gsMediumInterpMessageDegree false).run gen let message := cpolyOfArray coeffs let fastMessage := cpolyOfArray (koalaBearFastArray coeffs) - let points := gsLargeBenchmarkPoints message - let fastPoints := gsLargeBenchmarkPoints fastMessage + let points := gsMediumBenchmarkPoints message + let fastPoints := gsMediumBenchmarkPoints fastMessage let rothRootContext := koalaBearRothRootContext let fastRothRootContext := fastKoalaBearRothRootContext let alekRootContext := koalaBearAlekhnovichRootContext @@ -289,159 +289,159 @@ def runGsCoreLargeKoala (preset : BenchPreset) (gen : StdGen) : let koetterRow <- runTimed "guruswami-sudan-core-koetter-roth" "CBivariate" "Koetter + RR roots" - "KoalaBear.Field" gsLargeInterpInputShape preset warmup koetterMeasured + "KoalaBear.Field" gsMediumInterpInputShape preset warmup koetterMeasured (fun _ ↦ gsCore points koalaBearKoetterInterpContext rothRootContext - gsLargeInterpParams) + gsMediumInterpParams) checksumPolynomialArrayKoala checksumIterations let koetterAlekRow <- runTimed "guruswami-sudan-core-koetter-alekhnovich" "CBivariate" "Koetter + Alekhnovich roots" - "KoalaBear.Field" gsLargeInterpInputShape preset warmup alekKoetterMeasured + "KoalaBear.Field" gsMediumInterpInputShape preset warmup alekKoetterMeasured (fun _ ↦ gsCore points koalaBearKoetterInterpContext alekRootContext - gsLargeInterpParams) + gsMediumInterpParams) checksumPolynomialArrayKoala checksumIterations let leeDirectRow <- runTimed "guruswami-sudan-core-lee-direct-roth" "CBivariate" "Lee-O'Sullivan direct + RR roots" - "KoalaBear.Field" gsLargeInterpInputShape preset warmup leeMeasured + "KoalaBear.Field" gsMediumInterpInputShape preset warmup leeMeasured (fun _ ↦ gsCore points koalaBearLeeDirectInterpContext rothRootContext - gsLargeInterpParams) + gsMediumInterpParams) checksumPolynomialArrayKoala checksumIterations let leeDirectAlekRow <- runTimed "guruswami-sudan-core-lee-direct-alekhnovich" "CBivariate" "Lee-O'Sullivan direct + Alekhnovich roots" - "KoalaBear.Field" gsLargeInterpInputShape preset warmup alekLeeMeasured + "KoalaBear.Field" gsMediumInterpInputShape preset warmup alekLeeMeasured (fun _ ↦ gsCore points koalaBearLeeDirectInterpContext alekRootContext - gsLargeInterpParams) + gsMediumInterpParams) checksumPolynomialArrayKoala checksumIterations let leeSubproductRow <- runTimed "guruswami-sudan-core-lee-subproduct-roth" "CBivariate" "Lee-O'Sullivan subproduct + RR roots" - "KoalaBear.Field" gsLargeInterpInputShape preset warmup leeMeasured + "KoalaBear.Field" gsMediumInterpInputShape preset warmup leeMeasured (fun _ ↦ gsCore points koalaBearLeeSubproductInterpContext rothRootContext - gsLargeInterpParams) + gsMediumInterpParams) checksumPolynomialArrayKoala checksumIterations let leeSubproductAlekRow <- runTimed "guruswami-sudan-core-lee-subproduct-alekhnovich" "CBivariate" "Lee-O'Sullivan subproduct + Alekhnovich roots" - "KoalaBear.Field" gsLargeInterpInputShape preset warmup alekLeeMeasured + "KoalaBear.Field" gsMediumInterpInputShape preset warmup alekLeeMeasured (fun _ ↦ gsCore points koalaBearLeeSubproductInterpContext alekRootContext - gsLargeInterpParams) + gsMediumInterpParams) checksumPolynomialArrayKoala checksumIterations let approximantDirectRow <- runTimed "guruswami-sudan-core-approximant-direct-roth" "CBivariate" "Approximant basis direct + RR roots" - "KoalaBear.Field" gsLargeInterpInputShape preset warmup approximantDirectMeasured + "KoalaBear.Field" gsMediumInterpInputShape preset warmup approximantDirectMeasured (fun _ ↦ gsCore points koalaBearApproximantBasisDirectInterpContext - rothRootContext gsLargeInterpParams) + rothRootContext gsMediumInterpParams) checksumPolynomialArrayKoala checksumIterations let approximantDirectAlekRow <- runTimed "guruswami-sudan-core-approximant-direct-alekhnovich" "CBivariate" "Approximant basis direct + Alekhnovich roots" - "KoalaBear.Field" gsLargeInterpInputShape preset warmup alekApproximantDirectMeasured + "KoalaBear.Field" gsMediumInterpInputShape preset warmup alekApproximantDirectMeasured (fun _ ↦ gsCore points koalaBearApproximantBasisDirectInterpContext - alekRootContext gsLargeInterpParams) + alekRootContext gsMediumInterpParams) checksumPolynomialArrayKoala checksumIterations let approximantSubproductRow <- runTimed "guruswami-sudan-core-approximant-subproduct-roth" "CBivariate" "Approximant basis subproduct + RR roots" - "KoalaBear.Field" gsLargeInterpInputShape preset warmup + "KoalaBear.Field" gsMediumInterpInputShape preset warmup approximantSubproductMeasured (fun _ ↦ gsCore points koalaBearApproximantBasisSubproductInterpContext - rothRootContext gsLargeInterpParams) + rothRootContext gsMediumInterpParams) checksumPolynomialArrayKoala checksumIterations let approximantSubproductAlekRow <- runTimed "guruswami-sudan-core-approximant-subproduct-alekhnovich" "CBivariate" "Approximant basis subproduct + Alekhnovich roots" - "KoalaBear.Field" gsLargeInterpInputShape preset warmup + "KoalaBear.Field" gsMediumInterpInputShape preset warmup alekApproximantSubproductMeasured (fun _ ↦ gsCore points koalaBearApproximantBasisSubproductInterpContext - alekRootContext gsLargeInterpParams) + alekRootContext gsMediumInterpParams) checksumPolynomialArrayKoala checksumIterations let fastKoetterRow <- runTimed "guruswami-sudan-core-koetter-roth-fast" "CBivariate" "Koetter + RR roots" - "KoalaBear.Fast.Field" gsLargeInterpInputShape preset warmup fastKoetterMeasured + "KoalaBear.Fast.Field" gsMediumInterpInputShape preset warmup fastKoetterMeasured (fun _ ↦ gsCore fastPoints fastKoalaBearKoetterInterpContext fastRothRootContext - gsLargeInterpParams) + gsMediumInterpParams) checksumPolynomialArrayKoalaFast checksumIterations let fastKoetterAlekRow <- runTimed "guruswami-sudan-core-koetter-alekhnovich-fast" "CBivariate" "Koetter + Alekhnovich roots" - "KoalaBear.Fast.Field" gsLargeInterpInputShape preset warmup + "KoalaBear.Fast.Field" gsMediumInterpInputShape preset warmup fastAlekKoetterMeasured (fun _ ↦ gsCore fastPoints fastKoalaBearKoetterInterpContext fastAlekRootContext - gsLargeInterpParams) + gsMediumInterpParams) checksumPolynomialArrayKoalaFast checksumIterations let fastLeeDirectRow <- runTimed "guruswami-sudan-core-lee-direct-roth-fast" "CBivariate" "Lee-O'Sullivan direct + RR roots" - "KoalaBear.Fast.Field" gsLargeInterpInputShape preset warmup fastLeeMeasured + "KoalaBear.Fast.Field" gsMediumInterpInputShape preset warmup fastLeeMeasured (fun _ ↦ gsCore fastPoints fastKoalaBearLeeDirectInterpContext fastRothRootContext - gsLargeInterpParams) + gsMediumInterpParams) checksumPolynomialArrayKoalaFast checksumIterations let fastLeeDirectAlekRow <- runTimed "guruswami-sudan-core-lee-direct-alekhnovich-fast" "CBivariate" "Lee-O'Sullivan direct + Alekhnovich roots" - "KoalaBear.Fast.Field" gsLargeInterpInputShape preset warmup fastAlekLeeMeasured + "KoalaBear.Fast.Field" gsMediumInterpInputShape preset warmup fastAlekLeeMeasured (fun _ ↦ gsCore fastPoints fastKoalaBearLeeDirectInterpContext fastAlekRootContext - gsLargeInterpParams) + gsMediumInterpParams) checksumPolynomialArrayKoalaFast checksumIterations let fastLeeSubproductRow <- runTimed "guruswami-sudan-core-lee-subproduct-roth-fast" "CBivariate" "Lee-O'Sullivan subproduct + RR roots" - "KoalaBear.Fast.Field" gsLargeInterpInputShape preset warmup fastLeeMeasured + "KoalaBear.Fast.Field" gsMediumInterpInputShape preset warmup fastLeeMeasured (fun _ ↦ gsCore fastPoints fastKoalaBearLeeSubproductInterpContext fastRothRootContext - gsLargeInterpParams) + gsMediumInterpParams) checksumPolynomialArrayKoalaFast checksumIterations let fastLeeSubproductAlekRow <- runTimed "guruswami-sudan-core-lee-subproduct-alekhnovich-fast" "CBivariate" "Lee-O'Sullivan subproduct + Alekhnovich roots" - "KoalaBear.Fast.Field" gsLargeInterpInputShape preset warmup fastAlekLeeMeasured + "KoalaBear.Fast.Field" gsMediumInterpInputShape preset warmup fastAlekLeeMeasured (fun _ ↦ gsCore fastPoints fastKoalaBearLeeSubproductInterpContext fastAlekRootContext - gsLargeInterpParams) + gsMediumInterpParams) checksumPolynomialArrayKoalaFast checksumIterations let fastApproximantDirectRow <- runTimed "guruswami-sudan-core-approximant-direct-roth-fast" "CBivariate" "Approximant basis direct + RR roots" - "KoalaBear.Fast.Field" gsLargeInterpInputShape preset warmup + "KoalaBear.Fast.Field" gsMediumInterpInputShape preset warmup fastApproximantDirectMeasured (fun _ ↦ gsCore fastPoints fastKoalaBearApproximantBasisDirectInterpContext - fastRothRootContext gsLargeInterpParams) + fastRothRootContext gsMediumInterpParams) checksumPolynomialArrayKoalaFast checksumIterations let fastApproximantDirectAlekRow <- runTimed "guruswami-sudan-core-approximant-direct-alekhnovich-fast" "CBivariate" "Approximant basis direct + Alekhnovich roots" - "KoalaBear.Fast.Field" gsLargeInterpInputShape preset warmup + "KoalaBear.Fast.Field" gsMediumInterpInputShape preset warmup fastAlekApproximantDirectMeasured (fun _ ↦ gsCore fastPoints fastKoalaBearApproximantBasisDirectInterpContext - fastAlekRootContext gsLargeInterpParams) + fastAlekRootContext gsMediumInterpParams) checksumPolynomialArrayKoalaFast checksumIterations let fastApproximantSubproductRow <- runTimed "guruswami-sudan-core-approximant-subproduct-roth-fast" "CBivariate" "Approximant basis subproduct + RR roots" - "KoalaBear.Fast.Field" gsLargeInterpInputShape preset warmup + "KoalaBear.Fast.Field" gsMediumInterpInputShape preset warmup fastApproximantSubproductMeasured (fun _ ↦ gsCore fastPoints fastKoalaBearApproximantBasisSubproductInterpContext - fastRothRootContext gsLargeInterpParams) + fastRothRootContext gsMediumInterpParams) checksumPolynomialArrayKoalaFast checksumIterations let fastApproximantSubproductAlekRow <- runTimed "guruswami-sudan-core-approximant-subproduct-alekhnovich-fast" "CBivariate" "Approximant basis subproduct + Alekhnovich roots" - "KoalaBear.Fast.Field" gsLargeInterpInputShape preset warmup + "KoalaBear.Fast.Field" gsMediumInterpInputShape preset warmup fastAlekApproximantSubproductMeasured (fun _ ↦ gsCore fastPoints fastKoalaBearApproximantBasisSubproductInterpContext - fastAlekRootContext gsLargeInterpParams) + fastAlekRootContext gsMediumInterpParams) checksumPolynomialArrayKoalaFast checksumIterations pure ({ - groupKey := "guruswami-sudan-core-large-koalabear", - title := "Guruswami-Sudan full core, large (KoalaBear)", + groupKey := "guruswami-sudan-core-medium-koalabear", + title := "Guruswami-Sudan full core, medium (KoalaBear)", records := #[ koetterRow, koetterAlekRow, leeDirectRow, leeDirectAlekRow, leeSubproductRow, leeSubproductAlekRow, approximantDirectRow, approximantDirectAlekRow, @@ -702,13 +702,13 @@ def runGsFilteredCoreSmallKoala (preset : BenchPreset) (gen : StdGen) : ] }, gen) -def runGsFilteredCoreLargeKoala (preset : BenchPreset) (gen : StdGen) : +def runGsFilteredCoreMediumKoala (preset : BenchPreset) (gen : StdGen) : IO (Prod BenchGroup StdGen) := do - let (coeffs, gen) := (koalaBearArray gsLargeInterpMessageDegree false).run gen + let (coeffs, gen) := (koalaBearArray gsMediumInterpMessageDegree false).run gen let message := cpolyOfArray coeffs let fastMessage := cpolyOfArray (koalaBearFastArray coeffs) - let points := gsLargeBenchmarkPoints message - let fastPoints := gsLargeBenchmarkPoints fastMessage + let points := gsMediumBenchmarkPoints message + let fastPoints := gsMediumBenchmarkPoints fastMessage let rothRootContext := koalaBearRothRootContext let fastRothRootContext := fastKoalaBearRothRootContext let alekRootContext := koalaBearAlekhnovichRootContext @@ -745,7 +745,7 @@ def runGsFilteredCoreLargeKoala (preset : BenchPreset) (gen : StdGen) : "KoalaBear.Field" gsFilteredShape preset warmup koetterMeasured (fun _ ↦ gsFilteredCore points koalaBearKoetterInterpContext rothRootContext - gsLargeInterpParams 0) + gsMediumInterpParams 0) checksumPolynomialArrayKoala checksumIterations let koetterAlekRow <- runTimed "guruswami-sudan-filtered-core-koetter-alekhnovich" "CBivariate" @@ -753,7 +753,7 @@ def runGsFilteredCoreLargeKoala (preset : BenchPreset) (gen : StdGen) : "KoalaBear.Field" gsFilteredShape preset warmup alekKoetterMeasured (fun _ ↦ gsFilteredCore points koalaBearKoetterInterpContext alekRootContext - gsLargeInterpParams 0) + gsMediumInterpParams 0) checksumPolynomialArrayKoala checksumIterations let leeDirectRow <- runTimed "guruswami-sudan-filtered-core-lee-direct" "CBivariate" @@ -761,7 +761,7 @@ def runGsFilteredCoreLargeKoala (preset : BenchPreset) (gen : StdGen) : "KoalaBear.Field" gsFilteredShape preset warmup leeMeasured (fun _ ↦ gsFilteredCore points koalaBearLeeDirectInterpContext rothRootContext - gsLargeInterpParams 0) + gsMediumInterpParams 0) checksumPolynomialArrayKoala checksumIterations let leeDirectAlekRow <- runTimed "guruswami-sudan-filtered-core-lee-direct-alekhnovich" "CBivariate" @@ -769,7 +769,7 @@ def runGsFilteredCoreLargeKoala (preset : BenchPreset) (gen : StdGen) : "KoalaBear.Field" gsFilteredShape preset warmup alekLeeMeasured (fun _ ↦ gsFilteredCore points koalaBearLeeDirectInterpContext alekRootContext - gsLargeInterpParams 0) + gsMediumInterpParams 0) checksumPolynomialArrayKoala checksumIterations let leeSubproductRow <- runTimed "guruswami-sudan-filtered-core-lee-subproduct" "CBivariate" @@ -777,7 +777,7 @@ def runGsFilteredCoreLargeKoala (preset : BenchPreset) (gen : StdGen) : "KoalaBear.Field" gsFilteredShape preset warmup leeMeasured (fun _ ↦ gsFilteredCore points koalaBearLeeSubproductInterpContext rothRootContext - gsLargeInterpParams 0) + gsMediumInterpParams 0) checksumPolynomialArrayKoala checksumIterations let leeSubproductAlekRow <- runTimed "guruswami-sudan-filtered-core-lee-subproduct-alekhnovich" "CBivariate" @@ -785,35 +785,35 @@ def runGsFilteredCoreLargeKoala (preset : BenchPreset) (gen : StdGen) : "KoalaBear.Field" gsFilteredShape preset warmup alekLeeMeasured (fun _ ↦ gsFilteredCore points koalaBearLeeSubproductInterpContext alekRootContext - gsLargeInterpParams 0) + gsMediumInterpParams 0) checksumPolynomialArrayKoala checksumIterations let approximantDirectRow <- runTimed "guruswami-sudan-filtered-core-approximant-direct-roth" "CBivariate" "Approximant basis direct + RR roots + filter" "KoalaBear.Field" gsFilteredShape preset warmup approximantDirectMeasured (fun _ ↦ gsFilteredCore points koalaBearApproximantBasisDirectInterpContext - rothRootContext gsLargeInterpParams 0) + rothRootContext gsMediumInterpParams 0) checksumPolynomialArrayKoala checksumIterations let approximantDirectAlekRow <- runTimed "guruswami-sudan-filtered-core-approximant-direct-alekhnovich" "CBivariate" "Approximant basis direct + Alekhnovich roots + filter" "KoalaBear.Field" gsFilteredShape preset warmup alekApproximantDirectMeasured (fun _ ↦ gsFilteredCore points koalaBearApproximantBasisDirectInterpContext - alekRootContext gsLargeInterpParams 0) + alekRootContext gsMediumInterpParams 0) checksumPolynomialArrayKoala checksumIterations let approximantSubproductRow <- runTimed "guruswami-sudan-filtered-core-approximant-subproduct-roth" "CBivariate" "Approximant basis subproduct + RR roots + filter" "KoalaBear.Field" gsFilteredShape preset warmup approximantSubproductMeasured (fun _ ↦ gsFilteredCore points koalaBearApproximantBasisSubproductInterpContext - rothRootContext gsLargeInterpParams 0) + rothRootContext gsMediumInterpParams 0) checksumPolynomialArrayKoala checksumIterations let approximantSubproductAlekRow <- runTimed "guruswami-sudan-filtered-core-approximant-subproduct-alekhnovich" "CBivariate" "Approximant basis subproduct + Alekhnovich roots + filter" "KoalaBear.Field" gsFilteredShape preset warmup alekApproximantSubproductMeasured (fun _ ↦ gsFilteredCore points koalaBearApproximantBasisSubproductInterpContext - alekRootContext gsLargeInterpParams 0) + alekRootContext gsMediumInterpParams 0) checksumPolynomialArrayKoala checksumIterations let fastKoetterRow <- runTimed "guruswami-sudan-filtered-core-koetter-fast" "CBivariate" @@ -821,7 +821,7 @@ def runGsFilteredCoreLargeKoala (preset : BenchPreset) (gen : StdGen) : "KoalaBear.Fast.Field" gsFilteredShape preset warmup fastKoetterMeasured (fun _ ↦ gsFilteredCore fastPoints fastKoalaBearKoetterInterpContext fastRothRootContext - gsLargeInterpParams 0) + gsMediumInterpParams 0) checksumPolynomialArrayKoalaFast checksumIterations let fastKoetterAlekRow <- runTimed "guruswami-sudan-filtered-core-koetter-alekhnovich-fast" "CBivariate" @@ -829,7 +829,7 @@ def runGsFilteredCoreLargeKoala (preset : BenchPreset) (gen : StdGen) : "KoalaBear.Fast.Field" gsFilteredShape preset warmup fastAlekKoetterMeasured (fun _ ↦ gsFilteredCore fastPoints fastKoalaBearKoetterInterpContext fastAlekRootContext - gsLargeInterpParams 0) + gsMediumInterpParams 0) checksumPolynomialArrayKoalaFast checksumIterations let fastLeeDirectRow <- runTimed "guruswami-sudan-filtered-core-lee-direct-fast" "CBivariate" @@ -837,7 +837,7 @@ def runGsFilteredCoreLargeKoala (preset : BenchPreset) (gen : StdGen) : "KoalaBear.Fast.Field" gsFilteredShape preset warmup fastLeeMeasured (fun _ ↦ gsFilteredCore fastPoints fastKoalaBearLeeDirectInterpContext fastRothRootContext - gsLargeInterpParams 0) + gsMediumInterpParams 0) checksumPolynomialArrayKoalaFast checksumIterations let fastLeeDirectAlekRow <- runTimed "guruswami-sudan-filtered-core-lee-direct-alekhnovich-fast" "CBivariate" @@ -845,7 +845,7 @@ def runGsFilteredCoreLargeKoala (preset : BenchPreset) (gen : StdGen) : "KoalaBear.Fast.Field" gsFilteredShape preset warmup fastAlekLeeMeasured (fun _ ↦ gsFilteredCore fastPoints fastKoalaBearLeeDirectInterpContext fastAlekRootContext - gsLargeInterpParams 0) + gsMediumInterpParams 0) checksumPolynomialArrayKoalaFast checksumIterations let fastLeeSubproductRow <- runTimed "guruswami-sudan-filtered-core-lee-subproduct-fast" "CBivariate" @@ -853,7 +853,7 @@ def runGsFilteredCoreLargeKoala (preset : BenchPreset) (gen : StdGen) : "KoalaBear.Fast.Field" gsFilteredShape preset warmup fastLeeMeasured (fun _ ↦ gsFilteredCore fastPoints fastKoalaBearLeeSubproductInterpContext fastRothRootContext - gsLargeInterpParams 0) + gsMediumInterpParams 0) checksumPolynomialArrayKoalaFast checksumIterations let fastLeeSubproductAlekRow <- runTimed "guruswami-sudan-filtered-core-lee-subproduct-alekhnovich-fast" "CBivariate" @@ -861,7 +861,7 @@ def runGsFilteredCoreLargeKoala (preset : BenchPreset) (gen : StdGen) : "KoalaBear.Fast.Field" gsFilteredShape preset warmup fastAlekLeeMeasured (fun _ ↦ gsFilteredCore fastPoints fastKoalaBearLeeSubproductInterpContext fastAlekRootContext - gsLargeInterpParams 0) + gsMediumInterpParams 0) checksumPolynomialArrayKoalaFast checksumIterations let fastApproximantDirectRow <- runTimed "guruswami-sudan-filtered-core-approximant-direct-roth-fast" "CBivariate" @@ -869,7 +869,7 @@ def runGsFilteredCoreLargeKoala (preset : BenchPreset) (gen : StdGen) : "KoalaBear.Fast.Field" gsFilteredShape preset warmup fastApproximantDirectMeasured (fun _ ↦ gsFilteredCore fastPoints fastKoalaBearApproximantBasisDirectInterpContext fastRothRootContext - gsLargeInterpParams 0) + gsMediumInterpParams 0) checksumPolynomialArrayKoalaFast checksumIterations let fastApproximantDirectAlekRow <- runTimed "guruswami-sudan-filtered-core-approximant-direct-alekhnovich-fast" "CBivariate" @@ -878,7 +878,7 @@ def runGsFilteredCoreLargeKoala (preset : BenchPreset) (gen : StdGen) : fastAlekApproximantDirectMeasured (fun _ ↦ gsFilteredCore fastPoints fastKoalaBearApproximantBasisDirectInterpContext fastAlekRootContext - gsLargeInterpParams 0) + gsMediumInterpParams 0) checksumPolynomialArrayKoalaFast checksumIterations let fastApproximantSubproductRow <- runTimed "guruswami-sudan-filtered-core-approximant-subproduct-roth-fast" "CBivariate" @@ -887,7 +887,7 @@ def runGsFilteredCoreLargeKoala (preset : BenchPreset) (gen : StdGen) : fastApproximantSubproductMeasured (fun _ ↦ gsFilteredCore fastPoints fastKoalaBearApproximantBasisSubproductInterpContext fastRothRootContext - gsLargeInterpParams 0) + gsMediumInterpParams 0) checksumPolynomialArrayKoalaFast checksumIterations let fastApproximantSubproductAlekRow <- runTimed "guruswami-sudan-filtered-core-approximant-subproduct-alekhnovich-fast" @@ -897,11 +897,11 @@ def runGsFilteredCoreLargeKoala (preset : BenchPreset) (gen : StdGen) : fastAlekApproximantSubproductMeasured (fun _ ↦ gsFilteredCore fastPoints fastKoalaBearApproximantBasisSubproductInterpContext fastAlekRootContext - gsLargeInterpParams 0) + gsMediumInterpParams 0) checksumPolynomialArrayKoalaFast checksumIterations pure ({ - groupKey := "guruswami-sudan-filtered-core-large-koalabear", - title := "Guruswami-Sudan filtered core, large (KoalaBear)", + groupKey := "guruswami-sudan-filtered-core-medium-koalabear", + title := "Guruswami-Sudan filtered core, medium (KoalaBear)", records := #[ koetterRow, koetterAlekRow, leeDirectRow, leeDirectAlekRow, leeSubproductRow, leeSubproductAlekRow, approximantDirectRow, approximantDirectAlekRow, diff --git a/bench/CompPolyBench/Bivariate/GuruswamiSudan/ReceivedWord.lean b/bench/CompPolyBench/Bivariate/GuruswamiSudan/ReceivedWord.lean index 1076aaf9..800b55c6 100644 --- a/bench/CompPolyBench/Bivariate/GuruswamiSudan/ReceivedWord.lean +++ b/bench/CompPolyBench/Bivariate/GuruswamiSudan/ReceivedWord.lean @@ -10,9 +10,11 @@ import CompPolyBench.Bivariate.GuruswamiSudan.Shared # Guruswami-Sudan Perturbed Received-Word Benchmarks Perturbed (non-codeword) counterparts of the codeword interpolation, core, and -filtered-core benchmark groups, over the same small and large input shapes and -the same backend rows. Perturbations stay within the Guruswami-Sudan decoding -radius, so the full pipeline still recovers the original message. +filtered-core benchmark groups, over the same small and medium input shapes and +the same backend rows, plus a long-code large interpolation group comparing +only the Lee-O'Sullivan and approximant backends. Perturbations stay within +the Guruswami-Sudan decoding radius, so the full pipeline still recovers the +original message. Different interpolation backends may return different valid witnesses on the same perturbed word, so interpolation rows checksum the witness validity class @@ -27,33 +29,39 @@ namespace CompPolyBench /-! ### Perturbation shapes The small shape perturbs every 3rd symbol (22 errors at `n=64`, inside the -`m=2, D=75` decoding radius of 26), the large shape every 7th symbol -(28 errors at `n=192`, inside the radius of 71). Filtered groups use the +`m=2, D=75` decoding radius of 26), the medium and large shapes every 7th +symbol (28 errors at `n=192`, inside the radius of 71; 720 errors at +`n=5040`, inside the `m=2, D=6300` radius of 1890). Filtered groups use the exact error count as the distance radius. -/ private def gsNonCodewordSmallPeriod : Nat := 3 +private def gsNonCodewordMediumPeriod : Nat := 7 + private def gsNonCodewordLargePeriod : Nat := 7 private def gsNonCodewordSmallErrors : Nat := (gsSmallPointCount + gsNonCodewordSmallPeriod - 1) / gsNonCodewordSmallPeriod -private def gsNonCodewordLargeErrors : Nat := - (gsLargeInterpPointCount + gsNonCodewordLargePeriod - 1) / - gsNonCodewordLargePeriod +private def gsNonCodewordMediumErrors : Nat := + (gsMediumInterpPointCount + gsNonCodewordMediumPeriod - 1) / + gsNonCodewordMediumPeriod private def gsNonCodewordSmallInputShape : String := gsSmallInterpInputShape ++ s!",errors=every{gsNonCodewordSmallPeriod}" +private def gsNonCodewordMediumInputShape : String := + gsMediumInterpInputShape ++ s!",errors=every{gsNonCodewordMediumPeriod}" + private def gsNonCodewordLargeInputShape : String := gsLargeInterpInputShape ++ s!",errors=every{gsNonCodewordLargePeriod}" private def gsNonCodewordSmallFilteredShape : String := gsNonCodewordSmallInputShape ++ s!",r={gsNonCodewordSmallErrors}" -private def gsNonCodewordLargeFilteredShape : String := - gsNonCodewordLargeInputShape ++ s!",r={gsNonCodewordLargeErrors}" +private def gsNonCodewordMediumFilteredShape : String := + gsNonCodewordMediumInputShape ++ s!",r={gsNonCodewordMediumErrors}" private def perturbEveryNthY {F : Type*} [Add F] [OfNat F 1] (period : Nat) (points : Array (Prod F F)) : Array (Prod F F) := @@ -113,11 +121,14 @@ private def runPerturbedGroup (info : BenchGroupInfo) (inputShape : String) records := records.push record pure { groupKey := info.groupKey, title := info.title, records := records } -/-- Interpolation rows for one field implementation. -/ +/-- Interpolation rows for one field implementation. `checksumQ` maps the +returned witness option to its checksum class. -/ private def perturbedInterpRows {F : Type*} [Field F] [BEq F] [LawfulBEq F] [DecidableEq F] (fieldName fieldSuffix sizeKey : String) (preset : BenchPreset) (points : Array (Prod F F)) (params : GSInterpParams) + (checksumQ : + Array (Prod F F) → GSInterpParams → Option (CBivariate F) → Nat) (backends : Array (PerturbedBackend F)) : Array PerturbedRowSpec := backends.map fun backend ↦ { key := @@ -126,8 +137,29 @@ private def perturbedInterpRows {F : Type*} [Field F] [BEq F] [LawfulBEq F] field := fieldName measured := backend.measured preset run := fun _ ↦ - checksumInterpolationValidityOption points params - (backend.ctx.interpolate points params) } + checksumQ points params (backend.ctx.interpolate points params) } + +/-- Witness validity class through the divisibility-based full check: the +per-point Hasse check costs `O(n * m^2 * l * D)` term evaluations — minutes +per row at `n ≈ 5000`, dominating every backend inside the timed closure — so +the large group validates witnesses through the base-`(Y - R)` digit +divisibility characterization, which checks the multiplicity constraints at +every point in seconds with NTT-backed contexts. -/ +private def checksumDivisibilityInterpolationValidityOption {F : Type*} + [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + (V : CPolynomial.VanishingPolynomialContext F) + (E : CPolynomial.BatchEvalContext F) + (Mul : CPolynomial.MulContext F) (Mod : CPolynomial.ModContext F) + (points : Array (F × F)) (params : GSInterpParams) + (Q? : Option (CBivariate F)) : Nat := + match Q? with + | none => 0 + | some Q => + if interpolationWitnessIsValidViaDivisibilityBool V E Mul Mod + points params Q then + 2 + else + 1 /-- Core or filtered-core rows for one field implementation: one RR-roots row and one Alekhnovich-roots row per backend. `filtered` selects `gsFilteredCore`. @@ -200,7 +232,7 @@ private def gsNonCodewordSmallFastBackends : fastKoalaBearApproximantBasisSubproductInterpContext, 6, 1, 1⟩ ] -private def gsNonCodewordLargeSlowBackends : +private def gsNonCodewordMediumSlowBackends : Array (PerturbedBackend KoalaBear.Field) := #[ ⟨"koetter", "Koetter", koalaBearKoetterInterpContext, 1, 1, 1⟩, ⟨"lee-direct", "Lee-O'Sullivan direct", koalaBearLeeDirectInterpContext, @@ -213,7 +245,7 @@ private def gsNonCodewordLargeSlowBackends : koalaBearApproximantBasisSubproductInterpContext, 1, 1, 1⟩ ] -private def gsNonCodewordLargeFastBackends : +private def gsNonCodewordMediumFastBackends : Array (PerturbedBackend KoalaBear.Fast.Field) := #[ ⟨"koetter", "Koetter", fastKoalaBearKoetterInterpContext, 2, 1, 1⟩, ⟨"lee-direct", "Lee-O'Sullivan direct", fastKoalaBearLeeDirectInterpContext, @@ -226,22 +258,41 @@ private def gsNonCodewordLargeFastBackends : fastKoalaBearApproximantBasisSubproductInterpContext, 1, 1, 1⟩ ] +/-- Long-code interpolation backends: only the two reduction-based backends +stay tractable at `n ≈ 5000` (Koetter grows like `~n^3.4` and the dense +solver far faster), so the large group benchmarks Lee-O'Sullivan against the +approximant basis, and only over the native-word fast field — canonical +KoalaBear multiplies every row by another ~3-5x without changing the +comparison. Only the subproduct variants run: the direct variants differ +just in the quadratic-in-`n` setup (vanishing polynomial, interpolant, and +modular products without a subproduct tree), which dominates their rows at +this size and adds minutes without informing the solver comparison. -/ +private def gsNonCodewordLargeFastBackends : + Array (PerturbedBackend KoalaBear.Fast.Field) := #[ + ⟨"lee-subproduct", "Lee-O'Sullivan subproduct", + fastKoalaBearLeeSubproductInterpContext, 2, 1, 1⟩, + ⟨"approximant-subproduct", "Approximant basis subproduct", + fastKoalaBearApproximantBasisSubproductInterpContext, 4, 2, 1⟩ +] + /-! ### Group metadata -/ /-- Benchmark group metadata for perturbed received-word rows. -/ def guruswamiSudanReceivedWordGroupInfos : List BenchGroupInfo := [ ⟨"guruswami-sudan-interp-noncodeword-small-koalabear", "Guruswami-Sudan interpolation on perturbed received word, small (KoalaBear)"⟩, - ⟨"guruswami-sudan-interp-noncodeword-large-koalabear", - "Guruswami-Sudan interpolation on perturbed received word, large (KoalaBear)"⟩, + ⟨"guruswami-sudan-interp-noncodeword-medium-koalabear", + "Guruswami-Sudan interpolation on perturbed received word, medium (KoalaBear)"⟩, ⟨"guruswami-sudan-core-noncodeword-small-koalabear", "Guruswami-Sudan full core on perturbed received word, small (KoalaBear)"⟩, - ⟨"guruswami-sudan-core-noncodeword-large-koalabear", - "Guruswami-Sudan full core on perturbed received word, large (KoalaBear)"⟩, + ⟨"guruswami-sudan-core-noncodeword-medium-koalabear", + "Guruswami-Sudan full core on perturbed received word, medium (KoalaBear)"⟩, ⟨"guruswami-sudan-filtered-core-noncodeword-small-koalabear", "Guruswami-Sudan filtered core on perturbed received word, small (KoalaBear)"⟩, - ⟨"guruswami-sudan-filtered-core-noncodeword-large-koalabear", - "Guruswami-Sudan filtered core on perturbed received word, large (KoalaBear)"⟩ + ⟨"guruswami-sudan-filtered-core-noncodeword-medium-koalabear", + "Guruswami-Sudan filtered core on perturbed received word, medium (KoalaBear)"⟩, + ⟨"guruswami-sudan-interp-noncodeword-large-koalabear", + "Guruswami-Sudan interpolation on perturbed received word, large (KoalaBear)"⟩ ] /-! ### Shared per-shape inputs -/ @@ -260,6 +311,16 @@ private def perturbedSmallInputs (gen : StdGen) : PerturbedInputs × StdGen := (codewordPointsWithCount gsSmallPointCount fastMessage) ({ points := points, fastPoints := fastPoints }, gen) +private def perturbedMediumInputs (gen : StdGen) : PerturbedInputs × StdGen := + let (coeffs, gen) := (koalaBearArray gsMediumInterpMessageDegree false).run gen + let message := cpolyOfArray coeffs + let fastMessage := cpolyOfArray (koalaBearFastArray coeffs) + let points := perturbEveryNthY gsNonCodewordMediumPeriod + (codewordPointsWithCount gsMediumInterpPointCount message) + let fastPoints := perturbEveryNthY gsNonCodewordMediumPeriod + (codewordPointsWithCount gsMediumInterpPointCount fastMessage) + ({ points := points, fastPoints := fastPoints }, gen) + private def perturbedLargeInputs (gen : StdGen) : PerturbedInputs × StdGen := let (coeffs, gen) := (koalaBearArray gsLargeInterpMessageDegree false).run gen let message := cpolyOfArray coeffs @@ -277,27 +338,31 @@ private def runGsInterpolationNonCodewordSmallKoala (preset : BenchPreset) let (inputs, gen) := perturbedSmallInputs gen let rows := perturbedInterpRows "KoalaBear.Field" "" "small" preset - inputs.points gsSmallParams gsNonCodewordSmallSlowBackends ++ + inputs.points gsSmallParams checksumInterpolationValidityOption + gsNonCodewordSmallSlowBackends ++ perturbedInterpRows "KoalaBear.Fast.Field" "-fast" "small" preset - inputs.fastPoints gsSmallParams gsNonCodewordSmallFastBackends + inputs.fastPoints gsSmallParams checksumInterpolationValidityOption + gsNonCodewordSmallFastBackends let group ← runPerturbedGroup (guruswamiSudanReceivedWordGroupInfos.getD 0 ⟨"guruswami-sudan-interp-noncodeword-small-koalabear", ""⟩) gsNonCodewordSmallInputShape preset rows pure (group, gen) -private def runGsInterpolationNonCodewordLargeKoala (preset : BenchPreset) +private def runGsInterpolationNonCodewordMediumKoala (preset : BenchPreset) (gen : StdGen) : IO (Prod BenchGroup StdGen) := do - let (inputs, gen) := perturbedLargeInputs gen + let (inputs, gen) := perturbedMediumInputs gen let rows := - perturbedInterpRows "KoalaBear.Field" "" "large" preset - inputs.points gsLargeInterpParams gsNonCodewordLargeSlowBackends ++ - perturbedInterpRows "KoalaBear.Fast.Field" "-fast" "large" preset - inputs.fastPoints gsLargeInterpParams gsNonCodewordLargeFastBackends + perturbedInterpRows "KoalaBear.Field" "" "medium" preset + inputs.points gsMediumInterpParams checksumInterpolationValidityOption + gsNonCodewordMediumSlowBackends ++ + perturbedInterpRows "KoalaBear.Fast.Field" "-fast" "medium" preset + inputs.fastPoints gsMediumInterpParams checksumInterpolationValidityOption + gsNonCodewordMediumFastBackends let group ← runPerturbedGroup (guruswamiSudanReceivedWordGroupInfos.getD 1 - ⟨"guruswami-sudan-interp-noncodeword-large-koalabear", ""⟩) - gsNonCodewordLargeInputShape preset rows + ⟨"guruswami-sudan-interp-noncodeword-medium-koalabear", ""⟩) + gsNonCodewordMediumInputShape preset rows pure (group, gen) private def runGsCoreNonCodewordSmallKoala (preset : BenchPreset) @@ -320,24 +385,24 @@ private def runGsCoreNonCodewordSmallKoala (preset : BenchPreset) gsNonCodewordSmallInputShape preset rows pure (group, gen) -private def runGsCoreNonCodewordLargeKoala (preset : BenchPreset) +private def runGsCoreNonCodewordMediumKoala (preset : BenchPreset) (gen : StdGen) : IO (Prod BenchGroup StdGen) := do - let (inputs, gen) := perturbedLargeInputs gen + let (inputs, gen) := perturbedMediumInputs gen let rows := - perturbedCoreRows "core" "KoalaBear.Field" "" "large" "" false - gsNonCodewordLargeErrors preset - inputs.points gsLargeInterpParams checksumPolynomialArrayKoala + perturbedCoreRows "core" "KoalaBear.Field" "" "medium" "" false + gsNonCodewordMediumErrors preset + inputs.points gsMediumInterpParams checksumPolynomialArrayKoala koalaBearRothRootContext koalaBearAlekhnovichRootContext - gsNonCodewordLargeSlowBackends ++ - perturbedCoreRows "core" "KoalaBear.Fast.Field" "-fast" "large" "" false - gsNonCodewordLargeErrors preset - inputs.fastPoints gsLargeInterpParams checksumPolynomialArrayKoalaFast + gsNonCodewordMediumSlowBackends ++ + perturbedCoreRows "core" "KoalaBear.Fast.Field" "-fast" "medium" "" false + gsNonCodewordMediumErrors preset + inputs.fastPoints gsMediumInterpParams checksumPolynomialArrayKoalaFast fastKoalaBearRothRootContext fastKoalaBearAlekhnovichRootContext - gsNonCodewordLargeFastBackends + gsNonCodewordMediumFastBackends let group ← runPerturbedGroup (guruswamiSudanReceivedWordGroupInfos.getD 3 - ⟨"guruswami-sudan-core-noncodeword-large-koalabear", ""⟩) - gsNonCodewordLargeInputShape preset rows + ⟨"guruswami-sudan-core-noncodeword-medium-koalabear", ""⟩) + gsNonCodewordMediumInputShape preset rows pure (group, gen) private def runGsFilteredCoreNonCodewordSmallKoala (preset : BenchPreset) @@ -360,24 +425,42 @@ private def runGsFilteredCoreNonCodewordSmallKoala (preset : BenchPreset) gsNonCodewordSmallFilteredShape preset rows pure (group, gen) -private def runGsFilteredCoreNonCodewordLargeKoala (preset : BenchPreset) +private def runGsFilteredCoreNonCodewordMediumKoala (preset : BenchPreset) (gen : StdGen) : IO (Prod BenchGroup StdGen) := do - let (inputs, gen) := perturbedLargeInputs gen + let (inputs, gen) := perturbedMediumInputs gen let rows := - perturbedCoreRows "filtered-core" "KoalaBear.Field" "" "large" " + filter" - true gsNonCodewordLargeErrors preset - inputs.points gsLargeInterpParams checksumPolynomialArrayKoala + perturbedCoreRows "filtered-core" "KoalaBear.Field" "" "medium" " + filter" + true gsNonCodewordMediumErrors preset + inputs.points gsMediumInterpParams checksumPolynomialArrayKoala koalaBearRothRootContext koalaBearAlekhnovichRootContext - gsNonCodewordLargeSlowBackends ++ - perturbedCoreRows "filtered-core" "KoalaBear.Fast.Field" "-fast" "large" - " + filter" true gsNonCodewordLargeErrors preset - inputs.fastPoints gsLargeInterpParams checksumPolynomialArrayKoalaFast + gsNonCodewordMediumSlowBackends ++ + perturbedCoreRows "filtered-core" "KoalaBear.Fast.Field" "-fast" "medium" + " + filter" true gsNonCodewordMediumErrors preset + inputs.fastPoints gsMediumInterpParams checksumPolynomialArrayKoalaFast fastKoalaBearRothRootContext fastKoalaBearAlekhnovichRootContext - gsNonCodewordLargeFastBackends + gsNonCodewordMediumFastBackends let group ← runPerturbedGroup (guruswamiSudanReceivedWordGroupInfos.getD 5 - ⟨"guruswami-sudan-filtered-core-noncodeword-large-koalabear", ""⟩) - gsNonCodewordLargeFilteredShape preset rows + ⟨"guruswami-sudan-filtered-core-noncodeword-medium-koalabear", ""⟩) + gsNonCodewordMediumFilteredShape preset rows + pure (group, gen) + +private def runGsInterpolationNonCodewordLargeKoala (preset : BenchPreset) + (gen : StdGen) : IO (Prod BenchGroup StdGen) := do + let (inputs, gen) := perturbedLargeInputs gen + let rows := + perturbedInterpRows "KoalaBear.Fast.Field" "-fast" "large" preset + inputs.fastPoints gsLargeInterpParams + (checksumDivisibilityInterpolationValidityOption + (CPolynomial.VanishingPolynomialContext.subproduct + fastKoalaBearNttFastMulContext) + fastKoalaBearNttFastBatchEvalContext + fastKoalaBearNttFastMulContext fastKoalaBearNttFastModContext) + gsNonCodewordLargeFastBackends + let group ← runPerturbedGroup + (guruswamiSudanReceivedWordGroupInfos.getD 6 + ⟨"guruswami-sudan-interp-noncodeword-large-koalabear", ""⟩) + gsNonCodewordLargeInputShape preset rows pure (group, gen) /-- Runnable received-word GS benchmark tasks. -/ @@ -386,20 +469,23 @@ def guruswamiSudanReceivedWordTasks : List BenchTask := [ ⟨"guruswami-sudan-interp-noncodeword-small-koalabear", ""⟩) runGsInterpolationNonCodewordSmallKoala, BenchTask.fromGroupRunner (guruswamiSudanReceivedWordGroupInfos.getD 1 - ⟨"guruswami-sudan-interp-noncodeword-large-koalabear", ""⟩) - runGsInterpolationNonCodewordLargeKoala, + ⟨"guruswami-sudan-interp-noncodeword-medium-koalabear", ""⟩) + runGsInterpolationNonCodewordMediumKoala, BenchTask.fromGroupRunner (guruswamiSudanReceivedWordGroupInfos.getD 2 ⟨"guruswami-sudan-core-noncodeword-small-koalabear", ""⟩) runGsCoreNonCodewordSmallKoala, BenchTask.fromGroupRunner (guruswamiSudanReceivedWordGroupInfos.getD 3 - ⟨"guruswami-sudan-core-noncodeword-large-koalabear", ""⟩) - runGsCoreNonCodewordLargeKoala, + ⟨"guruswami-sudan-core-noncodeword-medium-koalabear", ""⟩) + runGsCoreNonCodewordMediumKoala, BenchTask.fromGroupRunner (guruswamiSudanReceivedWordGroupInfos.getD 4 ⟨"guruswami-sudan-filtered-core-noncodeword-small-koalabear", ""⟩) runGsFilteredCoreNonCodewordSmallKoala, BenchTask.fromGroupRunner (guruswamiSudanReceivedWordGroupInfos.getD 5 - ⟨"guruswami-sudan-filtered-core-noncodeword-large-koalabear", ""⟩) - runGsFilteredCoreNonCodewordLargeKoala + ⟨"guruswami-sudan-filtered-core-noncodeword-medium-koalabear", ""⟩) + runGsFilteredCoreNonCodewordMediumKoala, + BenchTask.fromGroupRunner (guruswamiSudanReceivedWordGroupInfos.getD 6 + ⟨"guruswami-sudan-interp-noncodeword-large-koalabear", ""⟩) + runGsInterpolationNonCodewordLargeKoala ] end CompPolyBench diff --git a/bench/CompPolyBench/Bivariate/GuruswamiSudan/Shared.lean b/bench/CompPolyBench/Bivariate/GuruswamiSudan/Shared.lean index 8909f4b9..864369d2 100644 --- a/bench/CompPolyBench/Bivariate/GuruswamiSudan/Shared.lean +++ b/bench/CompPolyBench/Bivariate/GuruswamiSudan/Shared.lean @@ -9,6 +9,7 @@ import CompPoly.Bivariate.Deriv import CompPoly.Bivariate.GuruswamiSudan import CompPoly.Bivariate.GuruswamiSudan.Implementations import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Koetter.Algorithm +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.WitnessDivisibility import CompPoly.Bivariate.GuruswamiSudan.Root.FieldRoots.KoalaBear /-! @@ -35,16 +36,39 @@ def gsKoalaParams : GSInterpParams := multiplicity := gsMultiplicity weightedDegreeBound := gsWeightedDegreeBound } -def gsLargeInterpPointCount : Nat := 192 -def gsLargeInterpMessageDegree : Nat := 49 -def gsLargeInterpWeightedDegreeBound : Nat := - 5 * (gsLargeInterpMessageDegree - 1) +def gsMediumInterpPointCount : Nat := 192 +def gsMediumInterpMessageDegree : Nat := 49 +def gsMediumInterpMultiplicity : Nat := 2 +/-- Weighted-degree bound scaled with the multiplicity (`D/m = 2.5 * (k - 1)`), +so the agreement margin `m * t > D` is kept for both the codeword and the +`every7`-perturbed received words at any multiplicity. -/ +def gsMediumInterpWeightedDegreeBound : Nat := + 5 * gsMediumInterpMultiplicity * (gsMediumInterpMessageDegree - 1) / 2 +def gsMediumInterpParams : GSInterpParams := + { messageDegree := gsMediumInterpMessageDegree + multiplicity := gsMediumInterpMultiplicity + weightedDegreeBound := gsMediumInterpWeightedDegreeBound } + +/-- Long-code shape for the asymptotic interpolation comparison: at +`n ≈ 5000` the defect-driven Mulders-Storjohann reduction (quadratic in `n` +on error-bearing words) crosses over against the quasi-linear approximant +PM-basis solver, so the large group compares only those two backends. +Koetter grows like `~n^3.4` here (extrapolated ~13 h per call) and the dense +solver is far beyond that, so neither is benchmarked at this size. -/ +def gsLargeInterpPointCount : Nat := 5040 +def gsLargeInterpMessageDegree : Nat := 1261 def gsLargeInterpMultiplicity : Nat := 2 +def gsLargeInterpWeightedDegreeBound : Nat := + 5 * gsLargeInterpMultiplicity * (gsLargeInterpMessageDegree - 1) / 2 def gsLargeInterpParams : GSInterpParams := { messageDegree := gsLargeInterpMessageDegree multiplicity := gsLargeInterpMultiplicity weightedDegreeBound := gsLargeInterpWeightedDegreeBound } +def gsLargeInterpInputShape : String := + s!"n={gsLargeInterpPointCount},k={gsLargeInterpMessageDegree}," ++ + s!"m={gsLargeInterpMultiplicity},D={gsLargeInterpWeightedDegreeBound}" + def gsSmallPointCount : Nat := 64 def gsSmallMessageDegree : Nat := 16 def gsSmallWeightedDegreeBound : Nat := @@ -65,9 +89,9 @@ def gsSmallInputShape : String := def gsSmallInterpInputShape : String := gsSmallInputShape -def gsLargeInterpInputShape : String := - s!"n={gsLargeInterpPointCount},k={gsLargeInterpMessageDegree}," ++ - s!"m={gsLargeInterpMultiplicity},D={gsLargeInterpWeightedDegreeBound}" +def gsMediumInterpInputShape : String := + s!"n={gsMediumInterpPointCount},k={gsMediumInterpMessageDegree}," ++ + s!"m={gsMediumInterpMultiplicity},D={gsMediumInterpWeightedDegreeBound}" def gsMultiplicityShape : String := s!"n={gsPointCount},k={gsMessageDegree},m={gsCheckMultiplicity},Q=(Y-p)^2" @@ -76,7 +100,7 @@ def gsRootShape : String := s!"k={gsMessageDegree},Q=(Y-p)(Y-(p+7))" def gsFilteredShape : String := - gsLargeInterpInputShape ++ ",r=0" + gsMediumInterpInputShape ++ ",r=0" def gsSmallFilteredShape : String := gsSmallInterpInputShape ++ ",r=0" @@ -96,9 +120,9 @@ def gsSmallBenchmarkPoints {F : Type*} [Semiring F] [BEq F] [LawfulBEq F] (p : CPolynomial F) : Array (Prod F F) := codewordPointsWithCount gsSmallPointCount p -def gsLargeBenchmarkPoints {F : Type*} [Semiring F] [BEq F] [LawfulBEq F] +def gsMediumBenchmarkPoints {F : Type*} [Semiring F] [BEq F] [LawfulBEq F] (p : CPolynomial F) : Array (Prod F F) := - codewordPointsWithCount gsLargeInterpPointCount p + codewordPointsWithCount gsMediumInterpPointCount p def rootBenchmarkQ {F : Type*} [CommRing F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] @@ -179,10 +203,10 @@ def guruswamiSudanGroupInfos : List BenchGroupInfo := [ "Guruswami-Sudan dense interpolation solving, small (KoalaBear)"⟩, ⟨"guruswami-sudan-interp-small-koalabear", "Guruswami-Sudan interpolation, small (KoalaBear)"⟩, - ⟨"guruswami-sudan-interp-large-koalabear", - "Guruswami-Sudan interpolation, large (KoalaBear)"⟩, - ⟨"guruswami-sudan-lee-setup-large-koalabear", - "Guruswami-Sudan Lee-O'Sullivan setup, large (KoalaBear)"⟩, + ⟨"guruswami-sudan-interp-medium-koalabear", + "Guruswami-Sudan interpolation, medium (KoalaBear)"⟩, + ⟨"guruswami-sudan-lee-setup-medium-koalabear", + "Guruswami-Sudan Lee-O'Sullivan setup, medium (KoalaBear)"⟩, ⟨"guruswami-sudan-hasse-koalabear", "Guruswami-Sudan Hasse multiplicity checking (KoalaBear)"⟩, ⟨"guruswami-sudan-compose-koalabear", @@ -191,13 +215,13 @@ def guruswamiSudanGroupInfos : List BenchGroupInfo := [ "Guruswami-Sudan root finding (KoalaBear)"⟩, ⟨"guruswami-sudan-core-small-koalabear", "Guruswami-Sudan full core, small (KoalaBear)"⟩, - ⟨"guruswami-sudan-core-large-koalabear", - "Guruswami-Sudan full core, large (KoalaBear)"⟩, + ⟨"guruswami-sudan-core-medium-koalabear", + "Guruswami-Sudan full core, medium (KoalaBear)"⟩, ⟨"guruswami-sudan-packed-filter-koalabear", "Guruswami-Sudan packed distance filtering (KoalaBear)"⟩, ⟨"guruswami-sudan-filtered-core-small-koalabear", "Guruswami-Sudan filtered core, small (KoalaBear)"⟩, - ⟨"guruswami-sudan-filtered-core-large-koalabear", - "Guruswami-Sudan filtered core, large (KoalaBear)"⟩ + ⟨"guruswami-sudan-filtered-core-medium-koalabear", + "Guruswami-Sudan filtered core, medium (KoalaBear)"⟩ ] From e8c1e2e172ab9ef30b6144221bbc331998d1a8c4 Mon Sep 17 00:00:00 2001 From: Valerii Huhnin Date: Fri, 12 Jun 2026 11:30:24 +0000 Subject: [PATCH 07/10] Add hybrid Lee-approximant interpoaltion --- CompPoly.lean | 3 + .../GuruswamiSudan/Implementations.lean | 29 ++++ .../GuruswamiSudan/Interpolation.lean | 1 + .../GuruswamiSudan/Interpolation/Hybrid.lean | 12 ++ .../Interpolation/Hybrid/Algorithm.lean | 104 +++++++++++++ .../Interpolation/Hybrid/Correctness.lean | 143 ++++++++++++++++++ .../Reduction.lean | 37 +++++ .../Bivariate/GuruswamiSudan.lean | 25 ++- .../GuruswamiSudan/ReceivedWord.lean | 15 +- 9 files changed, 361 insertions(+), 8 deletions(-) create mode 100644 CompPoly/Bivariate/GuruswamiSudan/Interpolation/Hybrid.lean create mode 100644 CompPoly/Bivariate/GuruswamiSudan/Interpolation/Hybrid/Algorithm.lean create mode 100644 CompPoly/Bivariate/GuruswamiSudan/Interpolation/Hybrid/Correctness.lean diff --git a/CompPoly.lean b/CompPoly.lean index 57145e70..12f631ea 100644 --- a/CompPoly.lean +++ b/CompPoly.lean @@ -25,6 +25,9 @@ import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Basic import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Correctness import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Dense.Algorithm import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Dense.Correctness +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Hybrid +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Hybrid.Algorithm +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Hybrid.Correctness import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Koetter.Algorithm import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Koetter.Basic import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Koetter.Correctness diff --git a/CompPoly/Bivariate/GuruswamiSudan/Implementations.lean b/CompPoly/Bivariate/GuruswamiSudan/Implementations.lean index 1a2d196b..77621006 100644 --- a/CompPoly/Bivariate/GuruswamiSudan/Implementations.lean +++ b/CompPoly/Bivariate/GuruswamiSudan/Implementations.lean @@ -5,6 +5,7 @@ Authors: Valerii Huhnin -/ import CompPoly.Bivariate.GuruswamiSudan.Executable +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Hybrid.Correctness import CompPoly.Bivariate.GuruswamiSudan.Root.FieldRoots.KoalaBear import CompPoly.LinearAlgebra.PolynomialMatrix.MuldersStorjohannCorrectness.Fast import CompPoly.Univariate.BatchEval.Context @@ -193,6 +194,34 @@ def fastKoalaBearApproximantBasisInterpContext : GSInterpContext KoalaBear.Fast.Field := fastKoalaBearApproximantBasisSubproductInterpContext +/-- Mulders-Storjohann step budget for the hybrid interpolation backend — the +ski-rental `B` of `gs-interpolation-complexity-analysis.md`, Section 6: +proportional to `ℓ^(ω−1)` (with `ℓ + 1` the module width) and independent of +`n` and `m` under softly-linear multiplication. The constant is calibrated +from the `n = 5040` long-code benchmark shape, where the approximant fallback +costs ≈ 11.4 s against ≈ 0.65 ms per reduction step. -/ +def hybridReductionStepBudget (params : GSInterpParams) : Nat := + 500 * leeOSullivanWidth params * leeOSullivanWidth params + +/-- Hybrid interpolation (budgeted Lee-O'Sullivan reduction with approximant +fallback) over canonical KoalaBear. -/ +def koalaBearHybridInterpContext : GSInterpContext KoalaBear.Field := + Hybrid.hybridInterpContext + (CPolynomial.VanishingPolynomialContext.subproduct koalaBearNttFastMulContext) + koalaBearNttFastBatchEvalContext + koalaBearApproximantSolutionContext + hybridReductionStepBudget + +/-- Hybrid interpolation (budgeted Lee-O'Sullivan reduction with approximant +fallback) over native-word fast KoalaBear. -/ +def fastKoalaBearHybridInterpContext : GSInterpContext KoalaBear.Fast.Field := + Hybrid.hybridInterpContext + (CPolynomial.VanishingPolynomialContext.subproduct + fastKoalaBearNttFastMulContext) + fastKoalaBearNttFastBatchEvalContext + fastKoalaBearApproximantSolutionContext + hybridReductionStepBudget + /-- Roth-Ruckenstein root backend over canonical KoalaBear. -/ def koalaBearRothRootContext : GSRootContext KoalaBear.Field := rothRuckensteinRootContext KoalaBear.Field koalaBearFieldRootContext diff --git a/CompPoly/Bivariate/GuruswamiSudan/Interpolation.lean b/CompPoly/Bivariate/GuruswamiSudan/Interpolation.lean index 8b787fb0..3277456e 100644 --- a/CompPoly/Bivariate/GuruswamiSudan/Interpolation.lean +++ b/CompPoly/Bivariate/GuruswamiSudan/Interpolation.lean @@ -9,6 +9,7 @@ import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Dense.Correctness import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Koetter.Correctness import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Correctness import CompPoly.Bivariate.GuruswamiSudan.Interpolation.ApproximantBasis.Correctness +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Hybrid.Correctness /-! # Guruswami-Sudan Interpolation diff --git a/CompPoly/Bivariate/GuruswamiSudan/Interpolation/Hybrid.lean b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/Hybrid.lean new file mode 100644 index 00000000..f290fda1 --- /dev/null +++ b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/Hybrid.lean @@ -0,0 +1,12 @@ +/- +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.Interpolation.Hybrid.Algorithm +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Hybrid.Correctness + +/-! +# Hybrid Guruswami-Sudan Interpolation +-/ diff --git a/CompPoly/Bivariate/GuruswamiSudan/Interpolation/Hybrid/Algorithm.lean b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/Hybrid/Algorithm.lean new file mode 100644 index 00000000..6684100e --- /dev/null +++ b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/Hybrid/Algorithm.lean @@ -0,0 +1,104 @@ +/- +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.Interpolation.ApproximantBasis.Algorithm +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Algorithm + +/-! +# Hybrid Guruswami-Sudan Interpolation + +Output-sensitive interpolation backend combining the adaptive Lee-O'Sullivan +route with the order-driven approximant-basis route, following the ski-rental +analysis in `gs-interpolation-complexity-analysis.md`. + +The Mulders-Storjohann reduction of the Lee-O'Sullivan basis terminates after +a number of steps that vanishes on codewords and scales with the distance of +the received word from the code, while the approximant-basis backend costs +the same on every input. The hybrid runs the reduction with a calibrated step +budget `B` as fuel; if the budget is exhausted before the basis is +conflict-free, it falls back to the approximant solver, so the total cost is +within a constant factor of the cheaper route on every input. + +The budget is the entire fuel policy: the reduction loop stops by itself as +soon as the basis is conflict-free, so unused fuel is free and any cap below +`B` could only trigger premature fallback. (In particular the degree excess +`Δ` of `gs-interpolation-complexity-analysis.md` is *not* a usable cap: the +implementation's step count includes leading-position moves that leave the +shifted-degree sum unchanged, so reductions routinely need more than `Δ` +steps.) + +The dispatch affects performance only: both branches are complete verified +interpolators, and `Interpolation/Hybrid/Correctness.lean` shows the hybrid +result always coincides with one of them. +-/ + +namespace CompPoly + +namespace GuruswamiSudan + +namespace Hybrid + +open PolynomialMatrix +open PolynomialMatrix.Approximant + +variable {F : Type*} [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + +/-- Positive-`Y`-weight hybrid interpolation branch: reduce the +Lee-O'Sullivan basis with the budgeted fuel and keep the result when it +reached shifted weak Popov form (no leading conflict remains), otherwise fall +back to the approximant-basis solver. -/ +def hybridPositiveInterpolate + (V : CPolynomial.VanishingPolynomialContext F) + (E : CPolynomial.BatchEvalContext F) + (solver : ModularSolutionBasisContext F) + (budget : GSInterpParams → Nat) + (points : Array (F × F)) (params : GSInterpParams) : + Option (CBivariate F) := + if distinctXCoordinatesBool points then + let G := V.vanishingPolynomial (points.map fun point ↦ point.1) + let R := CPolynomial.interpolateCoefficientFormWithVanishing E G points + let basis := leeOSullivanBasisRowsWithRG R G params + let shift := leeOSullivanShifts params + let reduced := muldersStorjohannReduceWithFuelFast (budget params) basis shift + match cachedLeadingConflict? (rowLeadingPositions reduced shift) with + | some _ => + ApproximantBasis.approximantBasisPositiveInterpolate V E solver + points params + | none => + match LeeOSullivan.leastShiftedDegreeRow? reduced shift with + | none => none + | some row => + match rowShiftedDegree? row shift with + | none => none + | some degree => + if degree ≤ params.weightedDegreeBound then + let rawQ := CBivariate.ofCoeffRow row + match LeeOSullivan.normalizeLeeCandidate? params rawQ with + | none => none + | some Q => some Q + else + none + else + none + +/-- Hybrid interpolation with the shared low-message branch. -/ +def hybridInterpolate + (V : CPolynomial.VanishingPolynomialContext F) + (E : CPolynomial.BatchEvalContext F) + (solver : ModularSolutionBasisContext F) + (budget : GSInterpParams → Nat) + (points : Array (F × F)) (params : GSInterpParams) : + Option (CBivariate F) := + if params.messageDegree ≤ 1 then + some (lowMessageDegreeInterpolation points params.multiplicity) + else + hybridPositiveInterpolate V E solver budget points params + +end Hybrid + +end GuruswamiSudan + +end CompPoly diff --git a/CompPoly/Bivariate/GuruswamiSudan/Interpolation/Hybrid/Correctness.lean b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/Hybrid/Correctness.lean new file mode 100644 index 00000000..0a2ec274 --- /dev/null +++ b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/Hybrid/Correctness.lean @@ -0,0 +1,143 @@ +/- +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.Interpolation.ApproximantBasis.Correctness +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Hybrid.Algorithm +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Correctness +import CompPoly.LinearAlgebra.PolynomialMatrix.MuldersStorjohannCorrectness.Fast + +/-! +# Correctness of Hybrid Guruswami-Sudan Interpolation + +The hybrid's fuel/fallback dispatch affects performance only: its result +always coincides with the result of one of the two verified backends +(`hybridPositiveInterpolate_eq_lee_or_approximant`). Soundness and +completeness of `hybridInterpContext` follow by case analysis from the +corresponding backend theorems. + +The only reduction-level fact needed is determinism of the fueled +Mulders-Storjohann loop (`muldersStorjohannReduceWithFuel_eq_of_no_conflict`): +when the budgeted probe ends without a shifted leading conflict, it stopped at +the same matrix as the full reduction, so the kept Lee branch is literally the +Lee-O'Sullivan backend run with the fast reducer context. +-/ + +namespace CompPoly + +namespace GuruswamiSudan + +namespace Hybrid + +open PolynomialMatrix +open PolynomialMatrix.Approximant + +variable {F : Type*} [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] + +omit [Nontrivial F] in +/-- When the budgeted probe ends conflict-free it equals the full fast +reduction. -/ +private theorem hybrid_probe_eq_reduceFast + {fuel : Nat} {basis : PolynomialMatrix F} {shift : Array Nat} + (hWF : WellFormed basis) + (hconf : cachedLeadingConflict? + (rowLeadingPositions + (muldersStorjohannReduceWithFuelFast fuel basis shift) shift) = none) : + muldersStorjohannReduceWithFuelFast fuel basis shift = + muldersStorjohannReduceFast basis shift := by + rw [muldersStorjohannReduceWithFuelFast_eq, muldersStorjohannReduceFast_eq, + muldersStorjohannReduce] + apply muldersStorjohannReduceWithFuel_eq_of_no_conflict + · rw [cachedLeadingConflict?_eq, muldersStorjohannReduceWithFuelFast_eq] at hconf + exact hconf + · have hno := muldersStorjohannReduce_no_conflict basis shift hWF + rw [muldersStorjohannReduce] at hno + exact hno + +/-- The hybrid branch result always coincides with one of the two verified +backends. -/ +theorem hybridPositiveInterpolate_eq_lee_or_approximant + (V : CPolynomial.VanishingPolynomialContext F) + (E : CPolynomial.BatchEvalContext F) + (solver : ModularSolutionBasisContext F) + (budget : GSInterpParams → Nat) + (points : Array (F × F)) (params : GSInterpParams) : + hybridPositiveInterpolate V E solver budget points params = + LeeOSullivan.leeOSullivanPositiveInterpolate V E + (muldersStorjohannFastReducerContext F) points params ∨ + hybridPositiveInterpolate V E solver budget points params = + ApproximantBasis.approximantBasisPositiveInterpolate V E solver + points params := by + by_cases hdist : distinctXCoordinatesBool points + · simp only [hybridPositiveInterpolate, hdist, if_true] + split + case h_1 _ hconf => + right + rfl + case h_2 _ hconf => + left + rw [hybrid_probe_eq_reduceFast + (LeeOSullivan.leeOSullivanBasisRowsWithRG_wellFormed _ _ params) hconf] + simp only [LeeOSullivan.leeOSullivanPositiveInterpolate, hdist, if_true, + muldersStorjohannFastReducerContext] + rfl + · left + rw [hybridPositiveInterpolate, if_neg hdist, + LeeOSullivan.leeOSullivanPositiveInterpolate, if_neg hdist] + +/-- The hybrid result always coincides with one of the two verified +backends. -/ +theorem hybridInterpolate_eq_lee_or_approximant + (V : CPolynomial.VanishingPolynomialContext F) + (E : CPolynomial.BatchEvalContext F) + (solver : ModularSolutionBasisContext F) + (budget : GSInterpParams → Nat) + (points : Array (F × F)) (params : GSInterpParams) : + hybridInterpolate V E solver budget points params = + LeeOSullivan.leeOSullivanInterpolate V E + (muldersStorjohannFastReducerContext F) points params ∨ + hybridInterpolate V E solver budget points params = + ApproximantBasis.approximantBasisInterpolate V E solver points params := by + by_cases hlow : params.messageDegree ≤ 1 + · left + simp only [hybridInterpolate, LeeOSullivan.leeOSullivanInterpolate, hlow, + if_true] + · simp only [hybridInterpolate, LeeOSullivan.leeOSullivanInterpolate, + ApproximantBasis.approximantBasisInterpolate, hlow, if_false] + exact hybridPositiveInterpolate_eq_lee_or_approximant V E solver budget + points params + +/-- Public hybrid interpolation backend context: adaptive Lee-O'Sullivan +reduction under a step budget with an approximant-basis fallback. -/ +def hybridInterpContext + (V : CPolynomial.VanishingPolynomialContext F) + (E : CPolynomial.BatchEvalContext F) + (solver : ModularSolutionBasisContext F) + (budget : GSInterpParams → Nat) : GSInterpContext F where + interpolate := hybridInterpolate V E solver budget + sound := by + intro points params Q h + rcases hybridInterpolate_eq_lee_or_approximant V E solver budget points + params with heq | heq + · exact LeeOSullivan.leeOSullivanInterpolate_sound V E + (muldersStorjohannFastReducerContext F) (heq ▸ h) + · exact ApproximantBasis.approximantBasisInterpolate_sound V E solver + (heq ▸ h) + complete := by + intro points params hdistinct hexists + rcases hybridInterpolate_eq_lee_or_approximant V E solver budget points + params with heq | heq + · rw [heq] + exact LeeOSullivan.leeOSullivanInterpolate_complete V E + (muldersStorjohannFastReducerContext F) hdistinct hexists + · rw [heq] + exact ApproximantBasis.approximantBasisInterpolate_complete V E solver + points params hdistinct hexists + +end Hybrid + +end GuruswamiSudan + +end CompPoly diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix/MuldersStorjohannCorrectness/Reduction.lean b/CompPoly/LinearAlgebra/PolynomialMatrix/MuldersStorjohannCorrectness/Reduction.lean index dd88985f..bec0eb4e 100644 --- a/CompPoly/LinearAlgebra/PolynomialMatrix/MuldersStorjohannCorrectness/Reduction.lean +++ b/CompPoly/LinearAlgebra/PolynomialMatrix/MuldersStorjohannCorrectness/Reduction.lean @@ -286,6 +286,43 @@ theorem muldersStorjohannReduce_no_conflict (muldersStorjohannFuel M shift) M shift hM (shiftedMatrixMeasure_lt_muldersStorjohannFuel hM) +/-- The fueled reducer is deterministic: two runs that both end without a +shifted leading conflict stopped at the same (first conflict-free) matrix of +the common step orbit, regardless of their fuel. -/ +theorem muldersStorjohannReduceWithFuel_eq_of_no_conflict : + ∀ (f g : Nat) (M : PolynomialMatrix F) (shift : Array Nat), + shiftedLeadingConflict? (muldersStorjohannReduceWithFuel f M shift) shift = + none → + shiftedLeadingConflict? (muldersStorjohannReduceWithFuel g M shift) shift = + none → + muldersStorjohannReduceWithFuel f M shift = + muldersStorjohannReduceWithFuel g M shift := by + intro f + induction f with + | zero => + intro g M shift hf hg + simp only [muldersStorjohannReduceWithFuel] at hf + cases g with + | zero => rfl + | succ g => simp only [muldersStorjohannReduceWithFuel, hf] + | succ f ih => + intro g M shift hf hg + cases hconf : shiftedLeadingConflict? M shift with + | none => + cases g with + | zero => simp only [muldersStorjohannReduceWithFuel, hconf] + | succ g => simp only [muldersStorjohannReduceWithFuel, hconf] + | some pair => + rcases pair with ⟨i, j⟩ + simp only [muldersStorjohannReduceWithFuel, hconf] at hf ⊢ + cases g with + | zero => + simp only [muldersStorjohannReduceWithFuel, hconf] at hg + cases hg + | succ g => + simp only [muldersStorjohannReduceWithFuel, hconf] at hg ⊢ + exact ih g (muldersStorjohannStep M shift i j) shift hf hg + theorem muldersStorjohannReduceWithFuel_rowSpan_subset (fuel : Nat) (M : PolynomialMatrix F) (shift : Array Nat) (hM : WellFormed M) : RowSpan (muldersStorjohannReduceWithFuel fuel M shift) ⊆ RowSpan M := by diff --git a/bench/CompPolyBench/Bivariate/GuruswamiSudan.lean b/bench/CompPolyBench/Bivariate/GuruswamiSudan.lean index e0ee19e1..339c605b 100644 --- a/bench/CompPolyBench/Bivariate/GuruswamiSudan.lean +++ b/bench/CompPolyBench/Bivariate/GuruswamiSudan.lean @@ -226,10 +226,14 @@ private def runGsInterpolationMediumKoala (preset : BenchPreset) (gen : StdGen) let fastLeeMeasured := preset.selectNat 50 7 1 let fastApproximantDirectMeasured := preset.selectNat 7 1 1 let fastApproximantSubproductMeasured := preset.selectNat 7 1 1 + let hybridMeasured := preset.selectNat 5 1 1 + let fastHybridMeasured := preset.selectNat 25 4 1 let checksumIterations := groupChecksumIterations koetterMeasured [ leeMeasured, leeMeasured, approximantDirectMeasured, approximantSubproductMeasured, + hybridMeasured, fastKoetterMeasured, fastLeeMeasured, fastLeeMeasured, - fastApproximantDirectMeasured, fastApproximantSubproductMeasured + fastApproximantDirectMeasured, fastApproximantSubproductMeasured, + fastHybridMeasured ] let koetterRow <- runTimed "guruswami-sudan-interp-koetter" "CBivariate" @@ -268,6 +272,13 @@ private def runGsInterpolationMediumKoala (preset : BenchPreset) (gen : StdGen) gsMediumInterpParams) (checksumInterpolationValidityOption points gsMediumInterpParams) checksumIterations + let hybridRow <- runTimed + "guruswami-sudan-interp-hybrid" "CBivariate" + "Hybrid" + "KoalaBear.Field" gsMediumInterpInputShape preset warmup hybridMeasured + (fun _ ↦ koalaBearHybridInterpContext.interpolate points gsMediumInterpParams) + (checksumInterpolationValidityOption points gsMediumInterpParams) + checksumIterations let fastKoetterRow <- runTimed "guruswami-sudan-interp-koetter-fast" "CBivariate" "Koetter" @@ -309,14 +320,22 @@ private def runGsInterpolationMediumKoala (preset : BenchPreset) (gen : StdGen) gsMediumInterpParams) (checksumInterpolationValidityOption fastPoints gsMediumInterpParams) checksumIterations + let fastHybridRow <- runTimed + "guruswami-sudan-interp-hybrid-fast" "CBivariate" + "Hybrid" + "KoalaBear.Fast.Field" gsMediumInterpInputShape preset warmup fastHybridMeasured + (fun _ ↦ fastKoalaBearHybridInterpContext.interpolate fastPoints + gsMediumInterpParams) + (checksumInterpolationValidityOption fastPoints gsMediumInterpParams) + checksumIterations pure ({ groupKey := "guruswami-sudan-interp-medium-koalabear", title := "Guruswami-Sudan interpolation, medium (KoalaBear)", records := #[ koetterRow, leeDirectRow, leeSubproductRow, - approximantDirectRow, approximantSubproductRow, + approximantDirectRow, approximantSubproductRow, hybridRow, fastKoetterRow, fastLeeDirectRow, fastLeeSubproductRow, - fastApproximantDirectRow, fastApproximantSubproductRow + fastApproximantDirectRow, fastApproximantSubproductRow, fastHybridRow ] }, gen) diff --git a/bench/CompPolyBench/Bivariate/GuruswamiSudan/ReceivedWord.lean b/bench/CompPolyBench/Bivariate/GuruswamiSudan/ReceivedWord.lean index 800b55c6..6c6a729f 100644 --- a/bench/CompPolyBench/Bivariate/GuruswamiSudan/ReceivedWord.lean +++ b/bench/CompPolyBench/Bivariate/GuruswamiSudan/ReceivedWord.lean @@ -215,7 +215,8 @@ private def gsNonCodewordSmallSlowBackends : ⟨"approximant-direct", "Approximant basis direct", koalaBearApproximantBasisDirectInterpContext, 2, 1, 1⟩, ⟨"approximant-subproduct", "Approximant basis subproduct", - koalaBearApproximantBasisSubproductInterpContext, 2, 1, 1⟩ + koalaBearApproximantBasisSubproductInterpContext, 2, 1, 1⟩, + ⟨"hybrid", "Hybrid", koalaBearHybridInterpContext, 8, 1, 1⟩ ] private def gsNonCodewordSmallFastBackends : @@ -229,7 +230,8 @@ private def gsNonCodewordSmallFastBackends : ⟨"approximant-direct", "Approximant basis direct", fastKoalaBearApproximantBasisDirectInterpContext, 6, 1, 1⟩, ⟨"approximant-subproduct", "Approximant basis subproduct", - fastKoalaBearApproximantBasisSubproductInterpContext, 6, 1, 1⟩ + fastKoalaBearApproximantBasisSubproductInterpContext, 6, 1, 1⟩, + ⟨"hybrid", "Hybrid", fastKoalaBearHybridInterpContext, 35, 5, 1⟩ ] private def gsNonCodewordMediumSlowBackends : @@ -242,7 +244,8 @@ private def gsNonCodewordMediumSlowBackends : ⟨"approximant-direct", "Approximant basis direct", koalaBearApproximantBasisDirectInterpContext, 1, 1, 1⟩, ⟨"approximant-subproduct", "Approximant basis subproduct", - koalaBearApproximantBasisSubproductInterpContext, 1, 1, 1⟩ + koalaBearApproximantBasisSubproductInterpContext, 1, 1, 1⟩, + ⟨"hybrid", "Hybrid", koalaBearHybridInterpContext, 1, 1, 1⟩ ] private def gsNonCodewordMediumFastBackends : @@ -255,7 +258,8 @@ private def gsNonCodewordMediumFastBackends : ⟨"approximant-direct", "Approximant basis direct", fastKoalaBearApproximantBasisDirectInterpContext, 1, 1, 1⟩, ⟨"approximant-subproduct", "Approximant basis subproduct", - fastKoalaBearApproximantBasisSubproductInterpContext, 1, 1, 1⟩ + fastKoalaBearApproximantBasisSubproductInterpContext, 1, 1, 1⟩, + ⟨"hybrid", "Hybrid", fastKoalaBearHybridInterpContext, 5, 1, 1⟩ ] /-- Long-code interpolation backends: only the two reduction-based backends @@ -272,7 +276,8 @@ private def gsNonCodewordLargeFastBackends : ⟨"lee-subproduct", "Lee-O'Sullivan subproduct", fastKoalaBearLeeSubproductInterpContext, 2, 1, 1⟩, ⟨"approximant-subproduct", "Approximant basis subproduct", - fastKoalaBearApproximantBasisSubproductInterpContext, 4, 2, 1⟩ + fastKoalaBearApproximantBasisSubproductInterpContext, 4, 2, 1⟩, + ⟨"hybrid", "Hybrid", fastKoalaBearHybridInterpContext, 2, 1, 1⟩ ] /-! ### Group metadata -/ From ea895708eac79ccdab930eb59e09ed333d8e38c2 Mon Sep 17 00:00:00 2001 From: Valerii Huhnin Date: Fri, 12 Jun 2026 12:07:46 +0000 Subject: [PATCH 08/10] Adapt rebased branch to master base MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Drop Koetter backend dependencies (Koetter is not on master yet; it lives on the gs-koetter branch): remove Koetter interpolation/core benchmark rows and the Koetter interp/filtered-core contexts, and switch WitnessDivisibilityCorrectness to the ofYConstant lemmas from the Lee-O'Sullivan Combinations module. - Align Implementations imports with master's reorganized root-search layers (RothRuckenstein/Alekhnovich correctness modules). - Regenerate CompPoly.lean for the post-rebase file set. - Mathlib bump fixes: finset_sum_coeff/eval_finset_sum renames, and in me_verification_dominates replace the rw-then-exact step for hquotExact with a single rw chain — under the new mathlib the exact's unifier fell into whnf-unfolding CPolynomial.toPoly's executable fold through Polynomial.instMul (32 s for one step); the rewrite matches syntactically and the proof is back under default maxHeartbeats. --- CompPoly.lean | 71 +------ .../GuruswamiSudan/Implementations.lean | 31 +-- .../GuruswamiSudan/Interpolation.lean | 1 - .../Correctness/ModularData.lean | 4 +- .../WitnessDivisibilityCorrectness.lean | 2 +- .../ModularEquation/Completeness.lean | 3 +- .../PMBasis/KernelLeafCompleteness.lean | 2 +- .../PMBasis/KernelLeafSoundness.lean | 4 +- .../Bivariate/GuruswamiSudan.lean | 52 +---- .../Bivariate/GuruswamiSudan/Core.lean | 201 +++--------------- .../GuruswamiSudan/ReceivedWord.lean | 4 - .../Bivariate/GuruswamiSudan/Shared.lean | 3 +- 12 files changed, 56 insertions(+), 322 deletions(-) diff --git a/CompPoly.lean b/CompPoly.lean index 12f631ea..f9062220 100644 --- a/CompPoly.lean +++ b/CompPoly.lean @@ -5,14 +5,12 @@ import CompPoly.Bivariate.Deriv import CompPoly.Bivariate.Factor import CompPoly.Bivariate.FactorMonic import CompPoly.Bivariate.GuruswamiSudan -import CompPoly.Bivariate.GuruswamiSudan.Compose import CompPoly.Bivariate.GuruswamiSudan.Context import CompPoly.Bivariate.GuruswamiSudan.Core import CompPoly.Bivariate.GuruswamiSudan.CoreCorrectness import CompPoly.Bivariate.GuruswamiSudan.Executable import CompPoly.Bivariate.GuruswamiSudan.Filter import CompPoly.Bivariate.GuruswamiSudan.FilterCorrectness -import CompPoly.Bivariate.GuruswamiSudan.Hasse import CompPoly.Bivariate.GuruswamiSudan.Implementations import CompPoly.Bivariate.GuruswamiSudan.Interpolation import CompPoly.Bivariate.GuruswamiSudan.Interpolation.ApproximantBasis @@ -28,21 +26,12 @@ import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Dense.Correctness import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Hybrid import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Hybrid.Algorithm import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Hybrid.Correctness -import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Koetter.Algorithm -import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Koetter.Basic -import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Koetter.Correctness -import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Koetter.Correctness.Algebra -import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Koetter.Correctness.Combinations -import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Koetter.Correctness.Completeness -import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Koetter.Correctness.Selection -import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Koetter.Correctness.Soundness -import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Koetter.Correctness.Transport -import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Koetter.Correctness.Update import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Algorithm import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Basic import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Correctness import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Correctness.Basis +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Correctness.Combinations import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Correctness.Common import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Correctness.Completeness import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Correctness.Divisibility @@ -52,18 +41,14 @@ import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Correctness. import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Correctness.Soundness import CompPoly.Bivariate.GuruswamiSudan.Interpolation.WitnessDivisibility import CompPoly.Bivariate.GuruswamiSudan.Interpolation.WitnessDivisibilityCorrectness -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.Alekhnovich.Algorithm import CompPoly.Bivariate.GuruswamiSudan.Root.Alekhnovich.Correctness import CompPoly.Bivariate.GuruswamiSudan.Root.Alekhnovich.Lemmas -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.Binary import CompPoly.Bivariate.GuruswamiSudan.Root.FieldRoots.FiniteField import CompPoly.Bivariate.GuruswamiSudan.Root.FieldRoots.KoalaBear import CompPoly.Bivariate.GuruswamiSudan.Root.RothRuckenstein.Algorithm @@ -72,6 +57,7 @@ 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.Kronecker import CompPoly.Bivariate.ToPoly import CompPoly.Data.Array.Lemmas import CompPoly.Data.Classes.DCast @@ -106,47 +92,6 @@ import CompPoly.Fields.Binary.BF128Ghash.Prelude import CompPoly.Fields.Binary.BF128Ghash.XPowTwoPowGcdCertificate import CompPoly.Fields.Binary.BF128Ghash.XPowTwoPowModCertificate import CompPoly.Fields.Binary.Common -import CompPoly.Fields.Binary.Extension.Basic -import CompPoly.Fields.Binary.Extension.Enumeration -import CompPoly.Fields.Binary.Extension.Impl -import CompPoly.Fields.Binary.Extension.Prelude -import CompPoly.Fields.Binary.Extension.Primitive -import CompPoly.Fields.Binary.GF2_32 -import CompPoly.Fields.Binary.GF2_32.Basic -import CompPoly.Fields.Binary.GF2_32.Impl -import CompPoly.Fields.Binary.GF2_32.Prelude -import CompPoly.Fields.Binary.GF2_32.Primitive -import CompPoly.Fields.Binary.GF2_32.PrimitivePowerCertificate -import CompPoly.Fields.Binary.GF2_32.RootContexts -import CompPoly.Fields.Binary.GF2_32.XPowTwoPowGcdCertificate -import CompPoly.Fields.Binary.GF2_32.XPowTwoPowModCertificate -import CompPoly.Fields.Binary.GF2_48 -import CompPoly.Fields.Binary.GF2_48.Basic -import CompPoly.Fields.Binary.GF2_48.Impl -import CompPoly.Fields.Binary.GF2_48.Prelude -import CompPoly.Fields.Binary.GF2_48.Primitive -import CompPoly.Fields.Binary.GF2_48.PrimitivePowerCertificate -import CompPoly.Fields.Binary.GF2_48.RootContexts -import CompPoly.Fields.Binary.GF2_48.XPowTwoPowGcdCertificate -import CompPoly.Fields.Binary.GF2_48.XPowTwoPowModCertificate -import CompPoly.Fields.Binary.GF2_64 -import CompPoly.Fields.Binary.GF2_64.Basic -import CompPoly.Fields.Binary.GF2_64.Impl -import CompPoly.Fields.Binary.GF2_64.Prelude -import CompPoly.Fields.Binary.GF2_64.Primitive -import CompPoly.Fields.Binary.GF2_64.PrimitivePowerCertificate -import CompPoly.Fields.Binary.GF2_64.RootContexts -import CompPoly.Fields.Binary.GF2_64.XPowTwoPowGcdCertificate -import CompPoly.Fields.Binary.GF2_64.XPowTwoPowModCertificate -import CompPoly.Fields.Binary.GF2_72 -import CompPoly.Fields.Binary.GF2_72.Basic -import CompPoly.Fields.Binary.GF2_72.Impl -import CompPoly.Fields.Binary.GF2_72.Prelude -import CompPoly.Fields.Binary.GF2_72.Primitive -import CompPoly.Fields.Binary.GF2_72.PrimitivePowerCertificate -import CompPoly.Fields.Binary.GF2_72.RootContexts -import CompPoly.Fields.Binary.GF2_72.XPowTwoPowGcdCertificate -import CompPoly.Fields.Binary.GF2_72.XPowTwoPowModCertificate import CompPoly.Fields.Binary.Tower.Abstract.Algebra import CompPoly.Fields.Binary.Tower.Abstract.Basis import CompPoly.Fields.Binary.Tower.Abstract.Core @@ -304,19 +249,7 @@ import CompPoly.Univariate.Roots.Context import CompPoly.Univariate.Roots.Correctness import CompPoly.Univariate.Roots.Enumeration import CompPoly.Univariate.Roots.Extraction -import CompPoly.Univariate.Roots.LasVegas -import CompPoly.Univariate.Roots.LasVegas.Basic -import CompPoly.Univariate.Roots.LasVegas.Correctness -import CompPoly.Univariate.Roots.LasVegas.Correctness.Common -import CompPoly.Univariate.Roots.LasVegas.Correctness.EvenTrace -import CompPoly.Univariate.Roots.LasVegas.Correctness.Loop -import CompPoly.Univariate.Roots.LasVegas.Correctness.Odd -import CompPoly.Univariate.Roots.LasVegas.Probability import CompPoly.Univariate.Roots.RootProduct -import CompPoly.Univariate.Roots.Shoup -import CompPoly.Univariate.Roots.Shoup.Basic -import CompPoly.Univariate.Roots.Shoup.Correctness -import CompPoly.Univariate.Roots.Shoup.FrobeniusLinear import CompPoly.Univariate.Roots.SmoothSubgroup import CompPoly.Univariate.Roots.SmoothSubgroup.Basic import CompPoly.Univariate.Roots.SmoothSubgroup.Correctness diff --git a/CompPoly/Bivariate/GuruswamiSudan/Implementations.lean b/CompPoly/Bivariate/GuruswamiSudan/Implementations.lean index 77621006..6f7b8d99 100644 --- a/CompPoly/Bivariate/GuruswamiSudan/Implementations.lean +++ b/CompPoly/Bivariate/GuruswamiSudan/Implementations.lean @@ -5,7 +5,11 @@ Authors: Valerii Huhnin -/ import CompPoly.Bivariate.GuruswamiSudan.Executable +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Dense.Correctness import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Hybrid.Correctness +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Correctness +import CompPoly.Bivariate.GuruswamiSudan.Root.Alekhnovich.Correctness +import CompPoly.Bivariate.GuruswamiSudan.Root.RothRuckenstein.Correctness import CompPoly.Bivariate.GuruswamiSudan.Root.FieldRoots.KoalaBear import CompPoly.LinearAlgebra.PolynomialMatrix.MuldersStorjohannCorrectness.Fast import CompPoly.Univariate.BatchEval.Context @@ -31,14 +35,6 @@ def koalaBearDenseInterpContext : GSInterpContext KoalaBear.Field := def fastKoalaBearDenseInterpContext : GSInterpContext KoalaBear.Fast.Field := denseInterpContext KoalaBear.Fast.Field -/-- Direct Koetter interpolation backend over canonical KoalaBear. -/ -def koalaBearKoetterInterpContext : GSInterpContext KoalaBear.Field := - koetterInterpContext KoalaBear.Field - -/-- Direct Koetter interpolation backend over native-word fast KoalaBear. -/ -def fastKoalaBearKoetterInterpContext : GSInterpContext KoalaBear.Fast.Field := - koetterInterpContext KoalaBear.Fast.Field - /-- NTTFast-backed univariate multiplication over canonical KoalaBear. -/ def koalaBearNttFastMulContext : CPolynomial.MulContext KoalaBear.Field := CPolynomial.MulContext.nttFast CPolynomial.NTT.KoalaBear.bestDomainForLength? @@ -273,25 +269,6 @@ def fastKoalaBearDenseRothNttFastContext : GSFilteredCoreContext KoalaBear.Fast. filteredCoreContextOfInterpRootContexts fastKoalaBearDenseInterpContext fastKoalaBearRothNttFastRootContext -/-- Filtered Koetter/Roth context over canonical KoalaBear. -/ -def koalaBearKoetterRothContext : GSFilteredCoreContext KoalaBear.Field := - filteredCoreContextOfInterpRootContexts koalaBearKoetterInterpContext koalaBearRothRootContext - -/-- Filtered Koetter/Roth context over canonical KoalaBear with NTTFast field roots. -/ -def koalaBearKoetterRothNttFastContext : GSFilteredCoreContext KoalaBear.Field := - filteredCoreContextOfInterpRootContexts koalaBearKoetterInterpContext - koalaBearRothNttFastRootContext - -/-- Filtered Koetter/Roth context over native-word fast KoalaBear. -/ -def fastKoalaBearKoetterRothContext : GSFilteredCoreContext KoalaBear.Fast.Field := - filteredCoreContextOfInterpRootContexts fastKoalaBearKoetterInterpContext - fastKoalaBearRothRootContext - -/-- Filtered Koetter/Roth context over native-word fast KoalaBear with NTTFast field roots. -/ -def fastKoalaBearKoetterRothNttFastContext : GSFilteredCoreContext KoalaBear.Fast.Field := - filteredCoreContextOfInterpRootContexts fastKoalaBearKoetterInterpContext - fastKoalaBearRothNttFastRootContext - /-- Concrete soundness for the canonical KoalaBear dense/Roth core. -/ theorem koalaBearDenseRothGsCore_sound {points : Array (KoalaBear.Field × KoalaBear.Field)} {params : GSInterpParams} {p : CPolynomial KoalaBear.Field} diff --git a/CompPoly/Bivariate/GuruswamiSudan/Interpolation.lean b/CompPoly/Bivariate/GuruswamiSudan/Interpolation.lean index 3277456e..cb546867 100644 --- a/CompPoly/Bivariate/GuruswamiSudan/Interpolation.lean +++ b/CompPoly/Bivariate/GuruswamiSudan/Interpolation.lean @@ -6,7 +6,6 @@ Authors: Valerii Huhnin import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Correctness import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Dense.Correctness -import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Koetter.Correctness import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Correctness import CompPoly.Bivariate.GuruswamiSudan.Interpolation.ApproximantBasis.Correctness import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Hybrid.Correctness diff --git a/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Correctness/ModularData.lean b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Correctness/ModularData.lean index f6f16751..51671dae 100644 --- a/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Correctness/ModularData.lean +++ b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Correctness/ModularData.lean @@ -494,7 +494,7 @@ private theorem toPoly_ofCoeffRow_eq_sum (row : PolynomialRow F) : ∑ k ∈ Finset.range row.size, Polynomial.monomial k ((row.getD k 0).toPoly) := by ext n - rw [CBivariate.toPoly_coeff, Polynomial.finset_sum_coeff] + rw [CBivariate.toPoly_coeff, Polynomial.finsetSum_coeff] by_cases hn : n < row.size · rw [Finset.sum_eq_single n (fun k _hk hkn ↦ by rw [Polynomial.coeff_monomial, if_neg hkn]) @@ -517,7 +517,7 @@ theorem hasseDeriv_toPoly_ofCoeffRow_eval (row : PolynomialRow F) (Polynomial.hasseDeriv b (CBivariate.ofCoeffRow row).toPoly).eval R = ∑ k ∈ Finset.range row.size, Polynomial.C ((k.choose b : F)) * (row.getD k 0).toPoly * R ^ (k - b) := by - rw [toPoly_ofCoeffRow_eq_sum, map_sum, Polynomial.eval_finset_sum] + rw [toPoly_ofCoeffRow_eq_sum, map_sum, Polynomial.eval_finsetSum] refine Finset.sum_congr rfl fun k _hk ↦ ?_ rw [Polynomial.hasseDeriv_monomial, Polynomial.eval_monomial, Polynomial.C_eq_natCast] diff --git a/CompPoly/Bivariate/GuruswamiSudan/Interpolation/WitnessDivisibilityCorrectness.lean b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/WitnessDivisibilityCorrectness.lean index 436f7460..8612a608 100644 --- a/CompPoly/Bivariate/GuruswamiSudan/Interpolation/WitnessDivisibilityCorrectness.lean +++ b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/WitnessDivisibilityCorrectness.lean @@ -5,7 +5,7 @@ Authors: Valerii Huhnin -/ import CompPoly.Bivariate.GuruswamiSudan.Interpolation.WitnessDivisibility -import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Koetter.Correctness.Combinations +import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Correctness.Combinations import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Correctness.Basis import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Correctness.Divisibility diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation/Completeness.lean b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation/Completeness.lean index 349121bf..0bdbcc2e 100644 --- a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation/Completeness.lean +++ b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation/Completeness.lean @@ -778,8 +778,7 @@ theorem me_verification_dominates have hquotExact : ∀ {b : Nat}, b < mW → (quot b).toPoly * (equation.moduli.getD b 0).toPoly = (prodE b).toPoly := by intro b hb - rw [hquot b] - exact me_divByMonic_mul_eq (hmonic b hb) (hdvdRed hb) + rw [hquot b, me_divByMonic_mul_eq (hmonic b hb) (hdvdRed hb)] -- Degree bounds on the witness entries and quotients. have hrowStarEntry : ∀ {k : Nat}, rowGet rowStar k ≠ 0 → (rowGet rowStar k).toPoly.natDegree + shift.getD k 0 ≤ e := by diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/KernelLeafCompleteness.lean b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/KernelLeafCompleteness.lean index 047c58d6..6a21fa47 100644 --- a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/KernelLeafCompleteness.lean +++ b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/KernelLeafCompleteness.lean @@ -71,7 +71,7 @@ private theorem pm_coeff_C_mul (c : F) (p : CPolynomial F) (t : Nat) : private theorem pm_coeff_finset_sum (f : Nat → CPolynomial F) (m t : Nat) : CPolynomial.coeff (∑ i ∈ Finset.range m, f i) t = ∑ i ∈ Finset.range m, CPolynomial.coeff (f i) t := by - rw [CPolynomial.coeff_toPoly, pm_toPoly_finset_sum, Polynomial.finset_sum_coeff] + rw [CPolynomial.coeff_toPoly, pm_toPoly_finset_sum, Polynomial.finsetSum_coeff] exact Finset.sum_congr rfl fun i _hi ↦ (CPolynomial.coeff_toPoly (f i) t).symm /-- Coefficients of a product with the monomial `X^d`. -/ diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/KernelLeafSoundness.lean b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/KernelLeafSoundness.lean index 8ebf9c6b..14b90a0e 100644 --- a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/KernelLeafSoundness.lean +++ b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/KernelLeafSoundness.lean @@ -321,7 +321,7 @@ theorem vectorToPolynomialRow_approximates (mulCtx : CPolynomial.MulContext F) rw [coefficientMatrixRows, Array.toList_map] exact List.mem_map.mpr ⟨(j, t), mem_coefficientEquationIndices hj ht, rfl⟩ have hdot := homogeneousKernelBasisRows_dot_eq_zero hv hr - rw [Polynomial.finset_sum_coeff] + rw [Polynomial.finsetSum_coeff] refine Eq.trans ?_ hdot rw [pm_sum_range_mul] refine Finset.sum_congr rfl fun k hk ↦ ?_ @@ -551,7 +551,7 @@ theorem coefficientMatrixRows_dot_eq_zero_of_approximates rw [hentry k hk a ha1, if_neg (by omega), zero_mul] rw [pm_sum_range_mul, Finset.sum_congr rfl fun k hk ↦ hinner k (Finset.mem_range.mp hk), - ← Polynomial.finset_sum_coeff] + ← Polynomial.finsetSum_coeff] exact hcoeff /-- Every kernel-leaf basis row approximates the problem and has the principal diff --git a/bench/CompPolyBench/Bivariate/GuruswamiSudan.lean b/bench/CompPolyBench/Bivariate/GuruswamiSudan.lean index 339c605b..3cc1dd9a 100644 --- a/bench/CompPolyBench/Bivariate/GuruswamiSudan.lean +++ b/bench/CompPolyBench/Bivariate/GuruswamiSudan.lean @@ -11,7 +11,7 @@ import CompPolyBench.Bivariate.GuruswamiSudan.ReceivedWord # Guruswami-Sudan Benchmarks KoalaBear cost-center benchmarks for the dense interpolation path, -Koetter interpolation path, Roth-Ruckenstein and Alekhnovich root finding, and +Roth-Ruckenstein and Alekhnovich root finding, and full backend-parametric `gsCore` and `gsFilteredCore`. -/ @@ -90,20 +90,18 @@ private def runGsInterpolationSmallKoala (preset : BenchPreset) (gen : StdGen) : let fastPoints := gsSmallBenchmarkPoints fastMessage let warmup := gsWarmupIterations preset let denseMeasured := preset.selectNat 1 1 1 - let koetterMeasured := preset.selectNat 10 2 1 let leeDirectMeasured := preset.selectNat 100 15 3 let leeSubproductMeasured := preset.selectNat 90 13 3 let approximantDirectMeasured := preset.selectNat 7 1 1 let approximantSubproductMeasured := preset.selectNat 7 1 1 let fastDenseMeasured := preset.selectNat 2 1 1 - let fastKoetterMeasured := preset.selectNat 70 10 2 let fastLeeDirectMeasured := preset.selectNat 600 90 20 let fastLeeSubproductMeasured := preset.selectNat 400 60 10 let fastApproximantDirectMeasured := preset.selectNat 20 3 1 let fastApproximantSubproductMeasured := preset.selectNat 20 3 1 let checksumIterations := groupChecksumIterations denseMeasured [ - koetterMeasured, leeDirectMeasured, leeSubproductMeasured, fastDenseMeasured, - approximantDirectMeasured, approximantSubproductMeasured, fastKoetterMeasured, + leeDirectMeasured, leeSubproductMeasured, fastDenseMeasured, + approximantDirectMeasured, approximantSubproductMeasured, fastLeeDirectMeasured, fastLeeSubproductMeasured, fastApproximantDirectMeasured, fastApproximantSubproductMeasured ] @@ -114,13 +112,6 @@ private def runGsInterpolationSmallKoala (preset : BenchPreset) (gen : StdGen) : (fun _ ↦ denseInterpolate points gsSmallParams) (checksumInterpolationValidityOption points gsSmallParams) checksumIterations - let koetterRow <- runTimed - "guruswami-sudan-interp-koetter-small" "CBivariate" - "Koetter" - "KoalaBear.Field" gsSmallInterpInputShape preset warmup koetterMeasured - (fun _ ↦ koetterInterpolate points gsSmallParams) - (checksumInterpolationValidityOption points gsSmallParams) - checksumIterations let leeDirectRow <- runTimed "guruswami-sudan-interp-lee-direct-small" "CBivariate" "Lee-O'Sullivan direct" @@ -158,13 +149,6 @@ private def runGsInterpolationSmallKoala (preset : BenchPreset) (gen : StdGen) : (fun _ ↦ denseInterpolate fastPoints gsSmallParams) (checksumInterpolationValidityOption fastPoints gsSmallParams) checksumIterations - let fastKoetterRow <- runTimed - "guruswami-sudan-interp-koetter-small-fast" "CBivariate" - "Koetter" - "KoalaBear.Fast.Field" gsSmallInterpInputShape preset warmup fastKoetterMeasured - (fun _ ↦ koetterInterpolate fastPoints gsSmallParams) - (checksumInterpolationValidityOption fastPoints gsSmallParams) - checksumIterations let fastLeeDirectRow <- runTimed "guruswami-sudan-interp-lee-direct-small-fast" "CBivariate" "Lee-O'Sullivan direct" @@ -203,9 +187,9 @@ private def runGsInterpolationSmallKoala (preset : BenchPreset) (gen : StdGen) : groupKey := "guruswami-sudan-interp-small-koalabear", title := "Guruswami-Sudan interpolation, small (KoalaBear)", records := #[ - denseRow, koetterRow, leeDirectRow, leeSubproductRow, + denseRow, leeDirectRow, leeSubproductRow, approximantDirectRow, approximantSubproductRow, - fastDenseRow, fastKoetterRow, fastLeeDirectRow, fastLeeSubproductRow, + fastDenseRow, fastLeeDirectRow, fastLeeSubproductRow, fastApproximantDirectRow, fastApproximantSubproductRow ] }, gen) @@ -218,30 +202,21 @@ private def runGsInterpolationMediumKoala (preset : BenchPreset) (gen : StdGen) let points := gsMediumBenchmarkPoints message let fastPoints := gsMediumBenchmarkPoints fastMessage let warmup := gsWarmupIterations preset - let koetterMeasured := preset.selectNat 1 1 1 let leeMeasured := preset.selectNat 10 1 1 let approximantDirectMeasured := preset.selectNat 2 1 1 let approximantSubproductMeasured := preset.selectNat 2 1 1 - let fastKoetterMeasured := preset.selectNat 4 1 1 let fastLeeMeasured := preset.selectNat 50 7 1 let fastApproximantDirectMeasured := preset.selectNat 7 1 1 let fastApproximantSubproductMeasured := preset.selectNat 7 1 1 let hybridMeasured := preset.selectNat 5 1 1 let fastHybridMeasured := preset.selectNat 25 4 1 - let checksumIterations := groupChecksumIterations koetterMeasured [ - leeMeasured, leeMeasured, approximantDirectMeasured, approximantSubproductMeasured, + let checksumIterations := groupChecksumIterations leeMeasured [ + leeMeasured, approximantDirectMeasured, approximantSubproductMeasured, hybridMeasured, - fastKoetterMeasured, fastLeeMeasured, fastLeeMeasured, + fastLeeMeasured, fastLeeMeasured, fastApproximantDirectMeasured, fastApproximantSubproductMeasured, fastHybridMeasured ] - let koetterRow <- runTimed - "guruswami-sudan-interp-koetter" "CBivariate" - "Koetter" - "KoalaBear.Field" gsMediumInterpInputShape preset warmup koetterMeasured - (fun _ ↦ koetterInterpolate points gsMediumInterpParams) - (checksumInterpolationValidityOption points gsMediumInterpParams) - checksumIterations let leeDirectRow <- runTimed "guruswami-sudan-interp-lee-direct" "CBivariate" "Lee-O'Sullivan direct" @@ -279,13 +254,6 @@ private def runGsInterpolationMediumKoala (preset : BenchPreset) (gen : StdGen) (fun _ ↦ koalaBearHybridInterpContext.interpolate points gsMediumInterpParams) (checksumInterpolationValidityOption points gsMediumInterpParams) checksumIterations - let fastKoetterRow <- runTimed - "guruswami-sudan-interp-koetter-fast" "CBivariate" - "Koetter" - "KoalaBear.Fast.Field" gsMediumInterpInputShape preset warmup fastKoetterMeasured - (fun _ ↦ koetterInterpolate fastPoints gsMediumInterpParams) - (checksumInterpolationValidityOption fastPoints gsMediumInterpParams) - checksumIterations let fastLeeDirectRow <- runTimed "guruswami-sudan-interp-lee-direct-fast" "CBivariate" "Lee-O'Sullivan direct" @@ -332,9 +300,9 @@ private def runGsInterpolationMediumKoala (preset : BenchPreset) (gen : StdGen) groupKey := "guruswami-sudan-interp-medium-koalabear", title := "Guruswami-Sudan interpolation, medium (KoalaBear)", records := #[ - koetterRow, leeDirectRow, leeSubproductRow, + leeDirectRow, leeSubproductRow, approximantDirectRow, approximantSubproductRow, hybridRow, - fastKoetterRow, fastLeeDirectRow, fastLeeSubproductRow, + fastLeeDirectRow, fastLeeSubproductRow, fastApproximantDirectRow, fastApproximantSubproductRow, fastHybridRow ] }, gen) diff --git a/bench/CompPolyBench/Bivariate/GuruswamiSudan/Core.lean b/bench/CompPolyBench/Bivariate/GuruswamiSudan/Core.lean index a4d36e75..7022b899 100644 --- a/bench/CompPolyBench/Bivariate/GuruswamiSudan/Core.lean +++ b/bench/CompPolyBench/Bivariate/GuruswamiSudan/Core.lean @@ -30,37 +30,33 @@ def runGsCoreSmallKoala (preset : BenchPreset) (gen : StdGen) : let fastAlekRootContext := fastKoalaBearAlekhnovichRootContext let warmup := gsWarmupIterations preset let denseMeasured := preset.selectNat 1 1 1 - let koetterMeasured := preset.selectNat 10 2 1 let leeDirectMeasured := preset.selectNat 100 15 3 let leeSubproductMeasured := preset.selectNat 90 13 3 let approximantDirectMeasured := preset.selectNat 7 1 1 let approximantSubproductMeasured := preset.selectNat 7 1 1 let fastDenseMeasured := preset.selectNat 2 1 1 - let fastKoetterMeasured := preset.selectNat 70 10 2 let fastLeeDirectMeasured := preset.selectNat 600 90 20 let fastLeeSubproductMeasured := preset.selectNat 400 60 10 let fastApproximantDirectMeasured := preset.selectNat 20 3 1 let fastApproximantSubproductMeasured := preset.selectNat 20 3 1 let alekDenseMeasured := denseMeasured - let alekKoetterMeasured := koetterMeasured let alekLeeDirectMeasured := leeDirectMeasured let alekLeeSubproductMeasured := leeSubproductMeasured let alekApproximantDirectMeasured := approximantDirectMeasured let alekApproximantSubproductMeasured := approximantSubproductMeasured let fastAlekDenseMeasured := fastDenseMeasured - let fastAlekKoetterMeasured := fastKoetterMeasured let fastAlekLeeDirectMeasured := fastLeeDirectMeasured let fastAlekLeeSubproductMeasured := fastLeeSubproductMeasured let fastAlekApproximantDirectMeasured := fastApproximantDirectMeasured let fastAlekApproximantSubproductMeasured := fastApproximantSubproductMeasured let checksumIterations := groupChecksumIterations denseMeasured [ - koetterMeasured, leeDirectMeasured, leeSubproductMeasured, fastDenseMeasured, - approximantDirectMeasured, approximantSubproductMeasured, fastKoetterMeasured, - fastLeeDirectMeasured, fastLeeSubproductMeasured, fastApproximantDirectMeasured, - fastApproximantSubproductMeasured, alekDenseMeasured, alekKoetterMeasured, - alekLeeDirectMeasured, alekLeeSubproductMeasured, alekApproximantDirectMeasured, - alekApproximantSubproductMeasured, fastAlekDenseMeasured, fastAlekKoetterMeasured, - fastAlekLeeDirectMeasured, fastAlekLeeSubproductMeasured, + leeDirectMeasured, leeSubproductMeasured, fastDenseMeasured, + approximantDirectMeasured, approximantSubproductMeasured, fastLeeDirectMeasured, + fastLeeSubproductMeasured, fastApproximantDirectMeasured, + fastApproximantSubproductMeasured, alekDenseMeasured, alekLeeDirectMeasured, + alekLeeSubproductMeasured, alekApproximantDirectMeasured, + alekApproximantSubproductMeasured, fastAlekDenseMeasured, fastAlekLeeDirectMeasured, + fastAlekLeeSubproductMeasured, fastAlekApproximantDirectMeasured, fastAlekApproximantSubproductMeasured ] let denseRow <- runTimed @@ -77,18 +73,6 @@ def runGsCoreSmallKoala (preset : BenchPreset) (gen : StdGen) : (fun _ ↦ gsCore points (denseInterpContext KoalaBear.Field) alekRootContext gsSmallParams) checksumPolynomialArrayKoala checksumIterations - let koetterRow <- runTimed - "guruswami-sudan-core-koetter-small" "CBivariate" - "Koetter + RR roots" - "KoalaBear.Field" gsSmallInterpInputShape preset warmup koetterMeasured - (fun _ ↦ gsCore points koalaBearKoetterInterpContext rothRootContext gsSmallParams) - checksumPolynomialArrayKoala checksumIterations - let koetterAlekRow <- runTimed - "guruswami-sudan-core-koetter-small-alekhnovich" "CBivariate" - "Koetter + Alekhnovich roots" - "KoalaBear.Field" gsSmallInterpInputShape preset warmup alekKoetterMeasured - (fun _ ↦ gsCore points koalaBearKoetterInterpContext alekRootContext gsSmallParams) - checksumPolynomialArrayKoala checksumIterations let leeDirectRow <- runTimed "guruswami-sudan-core-lee-direct-small" "CBivariate" "Lee-O'Sullivan direct + RR roots" @@ -161,20 +145,6 @@ def runGsCoreSmallKoala (preset : BenchPreset) (gen : StdGen) : gsCore fastPoints (denseInterpContext KoalaBear.Fast.Field) fastAlekRootContext gsSmallParams) checksumPolynomialArrayKoalaFast checksumIterations - let fastKoetterRow <- runTimed - "guruswami-sudan-core-koetter-small-fast" "CBivariate" - "Koetter + RR roots" - "KoalaBear.Fast.Field" gsSmallInterpInputShape preset warmup fastKoetterMeasured - (fun _ ↦ gsCore fastPoints fastKoalaBearKoetterInterpContext fastRothRootContext - gsSmallParams) - checksumPolynomialArrayKoalaFast checksumIterations - let fastKoetterAlekRow <- runTimed - "guruswami-sudan-core-koetter-small-alekhnovich-fast" "CBivariate" - "Koetter + Alekhnovich roots" - "KoalaBear.Fast.Field" gsSmallInterpInputShape preset warmup fastAlekKoetterMeasured - (fun _ ↦ gsCore fastPoints fastKoalaBearKoetterInterpContext fastAlekRootContext - gsSmallParams) - checksumPolynomialArrayKoalaFast checksumIterations let fastLeeDirectRow <- runTimed "guruswami-sudan-core-lee-direct-small-fast" "CBivariate" "Lee-O'Sullivan direct + RR roots" @@ -239,11 +209,10 @@ def runGsCoreSmallKoala (preset : BenchPreset) (gen : StdGen) : groupKey := "guruswami-sudan-core-small-koalabear", title := "Guruswami-Sudan full core, small (KoalaBear)", records := #[ - denseRow, denseAlekRow, koetterRow, koetterAlekRow, leeDirectRow, + denseRow, denseAlekRow, leeDirectRow, leeDirectAlekRow, leeSubproductRow, leeSubproductAlekRow, approximantDirectRow, approximantDirectAlekRow, approximantSubproductRow, approximantSubproductAlekRow, - fastDenseRow, fastDenseAlekRow, fastKoetterRow, fastKoetterAlekRow, - fastLeeDirectRow, fastLeeDirectAlekRow, fastLeeSubproductRow, + fastDenseRow, fastDenseAlekRow, fastLeeDirectRow, fastLeeDirectAlekRow, fastLeeSubproductRow, fastLeeSubproductAlekRow, fastApproximantDirectRow, fastApproximantDirectAlekRow, fastApproximantSubproductRow, fastApproximantSubproductAlekRow ] @@ -261,45 +230,27 @@ def runGsCoreMediumKoala (preset : BenchPreset) (gen : StdGen) : let alekRootContext := koalaBearAlekhnovichRootContext let fastAlekRootContext := fastKoalaBearAlekhnovichRootContext let warmup := gsWarmupIterations preset - let koetterMeasured := preset.selectNat 1 1 1 let leeMeasured := preset.selectNat 10 1 1 let approximantDirectMeasured := preset.selectNat 2 1 1 let approximantSubproductMeasured := preset.selectNat 2 1 1 - let fastKoetterMeasured := preset.selectNat 4 1 1 let fastLeeMeasured := preset.selectNat 50 7 1 let fastApproximantDirectMeasured := preset.selectNat 7 1 1 let fastApproximantSubproductMeasured := preset.selectNat 7 1 1 - let alekKoetterMeasured := koetterMeasured let alekLeeMeasured := leeMeasured let alekApproximantDirectMeasured := approximantDirectMeasured let alekApproximantSubproductMeasured := approximantSubproductMeasured - let fastAlekKoetterMeasured := fastKoetterMeasured let fastAlekLeeMeasured := fastLeeMeasured let fastAlekApproximantDirectMeasured := fastApproximantDirectMeasured let fastAlekApproximantSubproductMeasured := fastApproximantSubproductMeasured - let checksumIterations := groupChecksumIterations koetterMeasured [ + let checksumIterations := groupChecksumIterations leeMeasured [ leeMeasured, leeMeasured, approximantDirectMeasured, approximantSubproductMeasured, - fastKoetterMeasured, fastLeeMeasured, fastLeeMeasured, + fastLeeMeasured, fastLeeMeasured, fastApproximantDirectMeasured, fastApproximantSubproductMeasured, - alekKoetterMeasured, alekLeeMeasured, alekLeeMeasured, alekApproximantDirectMeasured, - alekApproximantSubproductMeasured, fastAlekKoetterMeasured, fastAlekLeeMeasured, + alekLeeMeasured, alekLeeMeasured, alekApproximantDirectMeasured, + alekApproximantSubproductMeasured, fastAlekLeeMeasured, fastAlekLeeMeasured, fastAlekApproximantDirectMeasured, fastAlekApproximantSubproductMeasured ] - let koetterRow <- runTimed - "guruswami-sudan-core-koetter-roth" "CBivariate" - "Koetter + RR roots" - "KoalaBear.Field" gsMediumInterpInputShape preset warmup koetterMeasured - (fun _ ↦ gsCore points koalaBearKoetterInterpContext rothRootContext - gsMediumInterpParams) - checksumPolynomialArrayKoala checksumIterations - let koetterAlekRow <- runTimed - "guruswami-sudan-core-koetter-alekhnovich" "CBivariate" - "Koetter + Alekhnovich roots" - "KoalaBear.Field" gsMediumInterpInputShape preset warmup alekKoetterMeasured - (fun _ ↦ gsCore points koalaBearKoetterInterpContext alekRootContext - gsMediumInterpParams) - checksumPolynomialArrayKoala checksumIterations let leeDirectRow <- runTimed "guruswami-sudan-core-lee-direct-roth" "CBivariate" "Lee-O'Sullivan direct + RR roots" @@ -358,23 +309,6 @@ def runGsCoreMediumKoala (preset : BenchPreset) (gen : StdGen) : (fun _ ↦ gsCore points koalaBearApproximantBasisSubproductInterpContext alekRootContext gsMediumInterpParams) checksumPolynomialArrayKoala checksumIterations - let fastKoetterRow <- runTimed - "guruswami-sudan-core-koetter-roth-fast" "CBivariate" - "Koetter + RR roots" - "KoalaBear.Fast.Field" gsMediumInterpInputShape preset warmup fastKoetterMeasured - (fun _ ↦ - gsCore fastPoints fastKoalaBearKoetterInterpContext fastRothRootContext - gsMediumInterpParams) - checksumPolynomialArrayKoalaFast checksumIterations - let fastKoetterAlekRow <- runTimed - "guruswami-sudan-core-koetter-alekhnovich-fast" "CBivariate" - "Koetter + Alekhnovich roots" - "KoalaBear.Fast.Field" gsMediumInterpInputShape preset warmup - fastAlekKoetterMeasured - (fun _ ↦ - gsCore fastPoints fastKoalaBearKoetterInterpContext fastAlekRootContext - gsMediumInterpParams) - checksumPolynomialArrayKoalaFast checksumIterations let fastLeeDirectRow <- runTimed "guruswami-sudan-core-lee-direct-roth-fast" "CBivariate" "Lee-O'Sullivan direct + RR roots" @@ -443,10 +377,10 @@ def runGsCoreMediumKoala (preset : BenchPreset) (gen : StdGen) : groupKey := "guruswami-sudan-core-medium-koalabear", title := "Guruswami-Sudan full core, medium (KoalaBear)", records := #[ - koetterRow, koetterAlekRow, leeDirectRow, leeDirectAlekRow, leeSubproductRow, + leeDirectRow, leeDirectAlekRow, leeSubproductRow, leeSubproductAlekRow, approximantDirectRow, approximantDirectAlekRow, - approximantSubproductRow, approximantSubproductAlekRow, fastKoetterRow, - fastKoetterAlekRow, fastLeeDirectRow, fastLeeDirectAlekRow, fastLeeSubproductRow, + approximantSubproductRow, approximantSubproductAlekRow, fastLeeDirectRow, + fastLeeDirectAlekRow, fastLeeSubproductRow, fastLeeSubproductAlekRow, fastApproximantDirectRow, fastApproximantDirectAlekRow, fastApproximantSubproductRow, fastApproximantSubproductAlekRow ] @@ -465,37 +399,33 @@ def runGsFilteredCoreSmallKoala (preset : BenchPreset) (gen : StdGen) : let fastAlekRootContext := fastKoalaBearAlekhnovichRootContext let warmup := gsWarmupIterations preset let denseMeasured := preset.selectNat 1 1 1 - let koetterMeasured := preset.selectNat 10 2 1 let leeDirectMeasured := preset.selectNat 100 15 3 let leeSubproductMeasured := preset.selectNat 90 13 3 let approximantDirectMeasured := preset.selectNat 7 1 1 let approximantSubproductMeasured := preset.selectNat 7 1 1 let fastDenseMeasured := preset.selectNat 2 1 1 - let fastKoetterMeasured := preset.selectNat 70 10 2 let fastLeeDirectMeasured := preset.selectNat 600 90 20 let fastLeeSubproductMeasured := preset.selectNat 400 60 10 let fastApproximantDirectMeasured := preset.selectNat 20 3 1 let fastApproximantSubproductMeasured := preset.selectNat 20 3 1 let alekDenseMeasured := denseMeasured - let alekKoetterMeasured := koetterMeasured let alekLeeDirectMeasured := leeDirectMeasured let alekLeeSubproductMeasured := leeSubproductMeasured let alekApproximantDirectMeasured := approximantDirectMeasured let alekApproximantSubproductMeasured := approximantSubproductMeasured let fastAlekDenseMeasured := fastDenseMeasured - let fastAlekKoetterMeasured := fastKoetterMeasured let fastAlekLeeDirectMeasured := fastLeeDirectMeasured let fastAlekLeeSubproductMeasured := fastLeeSubproductMeasured let fastAlekApproximantDirectMeasured := fastApproximantDirectMeasured let fastAlekApproximantSubproductMeasured := fastApproximantSubproductMeasured let checksumIterations := groupChecksumIterations denseMeasured [ - koetterMeasured, leeDirectMeasured, leeSubproductMeasured, fastDenseMeasured, - approximantDirectMeasured, approximantSubproductMeasured, fastKoetterMeasured, - fastLeeDirectMeasured, fastLeeSubproductMeasured, fastApproximantDirectMeasured, - fastApproximantSubproductMeasured, alekDenseMeasured, alekKoetterMeasured, - alekLeeDirectMeasured, alekLeeSubproductMeasured, alekApproximantDirectMeasured, - alekApproximantSubproductMeasured, fastAlekDenseMeasured, fastAlekKoetterMeasured, - fastAlekLeeDirectMeasured, fastAlekLeeSubproductMeasured, + leeDirectMeasured, leeSubproductMeasured, fastDenseMeasured, + approximantDirectMeasured, approximantSubproductMeasured, fastLeeDirectMeasured, + fastLeeSubproductMeasured, fastApproximantDirectMeasured, + fastApproximantSubproductMeasured, alekDenseMeasured, alekLeeDirectMeasured, + alekLeeSubproductMeasured, alekApproximantDirectMeasured, + alekApproximantSubproductMeasured, fastAlekDenseMeasured, fastAlekLeeDirectMeasured, + fastAlekLeeSubproductMeasured, fastAlekApproximantDirectMeasured, fastAlekApproximantSubproductMeasured ] let denseRow <- runTimed @@ -514,20 +444,6 @@ def runGsFilteredCoreSmallKoala (preset : BenchPreset) (gen : StdGen) : gsFilteredCore points (denseInterpContext KoalaBear.Field) alekRootContext gsSmallParams 0) checksumPolynomialArrayKoala checksumIterations - let koetterRow <- runTimed - "guruswami-sudan-filtered-core-koetter-small" "CBivariate" - "Koetter + RR roots + filter" - "KoalaBear.Field" gsSmallFilteredShape preset warmup koetterMeasured - (fun _ ↦ gsFilteredCore points koalaBearKoetterInterpContext rothRootContext - gsSmallParams 0) - checksumPolynomialArrayKoala checksumIterations - let koetterAlekRow <- runTimed - "guruswami-sudan-filtered-core-koetter-small-alekhnovich" "CBivariate" - "Koetter + Alekhnovich roots + filter" - "KoalaBear.Field" gsSmallFilteredShape preset warmup alekKoetterMeasured - (fun _ ↦ gsFilteredCore points koalaBearKoetterInterpContext alekRootContext - gsSmallParams 0) - checksumPolynomialArrayKoala checksumIterations let leeDirectRow <- runTimed "guruswami-sudan-filtered-core-lee-direct-small" "CBivariate" "Lee-O'Sullivan direct + RR roots + filter" @@ -602,22 +518,6 @@ def runGsFilteredCoreSmallKoala (preset : BenchPreset) (gen : StdGen) : gsFilteredCore fastPoints (denseInterpContext KoalaBear.Fast.Field) fastAlekRootContext gsSmallParams 0) checksumPolynomialArrayKoalaFast checksumIterations - let fastKoetterRow <- runTimed - "guruswami-sudan-filtered-core-koetter-small-fast" "CBivariate" - "Koetter + RR roots + filter" - "KoalaBear.Fast.Field" gsSmallFilteredShape preset warmup fastKoetterMeasured - (fun _ ↦ - gsFilteredCore fastPoints fastKoalaBearKoetterInterpContext fastRothRootContext - gsSmallParams 0) - checksumPolynomialArrayKoalaFast checksumIterations - let fastKoetterAlekRow <- runTimed - "guruswami-sudan-filtered-core-koetter-small-alekhnovich-fast" "CBivariate" - "Koetter + Alekhnovich roots + filter" - "KoalaBear.Fast.Field" gsSmallFilteredShape preset warmup fastAlekKoetterMeasured - (fun _ ↦ - gsFilteredCore fastPoints fastKoalaBearKoetterInterpContext fastAlekRootContext - gsSmallParams 0) - checksumPolynomialArrayKoalaFast checksumIterations let fastLeeDirectRow <- runTimed "guruswami-sudan-filtered-core-lee-direct-small-fast" "CBivariate" "Lee-O'Sullivan direct + RR roots + filter" @@ -692,11 +592,10 @@ def runGsFilteredCoreSmallKoala (preset : BenchPreset) (gen : StdGen) : groupKey := "guruswami-sudan-filtered-core-small-koalabear", title := "Guruswami-Sudan filtered core, small (KoalaBear)", records := #[ - denseRow, denseAlekRow, koetterRow, koetterAlekRow, leeDirectRow, + denseRow, denseAlekRow, leeDirectRow, leeDirectAlekRow, leeSubproductRow, leeSubproductAlekRow, approximantDirectRow, approximantDirectAlekRow, approximantSubproductRow, approximantSubproductAlekRow, - fastDenseRow, fastDenseAlekRow, fastKoetterRow, fastKoetterAlekRow, - fastLeeDirectRow, fastLeeDirectAlekRow, fastLeeSubproductRow, + fastDenseRow, fastDenseAlekRow, fastLeeDirectRow, fastLeeDirectAlekRow, fastLeeSubproductRow, fastLeeSubproductAlekRow, fastApproximantDirectRow, fastApproximantDirectAlekRow, fastApproximantSubproductRow, fastApproximantSubproductAlekRow ] @@ -714,47 +613,27 @@ def runGsFilteredCoreMediumKoala (preset : BenchPreset) (gen : StdGen) : let alekRootContext := koalaBearAlekhnovichRootContext let fastAlekRootContext := fastKoalaBearAlekhnovichRootContext let warmup := gsWarmupIterations preset - let koetterMeasured := preset.selectNat 1 1 1 let leeMeasured := preset.selectNat 10 1 1 let approximantDirectMeasured := preset.selectNat 2 1 1 let approximantSubproductMeasured := preset.selectNat 2 1 1 - let fastKoetterMeasured := preset.selectNat 4 1 1 let fastLeeMeasured := preset.selectNat 50 7 1 let fastApproximantDirectMeasured := preset.selectNat 7 1 1 let fastApproximantSubproductMeasured := preset.selectNat 7 1 1 - let alekKoetterMeasured := koetterMeasured let alekLeeMeasured := leeMeasured let alekApproximantDirectMeasured := approximantDirectMeasured let alekApproximantSubproductMeasured := approximantSubproductMeasured - let fastAlekKoetterMeasured := fastKoetterMeasured let fastAlekLeeMeasured := fastLeeMeasured let fastAlekApproximantDirectMeasured := fastApproximantDirectMeasured let fastAlekApproximantSubproductMeasured := fastApproximantSubproductMeasured - let checksumIterations := groupChecksumIterations koetterMeasured [ + let checksumIterations := groupChecksumIterations leeMeasured [ leeMeasured, leeMeasured, approximantDirectMeasured, approximantSubproductMeasured, - fastKoetterMeasured, fastLeeMeasured, fastLeeMeasured, + fastLeeMeasured, fastLeeMeasured, fastApproximantDirectMeasured, fastApproximantSubproductMeasured, - alekKoetterMeasured, alekLeeMeasured, alekLeeMeasured, alekApproximantDirectMeasured, - alekApproximantSubproductMeasured, fastAlekKoetterMeasured, fastAlekLeeMeasured, + alekLeeMeasured, alekLeeMeasured, alekApproximantDirectMeasured, + alekApproximantSubproductMeasured, fastAlekLeeMeasured, fastAlekLeeMeasured, fastAlekApproximantDirectMeasured, fastAlekApproximantSubproductMeasured ] - let koetterRow <- runTimed - "guruswami-sudan-filtered-core-koetter" "CBivariate" - "Koetter + RR roots + filter" - "KoalaBear.Field" gsFilteredShape preset warmup koetterMeasured - (fun _ ↦ - gsFilteredCore points koalaBearKoetterInterpContext rothRootContext - gsMediumInterpParams 0) - checksumPolynomialArrayKoala checksumIterations - let koetterAlekRow <- runTimed - "guruswami-sudan-filtered-core-koetter-alekhnovich" "CBivariate" - "Koetter + Alekhnovich roots + filter" - "KoalaBear.Field" gsFilteredShape preset warmup alekKoetterMeasured - (fun _ ↦ - gsFilteredCore points koalaBearKoetterInterpContext alekRootContext - gsMediumInterpParams 0) - checksumPolynomialArrayKoala checksumIterations let leeDirectRow <- runTimed "guruswami-sudan-filtered-core-lee-direct" "CBivariate" "Lee-O'Sullivan direct + RR roots + filter" @@ -815,22 +694,6 @@ def runGsFilteredCoreMediumKoala (preset : BenchPreset) (gen : StdGen) : (fun _ ↦ gsFilteredCore points koalaBearApproximantBasisSubproductInterpContext alekRootContext gsMediumInterpParams 0) checksumPolynomialArrayKoala checksumIterations - let fastKoetterRow <- runTimed - "guruswami-sudan-filtered-core-koetter-fast" "CBivariate" - "Koetter + RR roots + filter" - "KoalaBear.Fast.Field" gsFilteredShape preset warmup fastKoetterMeasured - (fun _ ↦ - gsFilteredCore fastPoints fastKoalaBearKoetterInterpContext fastRothRootContext - gsMediumInterpParams 0) - checksumPolynomialArrayKoalaFast checksumIterations - let fastKoetterAlekRow <- runTimed - "guruswami-sudan-filtered-core-koetter-alekhnovich-fast" "CBivariate" - "Koetter + Alekhnovich roots + filter" - "KoalaBear.Fast.Field" gsFilteredShape preset warmup fastAlekKoetterMeasured - (fun _ ↦ - gsFilteredCore fastPoints fastKoalaBearKoetterInterpContext fastAlekRootContext - gsMediumInterpParams 0) - checksumPolynomialArrayKoalaFast checksumIterations let fastLeeDirectRow <- runTimed "guruswami-sudan-filtered-core-lee-direct-fast" "CBivariate" "Lee-O'Sullivan direct + RR roots + filter" @@ -903,10 +766,10 @@ def runGsFilteredCoreMediumKoala (preset : BenchPreset) (gen : StdGen) : groupKey := "guruswami-sudan-filtered-core-medium-koalabear", title := "Guruswami-Sudan filtered core, medium (KoalaBear)", records := #[ - koetterRow, koetterAlekRow, leeDirectRow, leeDirectAlekRow, leeSubproductRow, + leeDirectRow, leeDirectAlekRow, leeSubproductRow, leeSubproductAlekRow, approximantDirectRow, approximantDirectAlekRow, - approximantSubproductRow, approximantSubproductAlekRow, fastKoetterRow, - fastKoetterAlekRow, fastLeeDirectRow, fastLeeDirectAlekRow, fastLeeSubproductRow, + approximantSubproductRow, approximantSubproductAlekRow, fastLeeDirectRow, + fastLeeDirectAlekRow, fastLeeSubproductRow, fastLeeSubproductAlekRow, fastApproximantDirectRow, fastApproximantDirectAlekRow, fastApproximantSubproductRow, fastApproximantSubproductAlekRow ] diff --git a/bench/CompPolyBench/Bivariate/GuruswamiSudan/ReceivedWord.lean b/bench/CompPolyBench/Bivariate/GuruswamiSudan/ReceivedWord.lean index 6c6a729f..c8590752 100644 --- a/bench/CompPolyBench/Bivariate/GuruswamiSudan/ReceivedWord.lean +++ b/bench/CompPolyBench/Bivariate/GuruswamiSudan/ReceivedWord.lean @@ -207,7 +207,6 @@ private def perturbedCoreRows {F : Type*} [Field F] [BEq F] [LawfulBEq F] private def gsNonCodewordSmallSlowBackends : Array (PerturbedBackend KoalaBear.Field) := #[ ⟨"dense", "Dense linear", koalaBearDenseInterpContext, 1, 1, 1⟩, - ⟨"koetter", "Koetter", koalaBearKoetterInterpContext, 6, 1, 1⟩, ⟨"lee-direct", "Lee-O'Sullivan direct", koalaBearLeeDirectInterpContext, 15, 2, 1⟩, ⟨"lee-subproduct", "Lee-O'Sullivan subproduct", @@ -222,7 +221,6 @@ private def gsNonCodewordSmallSlowBackends : private def gsNonCodewordSmallFastBackends : Array (PerturbedBackend KoalaBear.Fast.Field) := #[ ⟨"dense", "Dense linear", fastKoalaBearDenseInterpContext, 1, 1, 1⟩, - ⟨"koetter", "Koetter", fastKoalaBearKoetterInterpContext, 30, 4, 1⟩, ⟨"lee-direct", "Lee-O'Sullivan direct", fastKoalaBearLeeDirectInterpContext, 80, 11, 2⟩, ⟨"lee-subproduct", "Lee-O'Sullivan subproduct", @@ -236,7 +234,6 @@ private def gsNonCodewordSmallFastBackends : private def gsNonCodewordMediumSlowBackends : Array (PerturbedBackend KoalaBear.Field) := #[ - ⟨"koetter", "Koetter", koalaBearKoetterInterpContext, 1, 1, 1⟩, ⟨"lee-direct", "Lee-O'Sullivan direct", koalaBearLeeDirectInterpContext, 2, 1, 1⟩, ⟨"lee-subproduct", "Lee-O'Sullivan subproduct", @@ -250,7 +247,6 @@ private def gsNonCodewordMediumSlowBackends : private def gsNonCodewordMediumFastBackends : Array (PerturbedBackend KoalaBear.Fast.Field) := #[ - ⟨"koetter", "Koetter", fastKoalaBearKoetterInterpContext, 2, 1, 1⟩, ⟨"lee-direct", "Lee-O'Sullivan direct", fastKoalaBearLeeDirectInterpContext, 10, 1, 1⟩, ⟨"lee-subproduct", "Lee-O'Sullivan subproduct", diff --git a/bench/CompPolyBench/Bivariate/GuruswamiSudan/Shared.lean b/bench/CompPolyBench/Bivariate/GuruswamiSudan/Shared.lean index 864369d2..61f98e54 100644 --- a/bench/CompPolyBench/Bivariate/GuruswamiSudan/Shared.lean +++ b/bench/CompPolyBench/Bivariate/GuruswamiSudan/Shared.lean @@ -8,7 +8,6 @@ import CompPolyBench.Common import CompPoly.Bivariate.Deriv import CompPoly.Bivariate.GuruswamiSudan import CompPoly.Bivariate.GuruswamiSudan.Implementations -import CompPoly.Bivariate.GuruswamiSudan.Interpolation.Koetter.Algorithm import CompPoly.Bivariate.GuruswamiSudan.Interpolation.WitnessDivisibility import CompPoly.Bivariate.GuruswamiSudan.Root.FieldRoots.KoalaBear @@ -16,7 +15,7 @@ import CompPoly.Bivariate.GuruswamiSudan.Root.FieldRoots.KoalaBear # Guruswami-Sudan Benchmarks KoalaBear cost-center benchmarks for the dense interpolation path, -Koetter interpolation path, Roth-Ruckenstein and Alekhnovich root finding, and +Roth-Ruckenstein and Alekhnovich root finding, and full backend-parametric `gsCore` and `gsFilteredCore`. -/ From bff6bcce184224020bbd5ceee4de9ab6f925c0e7 Mon Sep 17 00:00:00 2001 From: Valerii Huhnin Date: Fri, 12 Jun 2026 12:46:16 +0000 Subject: [PATCH 09/10] Clean up comments across the branch - Remove references to untracked analysis notes (gs-interpolation-complexity-analysis.md) from the hybrid backend docstrings; the ski-rental rationale is stated inline instead. - Update benchmark docstrings that still described the long-code group as comparing exactly two backends (it also runs the hybrid row). - Replace temporal/refactoring wording with declarative phrasing (unchunked debug helper, iteration-count convention note) and an absolute-units timing claim in the validity-checksum docstring. --- .../Bivariate/GuruswamiSudan/Implementations.lean | 12 ++++++------ .../Interpolation/Hybrid/Algorithm.lean | 13 ++++++------- .../Approximant/ModularEquation/Basic.lean | 4 ++-- .../Bivariate/GuruswamiSudan/ReceivedWord.lean | 14 +++++++------- .../Bivariate/GuruswamiSudan/Shared.lean | 12 +++++++----- 5 files changed, 28 insertions(+), 27 deletions(-) diff --git a/CompPoly/Bivariate/GuruswamiSudan/Implementations.lean b/CompPoly/Bivariate/GuruswamiSudan/Implementations.lean index 6f7b8d99..080cda2e 100644 --- a/CompPoly/Bivariate/GuruswamiSudan/Implementations.lean +++ b/CompPoly/Bivariate/GuruswamiSudan/Implementations.lean @@ -190,12 +190,12 @@ def fastKoalaBearApproximantBasisInterpContext : GSInterpContext KoalaBear.Fast.Field := fastKoalaBearApproximantBasisSubproductInterpContext -/-- Mulders-Storjohann step budget for the hybrid interpolation backend — the -ski-rental `B` of `gs-interpolation-complexity-analysis.md`, Section 6: -proportional to `ℓ^(ω−1)` (with `ℓ + 1` the module width) and independent of -`n` and `m` under softly-linear multiplication. The constant is calibrated -from the `n = 5040` long-code benchmark shape, where the approximant fallback -costs ≈ 11.4 s against ≈ 0.65 ms per reduction step. -/ +/-- Mulders-Storjohann step budget for the hybrid interpolation backend: the +ski-rental rent/buy break-even, set near the cost ratio between one +approximant-fallback solve and one reduction step. Both scale with the input +mass, so the ratio is proportional to `ℓ^(ω−1)` (with `ℓ + 1` the module +width) and independent of `n` and `m` under softly-linear multiplication; +the constant is calibrated from the `n = 5040` long-code benchmark shape. -/ def hybridReductionStepBudget (params : GSInterpParams) : Nat := 500 * leeOSullivanWidth params * leeOSullivanWidth params diff --git a/CompPoly/Bivariate/GuruswamiSudan/Interpolation/Hybrid/Algorithm.lean b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/Hybrid/Algorithm.lean index 6684100e..c3891366 100644 --- a/CompPoly/Bivariate/GuruswamiSudan/Interpolation/Hybrid/Algorithm.lean +++ b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/Hybrid/Algorithm.lean @@ -11,8 +11,8 @@ import CompPoly.Bivariate.GuruswamiSudan.Interpolation.LeeOSullivan.Algorithm # Hybrid Guruswami-Sudan Interpolation Output-sensitive interpolation backend combining the adaptive Lee-O'Sullivan -route with the order-driven approximant-basis route, following the ski-rental -analysis in `gs-interpolation-complexity-analysis.md`. +route with the order-driven approximant-basis route through a ski-rental +budget policy. The Mulders-Storjohann reduction of the Lee-O'Sullivan basis terminates after a number of steps that vanishes on codewords and scales with the distance of @@ -24,11 +24,10 @@ within a constant factor of the cheaper route on every input. The budget is the entire fuel policy: the reduction loop stops by itself as soon as the basis is conflict-free, so unused fuel is free and any cap below -`B` could only trigger premature fallback. (In particular the degree excess -`Δ` of `gs-interpolation-complexity-analysis.md` is *not* a usable cap: the -implementation's step count includes leading-position moves that leave the -shifted-degree sum unchanged, so reductions routinely need more than `Δ` -steps.) +`B` could only trigger premature fallback. (In particular the excess of the +initial shifted-degree sum over the weak-Popov floor is *not* a usable cap: +the step count includes leading-position moves that leave the shifted-degree +sum unchanged, so reductions routinely need more steps than that excess.) The dispatch affects performance only: both branches are complete verified interpolators, and `Interpolation/Hybrid/Correctness.lean` shows the hybrid diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation/Basic.lean b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation/Basic.lean index 3d90529e..4486aea9 100644 --- a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation/Basic.lean +++ b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation/Basic.lean @@ -546,8 +546,8 @@ def repairSolutionRowsViaPMBasis pmCtx.runtime.leafCutoff repairState.filtered reduced /-- Debug helper for tiny problems that intentionally disables -principal-coordinate chunking. This keeps the old unchunked behavior available -for inspection without letting the production context bypass partial +principal-coordinate chunking, keeping an unchunked solve available for +inspection without letting the production context bypass partial linearization. -/ def debugUnchunkedFilteredSolutionBasisViaPMBasis (mulCtx : CPolynomial.MulContext F) (modCtx : CPolynomial.ModContext F) diff --git a/bench/CompPolyBench/Bivariate/GuruswamiSudan/ReceivedWord.lean b/bench/CompPolyBench/Bivariate/GuruswamiSudan/ReceivedWord.lean index c8590752..3a0d24cd 100644 --- a/bench/CompPolyBench/Bivariate/GuruswamiSudan/ReceivedWord.lean +++ b/bench/CompPolyBench/Bivariate/GuruswamiSudan/ReceivedWord.lean @@ -11,8 +11,8 @@ import CompPolyBench.Bivariate.GuruswamiSudan.Shared Perturbed (non-codeword) counterparts of the codeword interpolation, core, and filtered-core benchmark groups, over the same small and medium input shapes and -the same backend rows, plus a long-code large interpolation group comparing -only the Lee-O'Sullivan and approximant backends. Perturbations stay within +the same backend rows, plus a long-code large interpolation group for the +reduction-based backends. Perturbations stay within the Guruswami-Sudan decoding radius, so the full pipeline still recovers the original message. @@ -144,7 +144,7 @@ per-point Hasse check costs `O(n * m^2 * l * D)` term evaluations — minutes per row at `n ≈ 5000`, dominating every backend inside the timed closure — so the large group validates witnesses through the base-`(Y - R)` digit divisibility characterization, which checks the multiplicity constraints at -every point in seconds with NTT-backed contexts. -/ +every point in a negligible fraction of any row's solve time. -/ private def checksumDivisibilityInterpolationValidityOption {F : Type*} [Field F] [BEq F] [LawfulBEq F] [Nontrivial F] [DecidableEq F] (V : CPolynomial.VanishingPolynomialContext F) @@ -258,10 +258,10 @@ private def gsNonCodewordMediumFastBackends : ⟨"hybrid", "Hybrid", fastKoalaBearHybridInterpContext, 5, 1, 1⟩ ] -/-- Long-code interpolation backends: only the two reduction-based backends -stay tractable at `n ≈ 5000` (Koetter grows like `~n^3.4` and the dense -solver far faster), so the large group benchmarks Lee-O'Sullivan against the -approximant basis, and only over the native-word fast field — canonical +/-- Long-code interpolation backends: only the reduction-based backends +(Lee-O'Sullivan, approximant basis, and their hybrid) stay tractable at +`n ≈ 5000` — Koetter-style interpolation grows like `~n^3.4` and the dense +solver far faster — and only the native-word fast field runs: canonical KoalaBear multiplies every row by another ~3-5x without changing the comparison. Only the subproduct variants run: the direct variants differ just in the quadratic-in-`n` setup (vanishing polynomial, interpolant, and diff --git a/bench/CompPolyBench/Bivariate/GuruswamiSudan/Shared.lean b/bench/CompPolyBench/Bivariate/GuruswamiSudan/Shared.lean index 61f98e54..3f3074cc 100644 --- a/bench/CompPolyBench/Bivariate/GuruswamiSudan/Shared.lean +++ b/bench/CompPolyBench/Bivariate/GuruswamiSudan/Shared.lean @@ -51,9 +51,10 @@ def gsMediumInterpParams : GSInterpParams := /-- Long-code shape for the asymptotic interpolation comparison: at `n ≈ 5000` the defect-driven Mulders-Storjohann reduction (quadratic in `n` on error-bearing words) crosses over against the quasi-linear approximant -PM-basis solver, so the large group compares only those two backends. -Koetter grows like `~n^3.4` here (extrapolated ~13 h per call) and the dense -solver is far beyond that, so neither is benchmarked at this size. -/ +PM-basis solver, so the large group compares the reduction-based backends +(Lee-O'Sullivan, approximant basis, and their hybrid). Koetter-style +constraint-by-constraint interpolation grows like `~n^3.4` here and the dense +solver is far beyond that, so neither is tractable at this size. -/ def gsLargeInterpPointCount : Nat := 5040 def gsLargeInterpMessageDegree : Nat := 1261 def gsLargeInterpMultiplicity : Nat := 2 @@ -191,8 +192,9 @@ def gsWarmupIterations (preset : BenchPreset) : Nat := preset.selectNat 1 0 0 /- -Preset iteration counts are fixed per benchmark row to keep total runtimes -comparable within each group across `small`, `medium`, and `large` runs. +Within a group, each row's measured-iteration count is sized so the rows +spend approximately the same total wall-clock time; presets only scale the +counts. -/ /-- Benchmark group metadata for Guruswami-Sudan cost-center rows. -/ def guruswamiSudanGroupInfos : List BenchGroupInfo := [ From 191da6bea8b00ee6816b1c919f09d600a4d6ce6a Mon Sep 17 00:00:00 2001 From: Valerii Huhnin Date: Fri, 12 Jun 2026 12:53:32 +0000 Subject: [PATCH 10/10] Add literature references to the approximant backend Mirror the References convention of the other backends (LOS06, MS03, GS99, Ale05, RR00): - Approximant/Basic.lean and PMBasis/Recursion.lean cite Beckermann- Labahn [BL94] (minimal approximant bases) and Giorgi-Jeannerod- Villard [GJV03] (the PM-Basis divide-and-conquer). - PartialLinearization.lean cites Storjohann [Sto06] (partial linearization of unbalanced orders). - ModularEquation/Basic.lean cites [GJV03], [Sto06], and Chowdhury- Jeannerod-Neiger-Schost-Villard [CJNSV15] for the diagonal modular-system-to-order-basis reduction. - The GS ApproximantBasis backend (Basic, Algorithm) cites [CJNSV15] (interpolation with multiplicities as simultaneous approximations). - Hybrid/Algorithm.lean cites Karlin-Manasse-Rudolph-Sleator [KMRS88] for the ski-rental budget policy. --- .../Interpolation/ApproximantBasis/Algorithm.lean | 6 ++++++ .../Interpolation/ApproximantBasis/Basic.lean | 6 ++++++ .../GuruswamiSudan/Interpolation/Hybrid/Algorithm.lean | 5 +++++ .../PolynomialMatrix/Approximant/Basic.lean | 7 +++++++ .../Approximant/ModularEquation/Basic.lean | 9 +++++++++ .../PolynomialMatrix/Approximant/PMBasis/Recursion.lean | 7 +++++++ .../Approximant/PartialLinearization.lean | 4 ++++ 7 files changed, 44 insertions(+) diff --git a/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Algorithm.lean b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Algorithm.lean index a99516e9..e205b81c 100644 --- a/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Algorithm.lean +++ b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Algorithm.lean @@ -12,6 +12,12 @@ import CompPoly.Bivariate.GuruswamiSudan.Interpolation.ApproximantBasis.Basic This backend constructs the GS diagonal modular equations, calls an explicit solution-basis context, selects a least shifted-degree solution row, and normalizes the resulting bivariate polynomial. + +## References + +* [Chowdhury, M. F. I., Jeannerod, C.-P., Neiger, V., Schost, E., and + Villard, G., *Faster algorithms for multivariate interpolation with + multiplicities and simultaneous polynomial approximations*][CJNSV15] -/ namespace CompPoly diff --git a/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Basic.lean b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Basic.lean index 51fac8ca..70726dab 100644 --- a/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Basic.lean +++ b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/ApproximantBasis/Basic.lean @@ -15,6 +15,12 @@ import CompPoly.Univariate.Vanishing Construction of the diagonal modular equations used by the approximant-basis interpolation backend. + +## References + +* [Chowdhury, M. F. I., Jeannerod, C.-P., Neiger, V., Schost, E., and + Villard, G., *Faster algorithms for multivariate interpolation with + multiplicities and simultaneous polynomial approximations*][CJNSV15] -/ namespace CompPoly diff --git a/CompPoly/Bivariate/GuruswamiSudan/Interpolation/Hybrid/Algorithm.lean b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/Hybrid/Algorithm.lean index c3891366..3fdee84f 100644 --- a/CompPoly/Bivariate/GuruswamiSudan/Interpolation/Hybrid/Algorithm.lean +++ b/CompPoly/Bivariate/GuruswamiSudan/Interpolation/Hybrid/Algorithm.lean @@ -32,6 +32,11 @@ sum unchanged, so reductions routinely need more steps than that excess.) The dispatch affects performance only: both branches are complete verified interpolators, and `Interpolation/Hybrid/Correctness.lean` shows the hybrid result always coincides with one of them. + +## References + +* [Karlin, A. R., Manasse, M. S., Rudolph, L., and Sleator, D. D., + *Competitive snoopy caching*][KMRS88] -/ namespace CompPoly diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/Basic.lean b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/Basic.lean index 405913a5..44c4d0a6 100644 --- a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/Basic.lean +++ b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/Basic.lean @@ -11,6 +11,13 @@ import CompPoly.LinearAlgebra.PolynomialMatrix.Operations Basic data structures for approximant-basis computations over polynomial matrices. + +## References + +* [Beckermann, B., and Labahn, G., *A uniform approach for the fast + computation of matrix-type Pade approximants*][BL94] +* [Giorgi, P., Jeannerod, C.-P., and Villard, G., *On the complexity of + polynomial matrix computations*][GJV03] -/ namespace CompPoly diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation/Basic.lean b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation/Basic.lean index 4486aea9..80060833 100644 --- a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation/Basic.lean +++ b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/ModularEquation/Basic.lean @@ -37,6 +37,15 @@ Callers that only need a witness under a known degree bound pass it as solve is skipped entirely, and when it does not the certified window is shrunk to the bound. Passing `none` requests a degree-minimal answer and always runs the verification solve over the best-degree-shrunk window. + +## References + +* [Giorgi, P., Jeannerod, C.-P., and Villard, G., *On the complexity of + polynomial matrix computations*][GJV03] +* [Storjohann, A., *Notes on computing minimal approximant bases*][Sto06] +* [Chowdhury, M. F. I., Jeannerod, C.-P., Neiger, V., Schost, E., and + Villard, G., *Faster algorithms for multivariate interpolation with + multiplicities and simultaneous polynomial approximations*][CJNSV15] -/ namespace CompPoly diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/Recursion.lean b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/Recursion.lean index 46cea01d..86e56909 100644 --- a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/Recursion.lean +++ b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PMBasis/Recursion.lean @@ -12,6 +12,13 @@ import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant.PMBasis.KernelLeaf Runtime data for recursive PM-basis computation, the fuel-bounded divide-and-conquer driver, root normalization, the `pmBasis` entry point, and the `PMBasisContext` contract structure. + +## References + +* [Beckermann, B., and Labahn, G., *A uniform approach for the fast + computation of matrix-type Pade approximants*][BL94] +* [Giorgi, P., Jeannerod, C.-P., and Villard, G., *On the complexity of + polynomial matrix computations*][GJV03] -/ namespace CompPoly diff --git a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PartialLinearization.lean b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PartialLinearization.lean index 48b52fe5..b1e7d839 100644 --- a/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PartialLinearization.lean +++ b/CompPoly/LinearAlgebra/PolynomialMatrix/Approximant/PartialLinearization.lean @@ -11,6 +11,10 @@ import CompPoly.LinearAlgebra.PolynomialMatrix.Approximant.PMBasis Small executable helpers used by diagonal modular-equation solvers to size the expanded X-adic problem without using one global oversized order. + +## References + +* [Storjohann, A., *Notes on computing minimal approximant bases*][Sto06] -/ namespace CompPoly