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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CompPoly.lean
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ import CompPoly.Multilinear.TransformEquiv
import CompPoly.Multivariate.CMvMonomial
import CompPoly.Multivariate.CMvPolynomial
import CompPoly.Multivariate.CMvPolynomialEvalLemmas
import CompPoly.Multivariate.DegreeBound
import CompPoly.Multivariate.FinSuccEquiv
import CompPoly.Multivariate.HornerLemmas
import CompPoly.Multivariate.Lawful
Expand All @@ -147,6 +148,7 @@ import CompPoly.Multivariate.MvPolyEquiv.Core
import CompPoly.Multivariate.MvPolyEquiv.Eval
import CompPoly.Multivariate.MvPolyEquiv.Instances
import CompPoly.Multivariate.Operations
import CompPoly.Multivariate.PartialEval
import CompPoly.Multivariate.Rename
import CompPoly.Multivariate.Restrict
import CompPoly.Multivariate.Unlawful
Expand Down
42 changes: 42 additions & 0 deletions CompPoly/Multivariate/DegreeBound.lean
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/-
Copyright (c) 2026 CompPoly Contributors. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: ArkLib Contributors
-/
import CompPoly.Multivariate.CMvPolynomial
import CompPoly.Univariate.ToPoly.Impl

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Unused import (verified: removal compiles). CPolynomial.natDegree lives in CompPoly/Univariate/Basic.lean, which is already transitively imported via CompPoly.Multivariate.CMvPolynomialToPoly.Impl pulls in the whole Mathlib Polynomial bridge for nothing.


/-!
# Degree-Bounded Computable Polynomials

Generic wrappers for computable univariate and multivariate polynomials bundled
with degree bounds.
-/

open CPoly Std

namespace CPoly.CMvPolynomial

variable {n : ℕ} {R : Type} [CommSemiring R] [BEq R] [LawfulBEq R]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Typeclass assumptions much stronger than needed (verified: the minimized file compiles). Everything in this file needs only [Zero R], and R : Type* matches the rest of the library:

variable {n : ℕ} {R : Type*} [Zero R]
...
def CDegreeLE (R : Type*) [Zero R] (d : ℕ) :=
  { p : CPolynomial R // p.natDegree ≤ d }

def CMvDegreeLE (R : Type*) [Zero R] (n d : ℕ) :=
  { p : CMvPolynomial n R // CMvPolynomial.IndividualDegreeLE (R := R) d p }

CPolynomial, CMvPolynomial, natDegree, and Lawful.monomials all require just [Zero R]. As written, every consumer must thread BEq/LawfulBEq/CommSemiring instances it doesn't need (per docs/wiki/typeclass-minimization.md). I checked the ArkLib consumer branch: relaxing is strictly safe — all use sites supply stronger instances. Suggest also adding these three definitions to tests/CompPolyTests/Multivariate/TypeclassMinimization.lean so the minimal assumptions are pinned.


/-- `p` has individual degree at most `deg` when every monomial exponent is
bounded by `deg` in every coordinate. -/
def IndividualDegreeLE (deg : ℕ) (p : CMvPolynomial n R) : Prop :=
letI := Classical.decEq R

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Dead code (verified: removal compiles). Nothing in the body needs DecidableEq RLawful.monomials is a key list and CMvMonomial.degreeOf never touches R. This looks inherited from the ArkLib original; it embeds a spurious let in the definition that every consumer unfolds past.

Separately (verified), the predicate is equivalent to a bound on the existing CMvPolynomial.degreeOf, and the connection is worth recording here so the new predicate plugs into the existing degreeOf_equiv bridge:

theorem individualDegreeLE_iff {deg : ℕ} (p : CMvPolynomial n R) :
    IndividualDegreeLE deg p ↔ ∀ i, CMvPolynomial.degreeOf i p ≤ deg := by
  unfold IndividualDegreeLE CMvPolynomial.degreeOf
  constructor
  · intro h i
    exact Finset.sup_le fun m hm => h i m (by simpa using hm)
  · intro h i m hm
    exact (Finset.le_sup (s := (Lawful.monomials p).toFinset)
      (f := fun m => CMvMonomial.degreeOf m i) (by simpa using hm)).trans (h i)

∀ i : Fin n, ∀ mono ∈ Lawful.monomials p, mono.degreeOf i ≤ deg

end CPoly.CMvPolynomial

namespace CompPoly

/-- A computable univariate polynomial with `natDegree ≤ d`. -/
def CDegreeLE (R : Type) [BEq R] [Semiring R] [LawfulBEq R] (d : ℕ) :=
{ p : CPolynomial R // p.natDegree ≤ d }

/-- A computable multivariate polynomial with individual degree at most `d` in
every coordinate. -/
def CMvDegreeLE
(R : Type) [BEq R] [CommSemiring R] [LawfulBEq R] (n d : ℕ) :=
{ p : CMvPolynomial n R // CMvPolynomial.IndividualDegreeLE (R := R) d p }

end CompPoly
38 changes: 38 additions & 0 deletions CompPoly/Multivariate/Operations.lean
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,44 @@ attribute [grind =]
aeval_C aeval_X aeval_add aeval_mul
aeval_zero aeval_one aeval_pow aeval_neg aeval_sub


/-- The computable substitution `bind₁` agrees with Mathlib substitution after
transporting through `fromCMvPolynomial`. -/
theorem fromCMvPolynomial_bind₁ {n m : ℕ} {R : Type*} [CommSemiring R] [BEq R]
[LawfulBEq R] (f : Fin n → CMvPolynomial m R) (p : CMvPolynomial n R) :

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Consider spelling the RHS with MvPolynomial.aeval (verified: compiles, no new import):

fromCMvPolynomial (bind₁ f p) =
  MvPolynomial.aeval (fun i => fromCMvPolynomial (f i)) (fromCMvPolynomial p)

This mirrors CompPoly's own bind₁_eq_aeval and is more discoverable than raw eval₂ C. (MvPolynomial.bind₁ would be the most literal spelling, but it isn't in the current import closure — it lives in Mathlib.Algebra.MvPolynomial.Monad; the aeval form needs nothing new.) I checked there's no existing fromCMvPolynomial_aeval/eval₂ bridge that makes this lemma redundant, so the proof is genuinely needed — a general fromCMvPolynomial_eval₂ next to eval₂_equiv would subsume it and is nice follow-up material.

Nit: double blank line above the docstring (line 458).

fromCMvPolynomial (bind₁ f p) =
MvPolynomial.eval₂ MvPolynomial.C (fun i => fromCMvPolynomial (f i))
(fromCMvPolynomial p) := by
rw [bind₁_eq_aeval]
unfold aeval
have h := MvPolynomial.map_eval₂Hom
(f := algebraMap R (CMvPolynomial m R))
(g := f)
(φ := (CPoly.polyRingEquiv (n := m) (R := R)).toRingHom)
(p := fromCMvPolynomial p)
have hcomp :
((CPoly.polyRingEquiv (n := m) (R := R)).toRingHom).comp
(algebraMap R (CMvPolynomial m R)) = MvPolynomial.C := by
ext r μ
rw [RingHom.comp_apply]
change MvPolynomial.coeff μ
(fromCMvPolynomial (algebraMap R (CMvPolynomial m R) r)) =
MvPolynomial.coeff μ (MvPolynomial.C r)
rw [show (algebraMap R (CMvPolynomial m R)) r = CMvPolynomial.C (n := m) r from rfl]
rw [fromCMvPolynomial_C]
rw [eval₂_equiv (p := p) (f := algebraMap R (CMvPolynomial m R)) (vals := f)]
have h' :
fromCMvPolynomial
(MvPolynomial.eval₂ (algebraMap R (CMvPolynomial m R)) f
(fromCMvPolynomial p)) =
MvPolynomial.eval₂
(((CPoly.polyRingEquiv (n := m) (R := R)).toRingHom).comp
(algebraMap R (CMvPolynomial m R)))
(fun i => fromCMvPolynomial (f i))
(fromCMvPolynomial p) := by
simpa [CPoly.polyRingEquiv, CPoly.polyEquiv] using h
rw [hcomp] at h'
exact h'
end CMvPolynomial

namespace Lawful
Expand Down
184 changes: 184 additions & 0 deletions CompPoly/Multivariate/PartialEval.lean
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/-
Copyright (c) 2026 CompPoly Contributors. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: ArkLib Contributors
-/
import CompPoly.Data.MvPolynomial.Notation
import CompPoly.Multivariate.DegreeBound

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Two unused imports (verified: removal compiles). Nothing in this file uses the R[X σ] notation, and nothing here references IndividualDegreeLE or the DegreeBound file. (If you adopt the IndividualDegreeLE-preservation corollary suggested below on the degree lemma, the DegreeBound import becomes genuinely used — otherwise both lines can go.)

import CompPoly.Multivariate.Operations
import CompPoly.Multivariate.MvPolyEquiv.Eval

/-!
# Partial evaluation of the first variable of a `CMvPolynomial`

`partialEvalFirst a p` fixes variable `0` of a multivariate computable
polynomial to a scalar value, together with its evaluation lemma and the
per-variable degree-bound lemma.
-/

open CompPoly Std

namespace CPoly

namespace CMvPolynomial

variable {n : ℕ} {R : Type} [CommSemiring R] [BEq R] [LawfulBEq R]

omit [BEq R] [LawfulBEq R] in
private lemma partialEvalFirst_subst_degreeOf_le [Nontrivial R] (a : R)
(i : Fin n) (j : Fin (n + 1)) :
MvPolynomial.degreeOf i
(Fin.cases (MvPolynomial.C a) (fun k : Fin n => MvPolynomial.X k) j :
MvPolynomial (Fin n) R) ≤ if j = i.succ then 1 else 0 := by
cases j using Fin.cases with
| zero =>
simp [MvPolynomial.degreeOf_C]
| succ j =>
by_cases h : j = i
· subst h
simp
· have hsucc : Fin.succ j ≠ i.succ := by
intro h'
exact h ((Fin.succ_injective n) h')
have hi_ne_j : i ≠ j := fun hij => h hij.symm
simp [hsucc, hi_ne_j, MvPolynomial.degreeOf_X]

omit [BEq R] [LawfulBEq R] in
private lemma partialEvalFirst_eval₂_monomial_degreeOf_le [Nontrivial R] {deg : ℕ}
(a : R) (i : Fin n) (s : Fin (n + 1) →₀ ℕ) (c : R)
(hs : s i.succ ≤ deg) :
MvPolynomial.degreeOf i
(MvPolynomial.eval₂ MvPolynomial.C
(fun j : Fin (n + 1) =>
Fin.cases (MvPolynomial.C a) (fun k : Fin n => MvPolynomial.X k) j)
(MvPolynomial.monomial s c) : MvPolynomial (Fin n) R) ≤ deg := by
rw [MvPolynomial.eval₂_monomial]
refine (MvPolynomial.degreeOf_C_mul_le _ i c).trans ?_
rw [Finsupp.prod]
refine (MvPolynomial.degreeOf_prod_le i s.support
(fun j => (Fin.cases (MvPolynomial.C a) (fun k : Fin n => MvPolynomial.X k) j :
MvPolynomial (Fin n) R) ^ s j)).trans ?_
calc
(∑ x ∈ s.support,
MvPolynomial.degreeOf i
((Fin.cases (MvPolynomial.C a) (fun k : Fin n => MvPolynomial.X k) x :
MvPolynomial (Fin n) R) ^ s x))
≤ ∑ x ∈ s.support, s x * (if x = i.succ then 1 else 0) := by
refine Finset.sum_le_sum ?_
intro x hx
exact (MvPolynomial.degreeOf_pow_le i
(Fin.cases (MvPolynomial.C a) (fun k : Fin n => MvPolynomial.X k) x :
MvPolynomial (Fin n) R) (s x)).trans
(Nat.mul_le_mul_left _ (partialEvalFirst_subst_degreeOf_le a i x))
_ ≤ s i.succ := by
classical
by_cases hi : i.succ ∈ s.support
· rw [Finset.sum_eq_single i.succ]
· simp
· intro x hx hne
simp [hne]
· intro hnot
exact False.elim (hnot hi)
· rw [Finset.sum_eq_zero]
· simp
· intro x hx
have hne : x ≠ i.succ := fun h => hi (h ▸ hx)
simp [hne]
_ ≤ deg := hs

omit [BEq R] [LawfulBEq R] in
private lemma partialEvalFirst_eval₂_degreeOf_le [Nontrivial R] {deg : ℕ}
(a : R) (i : Fin n) (p : MvPolynomial (Fin (n + 1)) R)
(hDeg : ∀ s ∈ p.support, s i.succ ≤ deg) :
MvPolynomial.degreeOf i
(MvPolynomial.eval₂ MvPolynomial.C
(fun j : Fin (n + 1) =>
Fin.cases (MvPolynomial.C a) (fun k : Fin n => MvPolynomial.X k) j)
p : MvPolynomial (Fin n) R) ≤ deg := by
rw [MvPolynomial.eval₂_eq]
refine (MvPolynomial.degreeOf_sum_le i p.support
(fun s => MvPolynomial.C (p.coeff s) *
∏ x ∈ s.support,
(Fin.cases (MvPolynomial.C a) (fun k : Fin n => MvPolynomial.X k) x :
MvPolynomial (Fin n) R) ^ s x)).trans ?_
apply Finset.sup_le
intro s hs
simpa [MvPolynomial.eval₂_monomial, Finsupp.prod] using
partialEvalFirst_eval₂_monomial_degreeOf_le (n := n) (R := R)
(deg := deg) a i s (p.coeff s) (hDeg s hs)

/-! ## Core operation -/

/-- Fix variable 0 of a multivariate polynomial to a scalar value `a`. -/
def partialEvalFirst (a : R) (p : CMvPolynomial (n + 1) R) : CMvPolynomial n R :=
bind₁ (Fin.cons (C a) X) p

/-! ## Evaluation lemma -/

/-- `partialEvalFirst a p` correctly implements partial evaluation. -/
theorem partialEvalFirst_eval (a : R) (p : CMvPolynomial (n + 1) R) (v : Fin n → R) :
(partialEvalFirst a p).eval v = p.eval (Fin.cons a v) := by
unfold partialEvalFirst
rw [eval_equiv, fromCMvPolynomial_bind₁]
rw [MvPolynomial.eval₂_comp_left]
have hc : (MvPolynomial.eval v).comp MvPolynomial.C = RingHom.id R := by
ext r
simp
have hv :
(⇑(MvPolynomial.eval v) ∘
fun i => fromCMvPolynomial
(((Fin.cons (CMvPolynomial.C (n := n) a)
(fun i : Fin n => CMvPolynomial.X (R := R) i)) :
Fin (n + 1) → CMvPolynomial n R) i)) =
Fin.cons a v := by
funext i
cases i using Fin.cases with
| zero => simp [fromCMvPolynomial_C]
| succ i => simp [fromCMvPolynomial_X]
rw [hc, hv]
exact (eval_equiv (p := p) (vals := Fin.cons a v)).symm

/-! ## Degree preservation -/

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Nontrivial R] can be dropped from this public lemma (verified: compiles). Over a subsingleton ring the statement is vacuous — a Lawful polynomial stores no zero coefficients, hence no monomials. The private helpers can keep their [Nontrivial R]:

theorem partialEvalFirst_degreeOf_le {deg : ℕ} (a : R)
    (i : Fin n) (p : CMvPolynomial (n + 1) R)
    (hDeg : ∀ mono ∈ Lawful.monomials p, mono.degreeOf i.succ ≤ deg) :
    ∀ mono ∈ Lawful.monomials (partialEvalFirst a p), mono.degreeOf i ≤ deg := by
  intro mono hmono
  rcases subsingleton_or_nontrivial R with hR | hR
  · have hmem : mono ∈ (partialEvalFirst a p).1 := by simpa using hmono
    have hne := (partialEvalFirst a p).2 mono
    rw [getElem?_pos _ _ hmem,
      Subsingleton.elim ((partialEvalFirst a p).1[mono]'hmem) 0] at hne
    exact absurd rfl hne
  · <original proof body>

This matters downstream: on the ArkLib consumer branch, [Nontrivial R] is threaded through partialEvalFirst_individualDegreeLE, stepResidual, and SingleRound.lean's variable block solely to satisfy this hypothesis, over a generic CommSemiring.

Note I'm deliberately not suggesting restating this lemma via CMvPolynomial.degreeOf — that would break ArkLib's exact term-mode application at Interaction/CompPoly.lean:30. Instead, two additive corollaries (both verified) give the clean API with zero breakage:

theorem partialEvalFirst_degreeOf_le' {deg : ℕ} (a : R) (i : Fin n)
    (p : CMvPolynomial (n + 1) R) (h : CMvPolynomial.degreeOf i.succ p ≤ deg) :
    CMvPolynomial.degreeOf i (partialEvalFirst a p) ≤ deg := ...

theorem partialEvalFirst_individualDegreeLE {deg : ℕ} (a : R)
    (p : CMvPolynomial (n + 1) R) (h : IndividualDegreeLE deg p) :
    IndividualDegreeLE deg (partialEvalFirst a p) :=
  fun i => partialEvalFirst_degreeOf_le a i p (fun mono hmono => h i.succ mono hmono)

The second is exactly ArkLib's wrapper in Interaction/CompPoly.lean — upstreaming it lets ArkLib delete that wrapper.

/-- `partialEvalFirst` preserves degree bounds for each remaining variable. -/
theorem partialEvalFirst_degreeOf_le [Nontrivial R] {deg : ℕ} (a : R)
(i : Fin n) (p : CMvPolynomial (n + 1) R)
(hDeg : ∀ mono ∈ Lawful.monomials p, mono.degreeOf i.succ ≤ deg) :
∀ mono ∈ Lawful.monomials (partialEvalFirst a p), mono.degreeOf i ≤ deg := by
intro mono hmono
have hSupport :
∀ s ∈ (fromCMvPolynomial p).support, s i.succ ≤ deg := by
apply MvPolynomial.degreeOf_le_iff.mp
have hdegree := congrFun (degreeOf_equiv (p := p) (S := R)) i.succ
rw [← hdegree]
unfold CMvPolynomial.degreeOf
apply Finset.sup_le
intro mono hmono
exact hDeg mono (by simpa using hmono)
have hEval :
MvPolynomial.degreeOf i (fromCMvPolynomial (partialEvalFirst a p)) ≤ deg := by
unfold partialEvalFirst
rw [fromCMvPolynomial_bind₁]
have hvars :
(fun i : Fin (n + 1) =>
fromCMvPolynomial
(((Fin.cons (CMvPolynomial.C (n := n) a)
(fun i : Fin n => CMvPolynomial.X (R := R) i)) :
Fin (n + 1) → CMvPolynomial n R) i)) =
(fun j : Fin (n + 1) =>
Fin.cases (MvPolynomial.C a) (fun k : Fin n => MvPolynomial.X k) j) := by
funext j
cases j using Fin.cases with
| zero => simp [fromCMvPolynomial_C]
| succ j => simp [fromCMvPolynomial_X]
rw [hvars]
exact partialEvalFirst_eval₂_degreeOf_le (n := n) (R := R) a i
(fromCMvPolynomial p) hSupport
have hdegree := congrFun (degreeOf_equiv (p := partialEvalFirst a p) (S := R)) i
rw [← hdegree] at hEval
exact (Finset.le_sup (s := (Lawful.monomials (partialEvalFirst a p)).toFinset)
(f := fun m => m.degreeOf i) (by simpa using hmono)).trans hEval

end CMvPolynomial

end CPoly
Loading