Sums of squares - Basic properties of lists
Copyright (c) 2023 Matematiflo. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Authors: Florent Schaffhauser.
import SumSq.Defs
import Batteries.Data.List.Basic
import Mathlib.Data.List.Perm
import Mathlib.Tactic.FieldSimp
import Mathlib.Data.Multiset.Basic
#check Multiset
#check List.Perm
Appended lists
Recall from #List.append that L1 ++ L2
(which is notation for List.append L1 L2
) is defined as follows, by pattern matching on L1
(see Init.Data.List.Basic for details).
def List.append : (L1 L2 : List R) → List R
| [], L2 => L2
| a::l1, L2 => a :: List.append l1 L2
We now prove that the sum of squares of the list L1 ++ L2
is equal to the sum of squares of L1
plus the sum of squares of L2
.
SumSq (L1 ++ L2) = SumSq L1 + SumSq L2
theorem SumSqAppend {R : Type} [Semiring R] (L1 L2 : List R) : SumSq (L1 ++ L2) = SumSq L1 + SumSq L2 := by
induction L1 with -- we prove the result by induction on the list L1
| nil => -- case when L1 is the empty list
simp only [SumSq, zero_add, List.nil_append L2] -- since [] ++ L2 = L2 by definition of ++, the result follows by definition of SumSq
| cons a l1 ih => -- case when L1 = (a :: L)
simp [SumSq] -- by [`List.cons_append](https://leanprover-community.github.io/mathlib4_docs/Init/Data/List/Basic.html#List.cons_append) (a :: L) ++ L2 = a :: (L ++ L2), so SumSq (a :: (L ++ L2)) = a ^ 2 + SumSq (L ++ L2)
rw [ih] -- recall that ih : SumSq (L ++ L2) = SumSq L + SumSq L2
rw [add_assoc] -- the two terms are now equal up to associativity of addition
Permuted lists
Recall from #List.Perm that the relation L1 ~ L2
(which is notation for List.Perm L1 L2
) is defined inductively using pairwise swaps.
inductive Perm : List R → List R → Prop
| nil : Perm [] []
| cons (a : R) {l₁ l₂ : List R} : Perm l₁ l₂ → Perm (a :: l₁) (a :: l₂)
| swap (a₁ a₂ : R) (l : List R) : Perm (a₂ :: a₁ :: l) (a₁ :: a₂ :: l)
| trans {L₁ L₂ L₃ : List R} : Perm L₁ L₂ → Perm L₂ L₃ → Perm L₁ L₃
So we wee that List.Perm
is an inductive type: a list L2
is a permutation of the list L1
if and only if one of four cases occurs.
We can now prove that a sum of squares is invariant under permutations:
L1 ~ L2 → SumSq L1 = SumSq L2
Note that, since List.Perm
uses implicit variables for the constructors cons
and trans
, we choose to write the proof by indution with a slighty different syntax (using case
), to make the Lean infoview rendering more readable (for more on this, see Exercise 1 below).
theorem SumSqPermut {R : Type} [Semiring R] {L1 L2 : List R} (H : L1.Perm L2) : SumSq L1 = SumSq L2 := by
induction H -- we prove the result by induction on `H`, which is a term of type `L1 ~ L2` (and the latter is indeed inductively defined)
· case nil => -- case when L1 L2 are both empty
rfl -- equality holds by definition
· case cons x l1 l2 Hl Sum12 => -- case when L1 = (x :: l1) and L2 = (x :: l2) with l1 ~ l2
simp only [SumSq] -- by definition, SumSq (x :: lj) = x ^ 2 + SumSq lj for j = 1, 2
rw [Sum12] -- by induction, SumSq l1 = SumSq l2
· case swap a b L => -- case when L1 = (b :: (a :: L)) and L2 = (a :: (b :: L))
simp only [SumSq] -- by definition, SumSq (b :: (a :: L)) = b ^ 2 + (a ^ 2 + SumSq L)
rw [← add_assoc, ← add_assoc, add_comm (b ^ 2) (a ^ 2)] -- the two expressions are equal in R
· case trans L1 L L2 H1 H2 Sum1 Sum2 => -- case when L1 ~ L and L ~ L2
rw [Sum1, Sum2] -- by induction SumSq L1 = SumSq L and SumSq L = SumSq L2
Erasing an entry
The function List.erase
can erase an entry of a list. It is defined as follows in #List.erase.
def List.erase {R : Type} [BEq R] : List R → R → List R
| [], _ => []
| a::l, b => match a == b with
| true => l
| false => a :: List.erase l b
Note that this declaration uses Boolean equality. It could be written using decidable equality, in the following way.
def List.erase' {R : Type} [DecidableEq R] : List R → R → List R
| [], _ => []
| a::l, b => if a = b then l else List.erase' l b
Whichever definition of erase
we choose, we need to assume that the type R
has decidable equality in order to be able to use it (and the same goes for the function #List.perm_cons_erase
, also used below).
We now prove that, if a term a : R
is an entry of a list L : List R
, then we can compute SumSq L
from a
and SumSq (L.erase a)
. More precisely:
a ∈ L → SumSq L = a ^ 2 + SumSq (L.erase a)
The type a ∈ L
is defined in #List.Mem. It is an inductive type: a term a : R
belongs to a list L : List R
if and only if L = (a :: l)
or L = (b :: l)
with b ∈ l
.
theorem SumSqErase {R : Type} [Semiring R] [DecidableEq R] (L : List R) (a : R) (h : a ∈ L) : SumSq L = a ^ 2 + SumSq (L.erase a) := by
have H : L.Perm (a :: (L.erase a)) := L.perm_cons_erase h -- this is the Mathlib proof that, if a ∈ L, then L ~ (a :: (L.erase a)), see also the exercises section below
rw [SumSqPermut H] -- since we have a proof that L ~ (a :: (L.erase a)), we can use the SumSq_permut function that we defined earlier to conclude that the two sums of squares are equal
rw [SumSq] -- the last simplification follows by definition of `SumSq`
Multiplication by a scalar
Let L
be a list with entries in a semiring R
. If c
is a term of type R
, we can multiply each entry of L
by c
to define a new list, that we shall denote c • L
. For instance, if L = [x, y, z]
, we should have:
c • [x, y, z] = [c * x, c * y, c * z]
Let us define this formally and take a look at a few examples. As we shall see, one has to be precise in the notation, in order for Lean to interpret the command correctly.
def ListSmul {R : Type} [Semiring R] (c : R) : List R → List R
| [] => []
| a :: l => (c * a) :: ListSmul c l
infixl:50 " • " => ListSmul
theorem ListSmulMap {R : Type} [Semiring R] (c : R) (L : List R) : (c • L) = L.map (c * .) := by
induction L with
| nil => simp [ListSmul] -- the case of the empty list is trivial
| cons a l ih => simp [ListSmul, ih] -- the case of a list of the form (a :: l) reduces immediately to the induction hypothesis
#eval ListSmul 2 [1, -2, 3] --[2, -4, 6]
#eval ((2 : ℤ) • [1, -2, 3])
example : ListSmul 2 [1, -2, 3] = [2, -4, 6] := by rfl
#eval SumSq (ListSmul 2 [1, -2, 3]) -- 56
#eval SumSq ((2 : ℤ) • [1, -2, 3]) -- 56
example : SumSq (ListSmul 2 [1, -2, 3]) = 4 * SumSq [1, -2, 3] := by rfl
#eval SumSq [2, -4, 6]
#eval 4 * SumSq [1, -2, 3] -- 56
example (a x y : ℚ) : (ListSmul a [x, y]) = [a * x, a * y] := by rfl
example (a x y : ℚ) : SumSq (ListSmul a [x, y]) = a ^ 2 * SumSq [x, y] := by simp [SumSq, mul_pow, mul_add, ListSmul]
The result we expect is then the following:
SumSq (c • L) = c ^ 2 * SumSq L
We will see below that this result holds when R
is a commutative semiring.
Indeed, in order to be able to apply lemmas such as #mul_pow
in the proof, we need to assume that the semiring R
is commutative.
Before proving the result that is relevant result, we prove an easier one, of possibly greater general interest, with SumSq
replaced by List.sum
.
theorem SumSmul {R : Type} [Semiring R] (c : R) (L : List R) : List.sum (c • L) = c * (List.sum L) := by
induction L with
| nil => simp only [ListSmul, List.sum_nil, mul_zero]
| cons a l ih => simp [ListSmul, mul_add, ih]
theorem SumSqSmul {R : Type} [CommSemiring R] (c : R) (L : List R) : SumSq (c • L) = c ^ 2 * SumSq L := by
induction L with -- we prove the result by induction on L
| nil => simp only [SumSq, mul_zero, ListSmul] -- the case of the empty list is trivial
| cons a l ih => simp only [ListSmul, SumSq, mul_add, ih, mul_pow] -- the case of a list of the form (a :: l) follows from simplifications and the use of the induction hypothesis
If we assume that the semiring R
is in fact a semifield, then we can also consider the list from L
obtained by dividing each entry by a term c
such that `c ≠ 0’.
theorem SumSqDiv {F : Type} [Semifield F]
(L : List F) (c : F) : SumSq (L.map (. / c)) = (1 / c ^ 2) * SumSq L := by
-- this will be an application of mul_sum_sq2, using the fact that . / c = . * c⁻¹
have aux : (fun x => x / c) = (fun x => c⁻¹ * x) := by field_simp
simp only [aux, ← ListSmulMap, SumSqSmul]
field_simp
Note that no assumption (hc : c ≠ 0)
has been used because Lean gives a value to division by c
in F
even if c = 0
and that the equality remains true in this case.
example [Semifield F] (x : F) : x / 0 = 0 := by field_simp
More computations
Before moving on to the exercises, we give another proof of theorem SumSqSmul
, seen in the section on Multiplication by a scalar.
It is a direct, more computational proof, harder to follow than the original proof (by induction).
lemma SumSmul2 {R : Type} [Semiring R] (c : R) (L : List R) : (L.map (c * .)).sum = c * L.sum := by
induction L with
| nil => simp
| cons a l ih => simp [mul_add, ih]
theorem SumSqSmul2 {R : Type} [CommSemiring R] (L : List R) (c : R) : ((L.map (c * .)).map (. ^2)).sum = c ^ 2 * (L.map (. ^ 2)).sum := by
simp [mul_add, mul_pow, ← SumSmul2] -- we simplify the statement
have aux : ((fun x => x ^ 2) ∘ fun x => c * x) = ((fun x => c ^ 2 * x) ∘ fun x => x ^ 2)
:= by simp [Function.funext_iff, mul_pow] -- we prove an auxiliary result in order to incorporate it in the goal
rw [aux]
Exercises
Exercise 1
Modify the syntax of the induction
tactic in SumSqPermut
to make it look more similar to that of SumSqAppend
. This means: in SumSqPermut
, replace induction H
by induction H with
and make the proof syntactically correct after that (start by changing ⬝ case nil
to | nil
).
Exercise 2
Let R
be a type with decidable equality. Let a
be a term of type R
and let L
be a term of type List R
. Prove that, if a ∈ L
, then the list a :: L.erase a
is a permutation of L
(we have used this standard result here).
Exercise 3
Prove that the statement of theorem SumSqSmul2
is indeed equivalent to the statement of theorem SumSqSmul
.