# The is_poly predicate #

WittVector.IsPoly is a (type-valued) predicate on functions f : Π R, 𝕎 R → 𝕎 R. It asserts that there is a family of polynomials φ : ℕ → MvPolynomial ℕ ℤ, such that the nth coefficient of f x is equal to φ n evaluated on the coefficients of x. Many operations on Witt vectors satisfy this predicate (or an analogue for higher arity functions). We say that such a function f is a polynomial function.

The power of satisfying this predicate comes from WittVector.IsPoly.ext. It shows that if φ and ψ witness that f and g are polynomial functions, then f = g not merely when φ = ψ, but in fact it suffices to prove

∀ n, bind₁ φ (wittPolynomial p _ n) = bind₁ ψ (wittPolynomial p _ n)


(in other words, when evaluating the Witt polynomials on φ and ψ, we get the same values) which will then imply φ = ψ and hence f = g.

Even though this sufficient condition looks somewhat intimidating, it is rather pleasant to check in practice; more so than direct checking of φ = ψ.

In practice, we apply this technique to show that the composition of WittVector.frobenius and WittVector.verschiebung is equal to multiplication by p.

## Main declarations #

• WittVector.IsPoly, WittVector.IsPoly₂: two predicates that assert that a unary/binary function on Witt vectors is polynomial in the coefficients of the input values.
• WittVector.IsPoly.ext, WittVector.IsPoly₂.ext: two polynomial functions are equal if their families of polynomials are equal after evaluating the Witt polynomials on them.
• WittVector.IsPoly.comp (+ many variants) show that unary/binary compositions of polynomial functions are polynomial.
• WittVector.idIsPoly, WittVector.negIsPoly, WittVector.addIsPoly₂, WittVector.mulIsPoly₂: several well-known operations are polynomial functions (for Verschiebung, Frobenius, and multiplication by p, see their respective files).

## On higher arity analogues #

Ideally, there should be a predicate IsPolyₙ for functions of higher arity, together with IsPolyₙ.comp that shows how such functions compose. Since mathlib does not have a library on composition of higher arity functions, we have only implemented the unary and binary variants so far. Nullary functions (a.k.a. constants) are treated as constant functions and fall under the unary case.

## Tactics #

There are important metaprograms defined in this file: the tactics ghost_simp and ghost_calc and the attribute @[ghost_simps]. These are used in combination to discharge proofs of identities between polynomial functions.

The ghost_calc tactic makes use of the IsPoly and IsPoly₂ typeclass and its instances. (In Lean 3, there was an @[is_poly] attribute to manage these instances, because typeclass resolution did not play well with function composition. This no longer seems to be an issue, so that such instances can be defined directly.)

Any lemma doing "ring equation rewriting" with polynomial functions should be tagged @[ghost_simps], e.g.

@[ghost_simps]
lemma bind₁_frobenius_poly_wittPolynomial (n : ℕ) :
bind₁ (frobenius_poly p) (wittPolynomial p ℤ n) = (wittPolynomial p ℤ (n+1))


Proofs of identities between polynomial functions will often follow the pattern

  ghost_calc _
<minor preprocessing>
ghost_simp


## References #

• [Hazewinkel, Witt Vectors][Haze09]

• [Commelin and Lewis, Formalizing the Ring of Witt Vectors][CL21]

### The IsPoly predicate #

theorem WittVector.poly_eq_of_wittPolynomial_bind_eq' (p : ) {idx : Type u_2} [Fact p.Prime] (f : MvPolynomial (idx × ) ) (g : MvPolynomial (idx × ) ) (h : ∀ (n : ), () = ()) :
f = g
theorem WittVector.poly_eq_of_wittPolynomial_bind_eq (p : ) [Fact p.Prime] (f : ) (g : ) (h : ∀ (n : ), () = ()) :
f = g
class WittVector.IsPoly (p : ) (f : R : Type u_3⦄ → [inst : ] → ) :

A function f : Π R, 𝕎 R → 𝕎 R that maps Witt vectors to Witt vectors over arbitrary base rings is said to be polynomial if there is a family of polynomials φₙ over ℤ such that the nth coefficient of f x is given by evaluating φₙ at the coefficients of x.

See also WittVector.IsPoly₂ for the binary variant.

The ghost_calc tactic makes use of the IsPoly and IsPoly₂ typeclass and its instances. (In Lean 3, there was an @[is_poly] attribute to manage these instances, because typeclass resolution did not play well with function composition. This no longer seems to be an issue, so that such instances can be defined directly.)

• mk' :: (
• )
Instances
theorem WittVector.IsPoly.poly {p : } {f : R : Type u_3⦄ → [inst : ] → } [self : ] :
∃ (φ : ), ∀ ⦃R : Type u_3⦄ [inst : ] (x : ), (f x).coeff = fun (n : ) => (MvPolynomial.aeval x.coeff) (φ n)
instance WittVector.idIsPoly (p : ) :
WittVector.IsPoly p fun (x : Type u_3) (x_1 : ) => id

The identity function on Witt vectors is a polynomial function.

Equations
• =
instance WittVector.idIsPolyI' (p : ) :
WittVector.IsPoly p fun (x : Type u_3) (x_1 : ) (a : ) => a
Equations
• =
instance WittVector.IsPoly.instInhabitedId (p : ) :
Inhabited (WittVector.IsPoly p fun (x : Type u_3) (x_1 : ) => id)
Equations
• = { default := }
theorem WittVector.IsPoly.ext {p : } [Fact p.Prime] {f : R : Type u⦄ → [inst : ] → } {g : R : Type u⦄ → [inst : ] → } (hf : ) (hg : ) (h : ∀ (R : Type u) [_Rcr : ] (x : ) (n : ), (f x) = (g x)) (R : Type u) [_Rcr : ] (x : ) :
f x = g x
instance WittVector.IsPoly.comp {p : } {g : R : Type u_3⦄ → [inst : ] → } {f : R : Type u_3⦄ → [inst : ] → } [hg : ] [hf : ] :
WittVector.IsPoly p fun (R : Type u_3) (_Rcr : ) => g f

The composition of polynomial functions is polynomial.

Equations
• =
class WittVector.IsPoly₂ (p : ) (f : R : Type u_3⦄ → [inst : ] → ) :

A binary function f : Π R, 𝕎 R → 𝕎 R → 𝕎 R on Witt vectors is said to be polynomial if there is a family of polynomials φₙ over ℤ such that the nth coefficient of f x y is given by evaluating φₙ at the coefficients of x and y.

See also WittVector.IsPoly for the unary variant.

The ghost_calc tactic makes use of the IsPoly and IsPoly₂ typeclass and its instances. (In Lean 3, there was an @[is_poly] attribute to manage these instances, because typeclass resolution did not play well with function composition. This no longer seems to be an issue, so that such instances can be defined directly.)

Instances
theorem WittVector.IsPoly₂.poly {p : } {f : R : Type u_3⦄ → [inst : ] → } [self : ] :
∃ (φ : MvPolynomial (Fin 2 × ) ), ∀ ⦃R : Type u_3⦄ [inst : ] (x y : ), (f x y).coeff = fun (n : ) => WittVector.peval (φ n) ![x.coeff, y.coeff]
instance WittVector.IsPoly₂.comp {p : } {h : R : Type u_3⦄ → [inst : ] → } {f : R : Type u_3⦄ → [inst : ] → } {g : R : Type u_3⦄ → [inst : ] → } [hh : ] [hf : ] [hg : ] :
WittVector.IsPoly₂ p fun (R : Type u_3) (_Rcr : ) (x y : ) => h (f x) (g y)

The composition of polynomial functions is polynomial.

Equations
• =
instance WittVector.IsPoly.comp₂ {p : } {g : R : Type u_3⦄ → [inst : ] → } {f : R : Type u_3⦄ → [inst : ] → } [hg : ] [hf : ] :
WittVector.IsPoly₂ p fun (R : Type u_3) (_Rcr : ) (x y : ) => g (f x y)

The composition of a polynomial function with a binary polynomial function is polynomial.

Equations
• =
instance WittVector.IsPoly₂.diag {p : } {f : R : Type u_3⦄ → [inst : ] → } [hf : ] :
WittVector.IsPoly p fun (R : Type u_3) (_Rcr : ) (x : ) => f x x

The diagonal fun x ↦ f x x of a polynomial function f is polynomial.

Equations
• =
instance WittVector.negIsPoly {p : } [Fact p.Prime] :
WittVector.IsPoly p fun (R : Type u_3) (x : ) => Neg.neg

The additive negation is a polynomial function on Witt vectors.

Equations
• =
instance WittVector.zeroIsPoly {p : } [Fact p.Prime] :
WittVector.IsPoly p fun (x : Type u_3) (x_1 : ) (x_2 : ) => 0

The function that is constantly zero on Witt vectors is a polynomial function.

Equations
• =
@[simp]
theorem WittVector.bind₁_zero_wittPolynomial {p : } {R : Type u} [] [Fact p.Prime] (n : ) :
() = 0

The coefficients of 1 : 𝕎 R as polynomials.

Equations
• = if n = 0 then 1 else 0
Instances For
@[simp]
theorem WittVector.bind₁_onePoly_wittPolynomial {p : } [hp : Fact p.Prime] (n : ) :
instance WittVector.oneIsPoly {p : } [Fact p.Prime] :
WittVector.IsPoly p fun (x : Type u_3) (x_1 : ) (x_2 : ) => 1

The function that is constantly one on Witt vectors is a polynomial function.

Equations
• =
instance WittVector.addIsPoly₂ {p : } [Fact p.Prime] :
WittVector.IsPoly₂ p fun (x : Type u_3) (x_1 : ) (x_2 x_3 : ) => x_2 + x_3

Addition of Witt vectors is a polynomial function.

Equations
• =
instance WittVector.mulIsPoly₂ {p : } [Fact p.Prime] :
WittVector.IsPoly₂ p fun (x : Type u_3) (x_1 : ) (x_2 x_3 : ) => x_2 * x_3

Multiplication of Witt vectors is a polynomial function.

Equations
• =
theorem WittVector.IsPoly.map {p : } {R : Type u} {S : Type u} [] [] [Fact p.Prime] {f : R : Type u⦄ → [inst : ] → } (hf : ) (g : R →+* S) (x : ) :
() (f x) = f (() x)
instance WittVector.IsPoly₂.instInhabitedHAdd {p : } [Fact p.Prime] :
Inhabited (WittVector.IsPoly₂ p fun (x : Type u_3) (x_1 : ) (x_2 x_3 : ) => x_2 + x_3)
Equations
• WittVector.IsPoly₂.instInhabitedHAdd = { default := }
theorem WittVector.IsPoly₂.compLeft {p : } {g : R : Type u_3⦄ → [inst : ] → } {f : R : Type u_3⦄ → [inst : ] → } [] [] :
WittVector.IsPoly₂ p fun (_R : Type u_3) (_Rcr : CommRing _R) (x y : WittVector p _R) => g (f x) y

The composition of a binary polynomial function with a unary polynomial function in the first argument is polynomial.

theorem WittVector.IsPoly₂.compRight {p : } {g : R : Type u_3⦄ → [inst : ] → } {f : R : Type u_3⦄ → [inst : ] → } [] [] :
WittVector.IsPoly₂ p fun (_R : Type u_3) (_Rcr : CommRing _R) (x y : WittVector p _R) => g x (f y)

The composition of a binary polynomial function with a unary polynomial function in the second argument is polynomial.

theorem WittVector.IsPoly₂.ext {p : } [Fact p.Prime] {f : R : Type u⦄ → [inst : ] → } {g : R : Type u⦄ → [inst : ] → } (hf : ) (hg : ) (h : ∀ (R : Type u) [_Rcr : ] (x y : ) (n : ), (f x y) = (g x y)) (R : Type u) [_Rcr : ] (x : ) (y : ) :
f x y = g x y
theorem WittVector.IsPoly₂.map {p : } {R : Type u} {S : Type u} [] [] [Fact p.Prime] {f : R : Type u⦄ → [inst : ] → } (hf : ) (g : R →+* S) (x : ) (y : ) :
() (f x y) = f (() x) (() y)

A macro for a common simplification when rewriting with ghost component equations.

Equations
• One or more equations did not get rendered due to their size.
Instances For

ghost_calc is a tactic for proving identities between polynomial functions. Typically, when faced with a goal like

∀ (x y : 𝕎 R), verschiebung (x * frobenius y) = verschiebung x * y


you can

1. call ghost_calc
2. do a small amount of manual work -- maybe nothing, maybe rintro, etc
3. call ghost_simp

and this will close the goal.

ghost_calc cannot detect whether you are dealing with unary or binary polynomial functions. You must give it arguments to determine this. If you are proving a universally quantified goal like the above, call ghost_calc _ _. If the variables are introduced already, call ghost_calc x y. In the unary case, use ghost_calc _ or ghost_calc x.

ghost_calc is a light wrapper around type class inference. All it does is apply the appropriate extensionality lemma and try to infer the resulting goals. This is subtle and Lean's elaborator doesn't like it because of the HO unification involved, so it is easier (and prettier) to put it in a tactic script.

Equations
• One or more equations did not get rendered due to their size.
Instances For