/-
Copyright (c) 2020 Anne Baanen. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Anne Baanen
-/
import data.matrix.basic
import data.fin.vec_notation
import tactic.fin_cases

/-!
# Matrix and vector notation

This file includes `simp` lemmas for applying operations in `data.matrix.basic` to values built out
of the matrix notation `![a, b] = vec_cons a (vec_cons b vec_empty)` defined in
`data.fin.vec_notation`.

## Implementation notes

The `simp` lemmas require that one of the arguments is of the form `vec_cons _ _`.
This ensures `simp` works with entries only when (some) entries are already given.
In other words, this notation will only appear in the output of `simp` if it
already appears in the input.

## Notations

We reuse notation `![a, b]` for `vec_cons a (vec_cons b vec_empty)`. It is a localized notation in
the `matrix` locale.

## Examples

Examples of usage can be found in the `test/matrix.lean` file.
-/

namespace matrix

universe u
variables {α : Type u} {o n m : ℕ} {m' n' o' : Type*}

open_locale matrix

/-- Use `![...]` notation for displaying a `fin`-indexed matrix, for example:

```
#eval ![![1, 2], ![3, 4]] + ![![3, 4], ![5, 6]] -- ![![4, 6], ![8, 10]]
```
-/
instance [has_repr α] : has_repr (matrix (fin m) (fin n) α) := pi_fin.has_repr

@[simp] lemma cons_val' (v : n' → α) (B : matrix (fin m) n' α) (i j) :
  vec_cons v B i j = vec_cons (v j) (λ i, B i j) i :=
by { refine fin.cases _ _ i; simp }

@[simp] lemma head_val' (B : matrix (fin m.succ) n' α) (j : n') :
  vec_head (λ i, B i j) = vec_head B j := rfl

@[simp] lemma tail_val' (B : matrix (fin m.succ) n' α) (j : n') :
  vec_tail (λ i, B i j) = λ i, vec_tail B i j :=
by { ext, simp [vec_tail] }

section dot_product

variables [add_comm_monoid α] [has_mul α]

@[simp] lemma dot_product_empty (v w : fin 0 → α) :
  dot_product v w = 0 := finset.sum_empty

@[simp] lemma cons_dot_product (x : α) (v : fin n → α) (w : fin n.succ → α) :
  dot_product (vec_cons x v) w = x * vec_head w + dot_product v (vec_tail w) :=
by simp [dot_product, fin.sum_univ_succ, vec_head, vec_tail]

@[simp] lemma dot_product_cons (v : fin n.succ → α) (x : α) (w : fin n → α) :
  dot_product v (vec_cons x w) = vec_head v * x + dot_product (vec_tail v) w :=
by simp [dot_product, fin.sum_univ_succ, vec_head, vec_tail]

@[simp] lemma cons_dot_product_cons (x : α) (v : fin n → α) (y : α) (w : fin n → α) :
  dot_product (vec_cons x v) (vec_cons y w) = x * y + dot_product v w :=
by simp

end dot_product

section col_row

@[simp] lemma col_empty (v : fin 0 → α) : col v = vec_empty :=
empty_eq _

@[simp] lemma col_cons (x : α) (u : fin m → α) :
  col (vec_cons x u) = vec_cons (λ _, x) (col u) :=
by { ext i j, refine fin.cases _ _ i; simp [vec_head, vec_tail] }

@[simp] lemma row_empty : row (vec_empty : fin 0 → α) = λ _, vec_empty :=
by { ext, refl }

@[simp] lemma row_cons (x : α) (u : fin m → α) :
  row (vec_cons x u) = λ _, vec_cons x u :=
by { ext, refl }

end col_row

section transpose

@[simp] lemma transpose_empty_rows (A : matrix m' (fin 0) α) : Aᵀ = ![] := empty_eq _

@[simp] lemma transpose_empty_cols : (![] : matrix (fin 0) m' α)ᵀ = λ i, ![] :=
funext (λ i, empty_eq _)

@[simp] lemma cons_transpose (v : n' → α) (A : matrix (fin m) n' α) :
  (vec_cons v A)ᵀ = λ i, vec_cons (v i) (Aᵀ i) :=
by { ext i j, refine fin.cases _ _ j; simp }

@[simp] lemma head_transpose (A : matrix m' (fin n.succ) α) : vec_head (Aᵀ) = vec_head ∘ A :=
rfl

@[simp] lemma tail_transpose (A : matrix m' (fin n.succ) α) : vec_tail (Aᵀ) = (vec_tail ∘ A)ᵀ :=
by { ext i j, refl }

end transpose

section mul

variables [semiring α]

@[simp] lemma empty_mul [fintype n'] (A : matrix (fin 0) n' α) (B : matrix n' o' α) :
  A ⬝ B = ![] :=
empty_eq _

@[simp] lemma empty_mul_empty (A : matrix m' (fin 0) α) (B : matrix (fin 0) o' α) :
  A ⬝ B = 0 :=
rfl

@[simp] lemma mul_empty [fintype n'] (A : matrix m' n' α) (B : matrix n' (fin 0) α) :
  A ⬝ B = λ _, ![] :=
funext (λ _, empty_eq _)

lemma mul_val_succ [fintype n']
  (A : matrix (fin m.succ) n' α) (B : matrix n' o' α) (i : fin m) (j : o') :
  (A ⬝ B) i.succ j = (vec_tail A ⬝ B) i j := rfl

@[simp] lemma cons_mul [fintype n'] (v : n' → α) (A : matrix (fin m) n' α) (B : matrix n' o' α) :
  vec_cons v A ⬝ B = vec_cons (vec_mul v B) (A  ⬝ B) :=
by { ext i j, refine fin.cases _ _ i, { refl }, simp [mul_val_succ] }

end mul

section vec_mul

variables [semiring α]

@[simp] lemma empty_vec_mul (v : fin 0 → α) (B : matrix (fin 0) o' α) :
  vec_mul v B = 0 :=
rfl

@[simp] lemma vec_mul_empty [fintype n'] (v : n' → α) (B : matrix n' (fin 0) α) :
  vec_mul v B = ![] :=
empty_eq _

@[simp] lemma cons_vec_mul (x : α) (v : fin n → α) (B : matrix (fin n.succ) o' α) :
  vec_mul (vec_cons x v) B = x • (vec_head B) + vec_mul v (vec_tail B) :=
by { ext i, simp [vec_mul] }

@[simp] lemma vec_mul_cons (v : fin n.succ → α) (w : o' → α) (B : matrix (fin n) o' α) :
  vec_mul v (vec_cons w B) = vec_head v • w + vec_mul (vec_tail v) B :=
by { ext i, simp [vec_mul] }

end vec_mul

section mul_vec

variables [semiring α]

@[simp] lemma empty_mul_vec [fintype n'] (A : matrix (fin 0) n' α) (v : n' → α) :
  mul_vec A v = ![] :=
empty_eq _

@[simp] lemma mul_vec_empty (A : matrix m' (fin 0) α) (v : fin 0 → α) :
  mul_vec A v = 0 :=
rfl

@[simp] lemma cons_mul_vec [fintype n'] (v : n' → α) (A : fin m → n' → α) (w : n' → α) :
  mul_vec (vec_cons v A) w = vec_cons (dot_product v w) (mul_vec A w) :=
by { ext i, refine fin.cases _ _ i; simp [mul_vec] }

@[simp] lemma mul_vec_cons {α} [comm_semiring α] (A : m' → (fin n.succ) → α) (x : α)
  (v : fin n → α) :
  mul_vec A (vec_cons x v) = (x • vec_head ∘ A) + mul_vec (vec_tail ∘ A) v :=
by { ext i, simp [mul_vec, mul_comm] }

end mul_vec

section vec_mul_vec

variables [semiring α]

@[simp] lemma empty_vec_mul_vec (v : fin 0 → α) (w : n' → α) :
  vec_mul_vec v w = ![] :=
empty_eq _

@[simp] lemma vec_mul_vec_empty (v : m' → α) (w : fin 0 → α) :
  vec_mul_vec v w = λ _, ![] :=
funext (λ i, empty_eq _)

@[simp] lemma cons_vec_mul_vec (x : α) (v : fin m → α) (w : n' → α) :
  vec_mul_vec (vec_cons x v) w = vec_cons (x • w) (vec_mul_vec v w) :=
by { ext i, refine fin.cases _ _ i; simp [vec_mul_vec] }

@[simp] lemma vec_mul_vec_cons (v : m' → α) (x : α) (w : fin n → α) :
  vec_mul_vec v (vec_cons x w) = λ i, v i • vec_cons x w :=
by { ext i j, rw [vec_mul_vec, pi.smul_apply, smul_eq_mul] }

end vec_mul_vec

section smul

variables [semiring α]

@[simp] lemma smul_mat_empty {m' : Type*} (x : α) (A : fin 0 → m' → α) : x • A = ![] := empty_eq _

@[simp] lemma smul_mat_cons (x : α) (v : n' → α) (A : matrix (fin m) n' α) :
  x • vec_cons v A = vec_cons (x • v) (x • A) :=
by { ext i, refine fin.cases _ _ i; simp }

end smul

section minor

@[simp] lemma minor_empty (A : matrix m' n' α) (row : fin 0 → m') (col : o' → n') :
  minor A row col = ![] :=
empty_eq _

@[simp] lemma minor_cons_row (A : matrix m' n' α) (i : m') (row : fin m → m') (col : o' → n') :
  minor A (vec_cons i row) col = vec_cons (λ j, A i (col j)) (minor A row col) :=
by { ext i j, refine fin.cases _ _ i; simp [minor] }

end minor

section vec2_and_vec3

section one

variables [has_zero α] [has_one α]

lemma one_fin_two : (1 : matrix (fin 2) (fin 2) α) = ![![1, 0], ![0, 1]] :=
by { ext i j, fin_cases i; fin_cases j; refl }

lemma one_fin_three : (1 : matrix (fin 3) (fin 3) α) = ![![1, 0, 0], ![0, 1, 0], ![0, 0, 1]] :=
by { ext i j, fin_cases i; fin_cases j; refl }

end one

lemma mul_fin_two [add_comm_monoid α] [has_mul α] (a₁₁ a₁₂ a₂₁ a₂₂ b₁₁ b₁₂ b₂₁ b₂₂ : α) :
  ![![a₁₁, a₁₂],
    ![a₂₁, a₂₂]] ⬝ ![![b₁₁, b₁₂],
                     ![b₂₁, b₂₂]] = ![![a₁₁ * b₁₁ + a₁₂ * b₂₁, a₁₁ * b₁₂ + a₁₂ * b₂₂],
                                      ![a₂₁ * b₁₁ + a₂₂ * b₂₁, a₂₁ * b₁₂ + a₂₂ * b₂₂]] :=
begin
  ext i j,
  fin_cases i; fin_cases j; simp [matrix.mul, dot_product, fin.sum_univ_succ]
end

lemma mul_fin_three [add_comm_monoid α] [has_mul α]
  (a₁₁ a₁₂ a₁₃ a₂₁ a₂₂ a₂₃ a₃₁ a₃₂ a₃₃ b₁₁ b₁₂ b₁₃ b₂₁ b₂₂ b₂₃ b₃₁ b₃₂ b₃₃ : α) :
  ![![a₁₁, a₁₂, a₁₃],
    ![a₂₁, a₂₂, a₂₃],
    ![a₃₁, a₃₂, a₃₃]] ⬝ ![![b₁₁, b₁₂, b₁₃],
                          ![b₂₁, b₂₂, b₂₃],
                          ![b₃₁, b₃₂, b₃₃]] =
  ![![a₁₁*b₁₁ + a₁₂*b₂₁ + a₁₃*b₃₁, a₁₁*b₁₂ + a₁₂*b₂₂ + a₁₃*b₃₂, a₁₁*b₁₃ + a₁₂*b₂₃ + a₁₃*b₃₃],
    ![a₂₁*b₁₁ + a₂₂*b₂₁ + a₂₃*b₃₁, a₂₁*b₁₂ + a₂₂*b₂₂ + a₂₃*b₃₂, a₂₁*b₁₃ + a₂₂*b₂₃ + a₂₃*b₃₃],
    ![a₃₁*b₁₁ + a₃₂*b₂₁ + a₃₃*b₃₁, a₃₁*b₁₂ + a₃₂*b₂₂ + a₃₃*b₃₂, a₃₁*b₁₃ + a₃₂*b₂₃ + a₃₃*b₃₃]] :=
begin
  ext i j,
  fin_cases i; fin_cases j; simp [matrix.mul, dot_product, fin.sum_univ_succ, ←add_assoc],
end

lemma vec2_eq {a₀ a₁ b₀ b₁ : α} (h₀ : a₀ = b₀) (h₁ : a₁ = b₁) :
  ![a₀, a₁] = ![b₀, b₁] :=
by subst_vars

lemma vec3_eq {a₀ a₁ a₂ b₀ b₁ b₂ : α} (h₀ : a₀ = b₀) (h₁ : a₁ = b₁) (h₂ : a₂ = b₂) :
  ![a₀, a₁, a₂] = ![b₀, b₁, b₂] :=
by subst_vars

lemma vec2_add [has_add α] (a₀ a₁ b₀ b₁ : α) :
  ![a₀, a₁] + ![b₀, b₁] = ![a₀ + b₀, a₁ + b₁] :=
by rw [cons_add_cons, cons_add_cons, empty_add_empty]

lemma vec3_add [has_add α] (a₀ a₁ a₂ b₀ b₁ b₂ : α) :
  ![a₀, a₁, a₂] + ![b₀, b₁, b₂] = ![a₀ + b₀, a₁ + b₁, a₂ + b₂] :=
by rw [cons_add_cons, cons_add_cons, cons_add_cons, empty_add_empty]

lemma smul_vec2 {R : Type*} [has_scalar R α] (x : R) (a₀ a₁ : α) :
  x • ![a₀, a₁] = ![x • a₀, x • a₁] :=
by rw [smul_cons, smul_cons, smul_empty]

lemma smul_vec3 {R : Type*} [has_scalar R α] (x : R) (a₀ a₁ a₂ : α) :
  x • ![a₀, a₁, a₂] = ![x • a₀, x • a₁, x • a₂] :=
by rw [smul_cons, smul_cons, smul_cons, smul_empty]

variables [add_comm_monoid α] [has_mul α]

lemma vec2_dot_product' {a₀ a₁ b₀ b₁ : α} :
  ![a₀, a₁] ⬝ᵥ ![b₀, b₁] = a₀ * b₀ + a₁ * b₁ :=
by rw [cons_dot_product_cons, cons_dot_product_cons, dot_product_empty, add_zero]

@[simp] lemma vec2_dot_product (v w : fin 2 → α) :
  v ⬝ᵥ w = v 0 * w 0 + v 1 * w 1 :=
vec2_dot_product'

lemma vec3_dot_product' {a₀ a₁ a₂ b₀ b₁ b₂ : α} :
  ![a₀, a₁, a₂] ⬝ᵥ ![b₀, b₁, b₂] = a₀ * b₀ + a₁ * b₁ + a₂ * b₂ :=
by rw [cons_dot_product_cons, cons_dot_product_cons, cons_dot_product_cons,
       dot_product_empty, add_zero, add_assoc]

@[simp] lemma vec3_dot_product (v w : fin 3 → α) :
  v ⬝ᵥ w = v 0 * w 0 + v 1 * w 1 + v 2 * w 2 :=
vec3_dot_product'

end vec2_and_vec3

end matrix


/- Copyright (c) 2022 Sina Hazratpour. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
----------------

# Algebraic Structures on Gaussian Integers
Sina Hazratpour
Introduction to Proof
MATH 301, Johns Hopkins University, Fall 2022
-/

import ..prooflab
import .lec12_gaussian_integers

import data.int.basic

namespace PROOFS
namespace STR

universe u

class comm_mult_monoid_str (M : Type u) extends mult_monoid_str M :=
(mul_comm : ∀ x y : M, x * y = y * x)

instance : comm_mult_monoid_str ℤ :=
{
  mul_comm := int.mul_comm,
}

class comm_additive_monoid_str (M : Type u) extends additive_monoid_str M :=
(add_comm : ∀ x y : M, x + y = y + x)

namespace gaussian_int

instance : comm_mult_monoid_str ℤ[i] :=
{
  mul_comm := gaussian_int.mul_comm,
}

#check has_int_cast

--Defined in mathlib
-- class has_int_cast (R : Type u) :=
-- (int_cast : ℤ → R)

@[simp]
instance : has_int_cast ℤ[i] := ⟨ λ n, ⟨n, 0⟩  ⟩

-- @[protect_proj]
-- class has_nat_cast (R : Type u) :=
-- (nat_cast : ℕ → R)
@[simp]
instance : has_nat_cast ℤ[i] := ⟨λ n,  ⟨n , 0⟩⟩ -- this works because of the coercion of nat to int

#check 2 + 3

#check (2 : ℤ[i]) + 3
#eval (2 : ℤ[i]) + 3

@[simp]
lemma int_cast_re {n : ℤ} :
  (n : ℤ[i]).re = n :=  -- proving an equality between integers
begin
  refl,
end

@[simp]
lemma int_cast_im {n : ℤ} :
  (n : ℤ[i]).im = 0 :=
begin
  refl,
end

@[simp]
lemma nat_cast_re {n : ℕ} :
  (n : ℤ[i]).re = n :=
begin
  refl,
end

@[simp]
lemma nat_cast_im {n : ℕ} :
  (n : ℤ[i]).im = 0 :=
begin
  refl,
end

@[simp]
lemma int_cast_mul {m n : ℤ} :
 ((m * n) : ℤ[i]).re = m * n :=
begin
  simp,
end

@[simp]
lemma mul_int_cast {m n : ℤ} :
 (m : ℤ[i]) * (n : ℤ[i]).re = m * n :=
begin
  refl,
end

end gaussian_int

/- Now we have all the tools to prove that the function `int_cast` is indeed a multiplicative monoid morphism.-/

@[ext]
class mult_monoid.morphism (M : Type u) (N : Type u) [mult_monoid_str M] [mult_monoid_str N] :=
(to_fun : M → N) -- f : M → N -- the underlying function of morphism
(resp_one : to_fun 1 = 1) -- f (1_M ) = 1_N
(resp_mul : ∀ x y : M, to_fun (x * y) = to_fun x * to_fun y) -- f(x *_M y) = (f x) *_N (f y)

#check mult_monoid.morphism

infixr ` →ₘ* `:25 := mult_monoid.morphism

#check has_coe_to_sort

@[class]
structure mult_monoid  :=
(carrier : Type u)
(str : mult_monoid_str carrier)

def bundle_up (M : Type u)[mult_monoid_str M] : mult_monoid :=
{
  carrier := M,
  str := by apply_instance,
}

#check bundle_up

#check has_coe_to_fun

variables {M N : Type u} [mult_monoid_str M] [mult_monoid_str N]

instance mult_monoid_to_sort_coe (M : Type u) :
  has_coe (mult_monoid_str M) (Type u)  :=
⟨coe := M⟩

instance : has_coe_to_fun  (M →ₘ* N)  (λ _, M → N) :=
⟨λ f, f.to_fun ⟩  --defined based on the projection `.to_fun` of monoid morphism to function

-- if we have a function between the underlying types of two multiplicative monoids and a proof that the function preserves `1` and `*` , then applying the coercion of the promotion is identity (i.e. it does nothing).
@[simp] lemma coe_mk_mul_mor {f : M → N} {h_one : f 1 = 1} {h_mul: ∀ x y : M, f (x * y) = f x * f y} {x : M} :
  { mult_monoid.morphism . to_fun := f, resp_one := h_one, resp_mul := h_mul } x = f x :=
begin
  refl,
end

@[simp]
lemma coe_mul_resp (f : M →ₘ* N) (x y : M) :
  f (x *  y) = f x * f y :=
begin
  apply mult_monoid.morphism.resp_mul,
end

@[simp]
lemma coe_mul_one (f : M →ₘ* N) :
  f 1 = 1 :=
begin
  apply mult_monoid.morphism.resp_one,
end

/-
⇑f and ↥M are notations for coe_fn f and coe_sort M. They are the coercion operators for the function and sort classes.

We can instruct Lean’s pretty-printer to hide the operators ↑ and ⇑ with set_option.
-/

lemma test (f : M →ₘ* N) (a : M) :
  f ((a * a) * a) = (f a * f a) * f a :=
begin
calc
  f ((a * a) * a) = f (a * a) * f a : by
  {rw [f.resp_mul (a * a) a], }
            ... = (f a * f a) * f a : by {rw [f.resp_mul]},

end

#check @test
set_option pp.coercions false
#check @test

@[ext]
class additive_monoid.morphism (M : Type u) (N : Type u) [additive_monoid_str M] [additive_monoid_str N] :=
(to_fun : M → N) -- f : M → N -- the underlying function of morphism
(resp_zero : to_fun 0 = 0) -- f (1_M ) = 1_N
(resp_add : ∀ x y : M, to_fun (x + y) = to_fun x + to_fun y) -- f(x *_M y) = (f x) *_N (f y)

infixr ` →ₘ+ `:25 := additive_monoid.morphism
variables {A B : Type u} [additive_monoid_str A] [additive_monoid_str B]

instance : has_coe_to_fun  (A →ₘ+ B)  (λ _, A → B) :=
⟨λ f, f.to_fun ⟩  --defined based on the projection `.to_fun` of monoid morphism to function

@[simp]
lemma coe_mk_add_mor {f : A → B} {h_zero : f 0 = 0} {h_add : ∀ x y : A, f (x + y) = f x + f y} {x : A} :
  { additive_monoid.morphism . to_fun := f, resp_zero := h_zero, resp_add := h_add } x = f x :=
begin
  refl,
end

-- instance : ℤ →ₘ* ℤ[i] := -- a monoid morphism from integers to gaussian integers
-- {
--   to_fun := has_int_cast.int_cast,
--   resp_one := rfl,
--   resp_mul := by {intros x y, ext; simp,}
-- } -- we need to prove more simp lemma

namespace gaussian_int

instance int_cast_mon_morphism : ℤ →ₘ* ℤ[i] :=
{
  to_fun := λ x, x,
  resp_one := by {refl, },
  resp_mul := by {intros x y, ext; simp, }
}

end gaussian_int

 -- there can be potentially many instances since we are looking for functions ℤ → ℤ which preserve 1 : ℤ and the * of ℤ. Is there a nontrivial monoid morphism  `ℤ →ₘ* ℤ` ?
def mult_monoid_morphism_int_constant : ℤ →ₘ* ℤ :=
{
  to_fun := λ n, (1 : ℤ),
  resp_one := by {refl,},
  resp_mul := by {simp,},
}
-- Whereas we have ℤ-many constant functions ℤ → ℤ there is only one constant monoid morphism ℤ → ℤ, namely `mult_monoid_morphism_int_constant`. -- Challenge: let's prove there this claim.

def is_constant {X Y : Type u} (f : X → Y) :=
∀ x x' : X, f x = f x'

#check is_constant

lemma unique_monoid_morphism_int_constant : ∀ (f : ℤ →ₘ* ℤ),  is_constant (f.to_fun) → (f = mult_monoid_morphism_int_constant) :=
begin
  intro f, -- let's fix a monoid morphism f : ℤ →ₘ* ℤ
  intro hf, -- a hypothesis that the underlying function of `f` is a constant function
  let f₀ := f.to_fun,
  rw (rfl : f.to_fun = f₀) at hf,
  unfold is_constant at hf,
  ext, -- the extensionality of monoid morphism structure states that in order to prove two morphisms are the same it suffices to prove their underlying functions are the same. Lean automatically applies the function ext as well.
  sorry,
end

namespace gaussian_int
-- conjugate of a Gaussian integers
@[simp]
def conj (z : ℤ[i]) : ℤ[i] := ⟨z.re, -z.im⟩

#check conj

#eval conj ⟨0 , 5⟩
#eval conj (⟨17, 6⟩ * ⟨4 , 6⟩)
#eval conj ⟨17, 6⟩ * (conj ⟨4, 6⟩)
#eval conj 1

/- We show that conjugation gives rise to a multiplicative monoid morphism. -/

def conj_mon_map : ℤ[i] →ₘ* ℤ[i] :=
{
  to_fun := conj,
  resp_one := by {refl},
  resp_mul := by {intros z w, ext; simp, rw int.add_comm,}
}

#eval conj_mon_map.to_fun ⟨2,1⟩
#eval conj_mon_map ⟨2,1⟩ -- eval works only for functions.  -- this works now due to coercion `has_coe_to_fun` for the type `M →ₘ* N` declared above.

end gaussian_int

/- Two monoids `M` and `N` are __isomorphic__ if there is a monoid morphism `f : M →ₘ* N` whose undelying function is a bijection (or equivalently, an equivalence) -/

#check is_surjective
#check is_injective

def is_bijective {X Y : Type} (f : X → Y) :=
is_surjective f ∧ is_injective f

structure mult_monoid.isomorphism (M : Type u) (N : Type u) [mult_monoid_str M] [mult_monoid_str N] :=
(mor :  M →ₘ* N)
(bij : is_bijective mor.to_fun)

infixr ` ≅ₘ* `:25 := mult_monoid.isomorphism

namespace gaussian_int

@[simp]
def idempotent {X : Type u} (f : X → X) := (f ∘ f = id)

lemma conj_conj :
  idempotent conj :=
begin
  simp,
  funext,
  simp,
  ext; refl,
end

#check auto

def conj_auto : auto (ℤ[i]) :=
{
  to_fun := conj,
  inv_fun := conj,
  left_inv := conj_conj,
  right_inv := conj_conj,
}

def conj_mon_map_iso {M : Type u} :
  ℤ[i] ≅ₘ* ℤ[i] :=
{
  mor := conj_mon_map,
  bij := by {split, sorry, sorry, },
}

end gaussian_int

/-!  In below we define the __group__ structure. A group structure on a type `X` consists of a binary operation (e.g. multiplication, addition) and a unary operation of taking __inverse__.
-/

class additive_group_str (X : Type u) extends additive_monoid_str X :=
(inv : X → X)
(left_inv : ∀ x : X,  (inv x) + x =  0)
(right_inv : ∀ x : X,  x + (inv x) = 0)

class mult_group_str (X : Type u) extends mult_monoid_str X :=
(inv : X → X)
(left_inv : ∀ x : X,  (inv x) * x =  1)
(right_inv : ∀ x : X,  x * (inv x) = 1)

-- our notation for inverse of an element.
postfix `ⁱ` :std.prec.max_plus := mult_group_str.inv

section
variables (G : Type u) [mult_group_str G] (x : G)
#check x
#check xⁱ
end

end STR
end PROOFS


/-
Copyright (c) 2017 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Mario Carneiro, Neil Strickland
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.data.nat.basic
import Mathlib.PostPort

namespace Mathlib

/-!
# The positive natural numbers

This file defines the type `ℕ+` or `pnat`, the subtype of natural numbers that are positive.
-/

/-- `ℕ+` is the type of positive natural numbers. It is defined as a subtype,
  and the VM representation of `ℕ+` is the same as `ℕ` because the proof
  is not stored. -/
def pnat :=
  Subtype fun (n : ℕ) => 0 < n

notation:1024 "ℕ+" => Mathlib.pnat

protected instance coe_pnat_nat : has_coe ℕ+ ℕ :=
  has_coe.mk subtype.val

protected instance pnat.has_repr : has_repr ℕ+ :=
  has_repr.mk fun (n : ℕ+) => repr (subtype.val n)

/-- Predecessor of a `ℕ+`, as a `ℕ`. -/
def pnat.nat_pred (i : ℕ+) : ℕ :=
  ↑i - 1

namespace nat

/-- Convert a natural number to a positive natural number. The
  positivity assumption is inferred by `dec_trivial`. -/
def to_pnat (n : ℕ) (h : autoParam (0 < n)
  (Lean.Syntax.ident Lean.SourceInfo.none (String.toSubstring "Mathlib.tactic.exact_dec_trivial")
    (Lean.Name.mkStr (Lean.Name.mkStr (Lean.Name.mkStr Lean.Name.anonymous "Mathlib") "tactic") "exact_dec_trivial") [])) : ℕ+ :=
  { val := n, property := h }

/-- Write a successor as an element of `ℕ+`. -/
def succ_pnat (n : ℕ) : ℕ+ :=
  { val := Nat.succ n, property := succ_pos n }

@[simp] theorem succ_pnat_coe (n : ℕ) : ↑(succ_pnat n) = Nat.succ n :=
  rfl

theorem succ_pnat_inj {n : ℕ} {m : ℕ} : succ_pnat n = succ_pnat m → n = m :=
  fun (h : succ_pnat n = succ_pnat m) =>
    let h' : ↑(succ_pnat n) = ↑(succ_pnat m) := congr_arg coe h;
    succ.inj h'

/-- Convert a natural number to a pnat. `n+1` is mapped to itself,
  and `0` becomes `1`. -/
def to_pnat' (n : ℕ) : ℕ+ :=
  succ_pnat (Nat.pred n)

@[simp] theorem to_pnat'_coe (n : ℕ) : ↑(to_pnat' n) = ite (0 < n) n 1 := sorry

end nat

namespace pnat

/-- We now define a long list of structures on ℕ+ induced by
 similar structures on ℕ. Most of these behave in a completely
 obvious way, but there are a few things to be said about
 subtraction, division and powers.
-/
protected instance decidable_eq : DecidableEq ℕ+ :=
  fun (a b : ℕ+) => subtype.decidable_eq a b

protected instance linear_order : linear_order ℕ+ :=
  subtype.linear_order fun (n : ℕ) => 0 < n

@[simp] theorem mk_le_mk (n : ℕ) (k : ℕ) (hn : 0 < n) (hk : 0 < k) : { val := n, property := hn } ≤ { val := k, property := hk } ↔ n ≤ k :=
  iff.rfl

@[simp] theorem mk_lt_mk (n : ℕ) (k : ℕ) (hn : 0 < n) (hk : 0 < k) : { val := n, property := hn } < { val := k, property := hk } ↔ n < k :=
  iff.rfl

@[simp] theorem coe_le_coe (n : ℕ+) (k : ℕ+) : ↑n ≤ ↑k ↔ n ≤ k :=
  iff.rfl

@[simp] theorem coe_lt_coe (n : ℕ+) (k : ℕ+) : ↑n < ↑k ↔ n < k :=
  iff.rfl

@[simp] theorem pos (n : ℕ+) : 0 < ↑n :=
  subtype.property n

theorem eq {m : ℕ+} {n : ℕ+} : ↑m = ↑n → m = n :=
  subtype.eq

@[simp] theorem coe_inj {m : ℕ+} {n : ℕ+} : ↑m = ↑n ↔ m = n :=
  set_coe.ext_iff

@[simp] theorem mk_coe (n : ℕ) (h : 0 < n) : ↑{ val := n, property := h } = n :=
  rfl

protected instance add_comm_semigroup : add_comm_semigroup ℕ+ :=
  add_comm_semigroup.mk (fun (a b : ℕ+) => { val := ↑a + ↑b, property := sorry }) sorry sorry

@[simp] theorem add_coe (m : ℕ+) (n : ℕ+) : ↑(m + n) = ↑m + ↑n :=
  rfl

protected instance coe_add_hom : is_add_hom coe :=
  is_add_hom.mk add_coe

protected instance add_left_cancel_semigroup : add_left_cancel_semigroup ℕ+ :=
  add_left_cancel_semigroup.mk add_comm_semigroup.add add_comm_semigroup.add_assoc sorry

protected instance add_right_cancel_semigroup : add_right_cancel_semigroup ℕ+ :=
  add_right_cancel_semigroup.mk add_comm_semigroup.add add_comm_semigroup.add_assoc sorry

@[simp] theorem ne_zero (n : ℕ+) : ↑n ≠ 0 :=
  ne_of_gt (subtype.property n)

theorem to_pnat'_coe {n : ℕ} : 0 < n → ↑(nat.to_pnat' n) = n :=
  nat.succ_pred_eq_of_pos

@[simp] theorem coe_to_pnat' (n : ℕ+) : nat.to_pnat' ↑n = n :=
  eq (to_pnat'_coe (pos n))

protected instance comm_monoid : comm_monoid ℕ+ :=
  comm_monoid.mk (fun (m n : ℕ+) => { val := subtype.val m * subtype.val n, property := sorry }) sorry (nat.succ_pnat 0)
    sorry sorry sorry

theorem lt_add_one_iff {a : ℕ+} {b : ℕ+} : a < b + 1 ↔ a ≤ b :=
  nat.lt_add_one_iff

theorem add_one_le_iff {a : ℕ+} {b : ℕ+} : a + 1 ≤ b ↔ a < b :=
  nat.add_one_le_iff

@[simp] theorem one_le (n : ℕ+) : 1 ≤ n :=
  subtype.property n

protected instance order_bot : order_bot ℕ+ :=
  order_bot.mk 1 partial_order.le partial_order.lt sorry sorry sorry sorry

@[simp] theorem bot_eq_zero : ⊥ = 1 :=
  rfl

protected instance inhabited : Inhabited ℕ+ :=
  { default := 1 }

-- Some lemmas that rewrite `pnat.mk n h`, for `n` an explicit numeral, into explicit numerals.

@[simp] theorem mk_one {h : 0 < 1} : { val := 1, property := h } = 1 :=
  rfl

@[simp] theorem mk_bit0 (n : ℕ) {h : 0 < bit0 n} : { val := bit0 n, property := h } = bit0 { val := n, property := nat.pos_of_bit0_pos h } :=
  rfl

@[simp] theorem mk_bit1 (n : ℕ) {h : 0 < bit1 n} {k : 0 < n} : { val := bit1 n, property := h } = bit1 { val := n, property := k } :=
  rfl

-- Some lemmas that rewrite inequalities between explicit numerals in `pnat`

-- into the corresponding inequalities in `nat`.

-- TODO: perhaps this should not be attempted by `simp`,

-- and instead we should expect `norm_num` to take care of these directly?

-- TODO: these lemmas are perhaps incomplete:

-- * 1 is not represented as a bit0 or bit1

-- * strict inequalities?

@[simp] theorem bit0_le_bit0 (n : ℕ+) (m : ℕ+) : bit0 n ≤ bit0 m ↔ bit0 ↑n ≤ bit0 ↑m :=
  iff.rfl

@[simp] theorem bit0_le_bit1 (n : ℕ+) (m : ℕ+) : bit0 n ≤ bit1 m ↔ bit0 ↑n ≤ bit1 ↑m :=
  iff.rfl

@[simp] theorem bit1_le_bit0 (n : ℕ+) (m : ℕ+) : bit1 n ≤ bit0 m ↔ bit1 ↑n ≤ bit0 ↑m :=
  iff.rfl

@[simp] theorem bit1_le_bit1 (n : ℕ+) (m : ℕ+) : bit1 n ≤ bit1 m ↔ bit1 ↑n ≤ bit1 ↑m :=
  iff.rfl

@[simp] theorem one_coe : ↑1 = 1 :=
  rfl

@[simp] theorem mul_coe (m : ℕ+) (n : ℕ+) : ↑(m * n) = ↑m * ↑n :=
  rfl

protected instance coe_mul_hom : is_monoid_hom coe :=
  is_monoid_hom.mk one_coe

@[simp] theorem coe_eq_one_iff {m : ℕ+} : ↑m = 1 ↔ m = 1 := sorry

@[simp] theorem coe_bit0 (a : ℕ+) : ↑(bit0 a) = bit0 ↑a :=
  rfl

@[simp] theorem coe_bit1 (a : ℕ+) : ↑(bit1 a) = bit1 ↑a :=
  rfl

@[simp] theorem pow_coe (m : ℕ+) (n : ℕ) : ↑(m ^ n) = ↑m ^ n := sorry

protected instance left_cancel_semigroup : left_cancel_semigroup ℕ+ :=
  left_cancel_semigroup.mk comm_monoid.mul comm_monoid.mul_assoc sorry

protected instance right_cancel_semigroup : right_cancel_semigroup ℕ+ :=
  right_cancel_semigroup.mk comm_monoid.mul comm_monoid.mul_assoc sorry

protected instance ordered_cancel_comm_monoid : ordered_cancel_comm_monoid ℕ+ :=
  ordered_cancel_comm_monoid.mk left_cancel_semigroup.mul left_cancel_semigroup.mul_assoc
    left_cancel_semigroup.mul_left_cancel comm_monoid.one comm_monoid.one_mul comm_monoid.mul_one comm_monoid.mul_comm
    right_cancel_semigroup.mul_right_cancel linear_order.le linear_order.lt linear_order.le_refl linear_order.le_trans
    linear_order.le_antisymm sorry sorry

protected instance distrib : distrib ℕ+ :=
  distrib.mk comm_monoid.mul add_comm_semigroup.add sorry sorry

/-- Subtraction a - b is defined in the obvious way when
  a > b, and by a - b = 1 if a ≤ b.
-/
protected instance has_sub : Sub ℕ+ :=
  { sub := fun (a b : ℕ+) => nat.to_pnat' (↑a - ↑b) }

theorem sub_coe (a : ℕ+) (b : ℕ+) : ↑(a - b) = ite (b < a) (↑a - ↑b) 1 := sorry

theorem add_sub_of_lt {a : ℕ+} {b : ℕ+} : a < b → a + (b - a) = b := sorry

protected instance has_well_founded : has_well_founded ℕ+ :=
  has_well_founded.mk Less sorry

/-- Strong induction on `pnat`. -/
theorem strong_induction_on {p : ℕ+ → Prop} (n : ℕ+) (h : ∀ (k : ℕ+), (∀ (m : ℕ+), m < k → p m) → p k) : p n := sorry

/-- If `(n : pnat)` is different from `1`, then it is the successor of some `(k : pnat)`. -/
theorem exists_eq_succ_of_ne_one {n : ℕ+} (h1 : n ≠ 1) : ∃ (k : ℕ+), n = k + 1 := sorry

theorem case_strong_induction_on {p : ℕ+ → Prop} (a : ℕ+) (hz : p 1) (hi : ∀ (n : ℕ+), (∀ (m : ℕ+), m ≤ n → p m) → p (n + 1)) : p a := sorry

/-- We define `m % k` and `m / k` in the same way as for `ℕ`
  except that when `m = n * k` we take `m % k = k` and
  `m / k = n - 1`.  This ensures that `m % k` is always positive
  and `m = (m % k) + k * (m / k)` in all cases.  Later we
  define a function `div_exact` which gives the usual `m / k`
  in the case where `k` divides `m`.
-/
def mod_div_aux : ℕ+ → ℕ → ℕ → ℕ+ × ℕ :=
  sorry

theorem mod_div_aux_spec (k : ℕ+) (r : ℕ) (q : ℕ) (h : ¬(r = 0 ∧ q = 0)) : ↑(prod.fst (mod_div_aux k r q)) + ↑k * prod.snd (mod_div_aux k r q) = r + ↑k * q := sorry

/-- `mod_div m k = (m % k, m / k)`.
  We define `m % k` and `m / k` in the same way as for `ℕ`
  except that when `m = n * k` we take `m % k = k` and
  `m / k = n - 1`.  This ensures that `m % k` is always positive
  and `m = (m % k) + k * (m / k)` in all cases.  Later we
  define a function `div_exact` which gives the usual `m / k`
  in the case where `k` divides `m`.
-/
def mod_div (m : ℕ+) (k : ℕ+) : ℕ+ × ℕ :=
  mod_div_aux k (↑m % ↑k) (↑m / ↑k)

/-- We define `m % k` in the same way as for `ℕ`
  except that when `m = n * k` we take `m % k = k` This ensures that `m % k` is always positive.
-/
def mod (m : ℕ+) (k : ℕ+) : ℕ+ :=
  prod.fst (mod_div m k)

/-- We define `m / k` in the same way as for `ℕ` except that when `m = n * k` we take
  `m / k = n - 1`. This ensures that `m = (m % k) + k * (m / k)` in all cases. Later we
  define a function `div_exact` which gives the usual `m / k` in the case where `k` divides `m`.
-/
def div (m : ℕ+) (k : ℕ+) : ℕ :=
  prod.snd (mod_div m k)

theorem mod_add_div (m : ℕ+) (k : ℕ+) : ↑(mod m k) + ↑k * div m k = ↑m := sorry

theorem div_add_mod (m : ℕ+) (k : ℕ+) : ↑k * div m k + ↑(mod m k) = ↑m :=
  Eq.trans (add_comm (↑k * div m k) ↑(mod m k)) (mod_add_div m k)

theorem mod_coe (m : ℕ+) (k : ℕ+) : ↑(mod m k) = ite (↑m % ↑k = 0) (↑k) (↑m % ↑k) := sorry

theorem div_coe (m : ℕ+) (k : ℕ+) : div m k = ite (↑m % ↑k = 0) (Nat.pred (↑m / ↑k)) (↑m / ↑k) := sorry

theorem mod_le (m : ℕ+) (k : ℕ+) : mod m k ≤ m ∧ mod m k ≤ k := sorry

theorem dvd_iff {k : ℕ+} {m : ℕ+} : k ∣ m ↔ ↑k ∣ ↑m := sorry

theorem dvd_iff' {k : ℕ+} {m : ℕ+} : k ∣ m ↔ mod m k = k := sorry

theorem le_of_dvd {m : ℕ+} {n : ℕ+} : m ∣ n → m ≤ n :=
  eq.mpr (id (Eq._oldrec (Eq.refl (m ∣ n → m ≤ n)) (propext dvd_iff')))
    fun (h : mod n m = m) => eq.mpr (id (Eq._oldrec (Eq.refl (m ≤ n)) (Eq.symm h))) (and.left (mod_le n m))

/-- If `h : k | m`, then `k * (div_exact m k) = m`. Note that this is not equal to `m / k`. -/
def div_exact (m : ℕ+) (k : ℕ+) : ℕ+ :=
  { val := Nat.succ (div m k), property := sorry }

theorem mul_div_exact {m : ℕ+} {k : ℕ+} (h : k ∣ m) : k * div_exact m k = m := sorry

theorem dvd_antisymm {m : ℕ+} {n : ℕ+} : m ∣ n → n ∣ m → m = n :=
  fun (hmn : m ∣ n) (hnm : n ∣ m) => le_antisymm (le_of_dvd hmn) (le_of_dvd hnm)

theorem dvd_one_iff (n : ℕ+) : n ∣ 1 ↔ n = 1 :=
  { mp := fun (h : n ∣ 1) => dvd_antisymm h (one_dvd n), mpr := fun (h : n = 1) => Eq.symm h ▸ dvd_refl 1 }

theorem pos_of_div_pos {n : ℕ+} {a : ℕ} (h : a ∣ ↑n) : 0 < a :=
  iff.mpr pos_iff_ne_zero
    (id fun (hzero : a = 0) => ne_zero n (eq_zero_of_zero_dvd (eq.mp (Eq._oldrec (Eq.refl (a ∣ ↑n)) hzero) h)))


import lib.m154

open m154
namespace f06
/-
Cette feuille continue l'étude du chapitre 2 du cours de math 101, en abordant
les suites extraites, les valeurs d'adhérence et les suites de Cauchy.
Il n'y a aucun nouvelle commande Lean à apprendre. Cependant les
exercices sont globalement un peu plus difficiles mathématiquement que dans
la feuille 5. Il est donc inutile de travailler cette feuille si la
feuille 5 n'est pas solidement comprise.

On rappelle des lemmes potentiellement utiles :

`abs_inferieur_ssi (x y : ℝ) : |x| ≤ y ↔ -y ≤ x ∧ x ≤ y`

`ineg_triangle (x y : ℝ) : |x + y| ≤ |x| + |y|`

`abs_diff (x y : ℝ) : |x - y| = |y - x|`

`superieur_max_ssi (p q r) : r ≥ max p q  ↔ r ≥ p ∧ r ≥ q`

`inferieur_max_gauche p q : p ≤ max p q`

`inferieur_max_droite p q : q ≤ max p q`
-/

lemma demi_pos { ε : ℝ } : ε > 0 → ε/2 > 0 :=
begin
  Supposons hyp,
  On conclut par hyp,
end

/- On rappelle aussi la définition de « u tend vers l » -/

/-- `limite_suite u l` signifie que la suite `u` tend vers `l` -/
def limite_suite (u : ℕ → ℝ) (l : ℝ) : Prop :=
∀ ε > 0, ∃ N, ∀ n ≥ N, |u n - l| ≤ ε

/-
On a démontré dans la feuille 5 l'unicité de la limite d'une suite convergente :

`unicite_limite : limite_suite u l → limite_suite u l' → l = l'`
-/

/-- Une fonction `φ : ℕ → ℕ` est une extraction si elle est
strictement croissante. -/
def extraction (φ : ℕ → ℕ) := ∀ n m, n < m → φ n < φ m

-- Dans la suite, φ désignera toujours une fonction de ℕ dans ℕ
variable { φ : ℕ → ℕ}

/-
On pourra utiliser le lemme suivant dont la démonstration est une récurrence
immédiate (mais nous n'avons pas encore vu comment faire des démonstrations
par récurrence avec Lean).

`extraction_superieur_id : extraction φ → ∀ n, n ≤ φ n`

Pour l'exercice suivant, il est utile de se souvenir que la notation
`∃ n ≥ N, P n` est l'abréviation de `∃ n, n ≥ N ∧ P n`.
-/

/-- Si `φ` est une extraction alors elle prend des valeurs
 arbitrairement grandes pour des indices arbitrairement grands. -/
lemma extraction_superieur : extraction φ → ∀ N N', ∃ n ≥ N', φ n ≥ N :=
begin
  sorry
end

/-- Un réel `a` est valeur d'adhérence d'une suite `u` s'il
existe une suite extraite de `u` qui tend vers `a`. -/
def valeur_adherence (u : ℕ → ℝ) (a : ℝ) :=
∃ φ, extraction φ ∧ limite_suite (u ∘ φ) a

-- Dans la suite u désigne une suite de réels, et a et l sont des réels
variables {u : ℕ → ℝ} {a l : ℝ}

/-- Si `a` est valeur d'adhérence de `u` alors il existe des valeurs
de `u` aussi proche qu'on veut de `a` pour des indices aussi grands qu'on veut. -/
lemma valeur_proche_adherence :
  valeur_adherence u a → ∀ ε > 0, ∀ N, ∃ n ≥ N, |u n - a| ≤ ε :=
begin
  sorry
end

/-- Si `u` tend vers `l` alors toutes ses suites extraites tendent vers `l`. -/
lemma limite_extraction_si_limite (h : limite_suite u l) (hφ : extraction φ) :
limite_suite (u ∘ φ) l :=
begin
  sorry
end

/-- Si une suite `u` qui tend vers `l` alors elle n'a pas d'autre valeur
d'adhérence que `l`. -/
lemma val_adh_lim (hl : limite_suite u l) (ha : valeur_adherence u a) : a = l :=
begin
  sorry
end

/-- Une suite `u` est de Cauchy si ses termes sont aussi proches qu'on
veut pour des indices assez grands. -/
def suite_cauchy (u : ℕ → ℝ) := ∀ ε > 0, ∃ N, ∀ p q, p ≥ N → q ≥ N → |u p - u q| ≤ ε

/-- Si une suite `u` converge alors elle est de Cauchy. -/
lemma cauchy_si_converge : (∃ l, limite_suite u l) → suite_cauchy u :=
begin
  sorry
end

/- Dans l'exercice suivant, on pourra utiliser valeur_proche_adherence
  démontré plus haut et dont on rappelle l'énoncé :

`valeur_proche_adherence : valeur_adherence u a → ∀ ε > 0, ∀ N, ∃ n ≥ N, |u n - a| ≤ ε`
-/

/-- Si une suite de Cauchy `u` admet une valeur d'adhérence `l` alors elle
converge vers `l`. -/
example (hu : suite_cauchy u) (hl : valeur_adherence u l) : limite_suite u l :=
begin
  sorry
end

end f06


theorem add_right_cancel_iff (t a b : ℕ) :  a + t = b + t ↔ a = b :=
begin
    split,

    intro h,
    apply nat.add_right_cancel h,

    intro f,
    rw f,
end


/-
Copyright (c) 2020 Yury Kudryashov. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yury Kudryashov

! This file was ported from Lean 3 source module analysis.analytic.radius_liminf
! leanprover-community/mathlib commit f2ce6086713c78a7f880485f7917ea547a215982
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Analysis.Analytic.Basic
import Mathbin.Analysis.SpecialFunctions.Pow

/-!
# Representation of `formal_multilinear_series.radius` as a `liminf`

In this file we prove that the radius of convergence of a `formal_multilinear_series` is equal to
$\liminf_{n\to\infty} \frac{1}{\sqrt[n]{‖p n‖}}$. This lemma can't go to `basic.lean` because this
would create a circular dependency once we redefine `exp` using `formal_multilinear_series`.
-/

variable {𝕜 : Type _} [NontriviallyNormedField 𝕜] {E : Type _} [NormedAddCommGroup E]
  [NormedSpace 𝕜 E] {F : Type _} [NormedAddCommGroup F] [NormedSpace 𝕜 F]

open Topology Classical BigOperators NNReal ENNReal

open Filter Asymptotics

namespace FormalMultilinearSeries

variable (p : FormalMultilinearSeries 𝕜 E F)

/-- The radius of a formal multilinear series is equal to
$\liminf_{n\to\infty} \frac{1}{\sqrt[n]{‖p n‖}}$. The actual statement uses `ℝ≥0` and some
coercions. -/
theorem radius_eq_liminf : p.radius = liminf (fun n => 1 / (‖p n‖₊ ^ (1 / (n : ℝ)) : ℝ≥0)) atTop :=
  by
  have :
    ∀ (r : ℝ≥0) {n : ℕ},
      0 < n → ((r : ℝ≥0∞) ≤ 1 / ↑(‖p n‖₊ ^ (1 / (n : ℝ))) ↔ ‖p n‖₊ * r ^ n ≤ 1) :=
    by
    intro r n hn
    have : 0 < (n : ℝ) := Nat.cast_pos.2 hn
    conv_lhs =>
      rw [one_div, ENNReal.le_inv_iff_mul_le, ← ENNReal.coe_mul, ENNReal.coe_le_one_iff, one_div, ←
        NNReal.rpow_one r, ← mul_inv_cancel this.ne', NNReal.rpow_mul, ← NNReal.mul_rpow, ←
        NNReal.one_rpow n⁻¹, NNReal.rpow_le_rpow_iff (inv_pos.2 this), mul_comm,
        NNReal.rpow_nat_cast]
  apply le_antisymm <;> refine' ENNReal.le_of_forall_nnreal_lt fun r hr => _
  · rcases((tFAE_exists_lt_isOCat_pow (fun n => ‖p n‖ * r ^ n) 1).out 1 7).1
        (p.is_o_of_lt_radius hr) with ⟨a, ha, H⟩
    refine' le_Liminf_of_le (by infer_param) (eventually_map.2 <| _)
    refine'
      H.mp ((eventually_gt_at_top 0).mono fun n hn₀ hn => (this _ hn₀).2 (NNReal.coe_le_coe.1 _))
    push_cast
    exact (le_abs_self _).trans (hn.trans (pow_le_one _ ha.1.le ha.2.le))
  · refine' p.le_radius_of_is_O (is_O.of_bound 1 _)
    refine' (eventually_lt_of_lt_liminf hr).mp ((eventually_gt_at_top 0).mono fun n hn₀ hn => _)
    simpa using NNReal.coe_le_coe.2 ((this _ hn₀).1 hn.le)
#align formal_multilinear_series.radius_eq_liminf FormalMultilinearSeries.radius_eq_liminf

end FormalMultilinearSeries


import algebra.char_p.basic
import algebra.euclidean_domain.defs
import ring_theory.power_series.basic
import logic.lemmas

import mason_stothers

noncomputable theory
open_locale polynomial classical

open polynomial
open unique_factorization_monoid

variables {k: Type*} [field k]

private lemma unit_ne_zero {u : k[X]ˣ} : (↑u : k[X]) ≠ 0 :=
begin
  rcases polynomial.is_unit_iff.mp u.is_unit with ⟨r, hr, er⟩,
  rw [←er, ne.def, C_eq_zero, ←ne.def],
  rw is_unit_iff_ne_zero at hr,
  exact hr,
end

private lemma unit_nat_degree_zero {u : k[X]ˣ} : (↑u : k[X]).nat_degree = 0 :=
begin
  rcases polynomial.is_unit_iff.mp u.is_unit with ⟨r, hr, er⟩,
  rw ←er, exact polynomial.nat_degree_C r,
end

private lemma derivative_unit_mul {u : k[X]ˣ} (a : k[X]) :
  (↑u * a).derivative = ↑u * a.derivative :=
begin
  rcases polynomial.is_unit_iff.mp u.is_unit with ⟨r, hr, er⟩,
  rw ←er, simp only [derivative_mul, derivative_C, zero_mul, zero_add],
end

private lemma is_coprime.mul_units_left {a b : k[X]} {u v : k[X]ˣ}
  (h : is_coprime a b) : is_coprime (↑u * a) (↑v * b) :=
by rw [is_coprime_mul_unit_left_left u.is_unit, is_coprime_mul_unit_left_right v.is_unit]; exact h

theorem polynomial.flt_catalan
  {p q r : ℕ} (hp : 0 < p) (hq : 0 < q) (hr : 0 < r)
  (hineq : q*r + r*p + p*q ≤ p*q*r)
  (chp : ¬(ring_char k ∣ p)) (chq : ¬(ring_char k ∣ q)) (chr : ¬(ring_char k ∣ r))
  {a b c : k[X]} (ha : a ≠ 0) (hb : b ≠ 0) (hc : c ≠ 0) (hab : is_coprime a b)
  {u v w : k[X]ˣ} (heq: ↑u*a^p + ↑v*b^q = ↑w*c^r) :
  (a.derivative = 0 ∧ b.derivative = 0 ∧ c.derivative = 0) :=
begin
  have hbc : is_coprime b c,
  { rw [←units.inv_mul_eq_iff_eq_mul, mul_add,
      ←mul_assoc, ←mul_assoc, ←units.coe_mul, ←units.coe_mul] at heq,
    rw [←is_coprime.pow_left_iff hq, ←is_coprime.pow_right_iff hr, ←heq],
    refine is_coprime.add_mul_right_right _ ↑(w⁻¹ * v),
    rw is_coprime_mul_unit_left_right (w⁻¹ * u).is_unit,
    exact hab.symm.pow },
  have hca : is_coprime c a,
  { rw [←units.inv_mul_eq_iff_eq_mul, mul_add,
      ←mul_assoc, ←mul_assoc, ←units.coe_mul, ←units.coe_mul] at heq,
    rw [←is_coprime.pow_left_iff hr, ←is_coprime.pow_right_iff hp, ←heq],
    refine is_coprime.mul_add_right_left _ ↑(w⁻¹ * u),
    rw is_coprime_mul_unit_left_left (w⁻¹ * v).is_unit,
    exact hab.symm.pow, },

  have hap : a^p ≠ 0 := pow_ne_zero _ ha,
  have hbp : b^q ≠ 0 := pow_ne_zero _ hb,
  have hcp : -c^r ≠ 0 := neg_ne_zero.mpr (pow_ne_zero _ hc),

  rw [←add_neg_eq_zero, ←mul_neg] at heq,

  have habp : is_coprime (↑u*a^p) (↑v*b^q) := hab.pow.mul_units_left,
  have hbcp : is_coprime (↑v*b^q) (↑w*-c^r) := hbc.pow.neg_right.mul_units_left,
  have hcap : is_coprime (↑w*-c^r) (↑u*a^p) := hca.pow.neg_left.mul_units_left,
  have habcp := hcap.symm.mul_left hbcp,

  cases (polynomial.abc
    (mul_ne_zero unit_ne_zero hap)
    (mul_ne_zero unit_ne_zero hbp)
    (mul_ne_zero unit_ne_zero hcp)
    habp hbcp hcap heq) with ineq dr0, swap,
  { simp_rw [derivative_unit_mul,
      units.mul_right_eq_zero,
      derivative_neg, neg_eq_zero] at dr0,
    rw [pow_derivative_eq_zero chp ha,
        pow_derivative_eq_zero chq hb,
        pow_derivative_eq_zero chr hc] at dr0,
    exact dr0, },

  exfalso, apply not_le_of_lt ineq, clear ineq,
  -- work on lhs
  rw [radical_mul habcp, radical_mul habp],
  simp_rw radical_unit_mul,
  rw [radical_neg,
    radical_pow a hp, radical_pow b hq, radical_pow c hr],
  rw [←radical_mul hab, ←radical_mul (hca.symm.mul_left hbc)],
  apply le_trans radical_nat_degree_le,
  rw nat_degree_mul (mul_ne_zero ha hb) hc,
  rw nat_degree_mul ha hb,
  -- work on rhs
  rw nat_degree_mul unit_ne_zero hap,
  rw nat_degree_mul unit_ne_zero hbp,
  rw nat_degree_mul unit_ne_zero hcp,
  simp_rw [
    polynomial.nat_degree_neg,
    polynomial.nat_degree_pow,
    unit_nat_degree_zero, zero_add],

  have hpqr : 0 < p*q*r := nat.mul_le_mul
    (nat.mul_le_mul hp hq) hr,
  apply le_of_mul_le_mul_left _ hpqr,
  apply le_trans _ (nat.mul_le_mul_right _ hineq),
  convert weighted_average_le_max3,
  ring_nf,
end

theorem polynomial.flt_coprime
  {n : ℕ} (hn : 3 ≤ n) (chn : ¬(ring_char k ∣ n))
  {a b c : k[X]} (ha : a ≠ 0) (hb : b ≠ 0) (hc : c ≠ 0)
  (hab : is_coprime a b) (heq: a^n + b^n = c^n) :
  (a.derivative = 0 ∧ b.derivative = 0 ∧ c.derivative = 0) :=
begin
  have hn' : 0 < n := by linarith,
  rw [←one_mul (a^n), ←one_mul (b^n), ←one_mul (c^n)] at heq,
  have h : ↑(1: k[X]ˣ) = (1: k[X]) := rfl,
  simp_rw ←h at heq,
  apply polynomial.flt_catalan hn' hn' hn' _
    chn chn chn ha hb hc hab heq,
  have eq_lhs : n*n + n*n + n*n = 3*n*n := by ring_nf,
  rw eq_lhs, rw [mul_assoc, mul_assoc],
  apply nat.mul_le_mul_right (n*n), exact hn,
end

namespace euclidean_domain

universe u
variables {R : Type u} [euclidean_domain R] {a b : R}

-- TODO: get rid of this once mathlib is updated
protected lemma mul_div_cancel' (hb : b ≠ 0) (hab : b ∣ a) : b * (a / b) = a :=
  by rw [←mul_div_assoc _ hab, mul_div_cancel_left _ hb]

lemma pow_n_dvd_pow_n_iff {a b : k[X]} {n : ℕ} (ha : a ≠ 0) (hb : b ≠ 0) (hn : 0 < n) :
  a^n ∣ b^n ↔ a ∣ b :=
begin
  rw dvd_iff_normalized_factors_le_normalized_factors ha hb,
  rw dvd_iff_normalized_factors_le_normalized_factors
    (pow_ne_zero _ ha) (pow_ne_zero _ hb),
  simp_rw [normalized_factors_pow, multiset.le_iff_count, multiset.count_nsmul],
  simp_rw mul_le_mul_left hn,
end

end euclidean_domain

theorem polynomial.flt
  [char_zero k] {n : ℕ} (hn : 3 ≤ n)
  {a b c : k[X]} (ha : a ≠ 0) (hb : b ≠ 0) (hc : c ≠ 0) (heq: a^n + b^n = c^n) :
  ∃ (d: k[X]) (sa sb sc : k), a = ↑sa * d ∧ b = ↑sb * d ∧ c = ↑sc * d :=
begin
  have hd : euclidean_domain.gcd a b ≠ 0,
  { intro h, rw euclidean_domain.gcd_eq_zero_iff at h, exact ha h.1, },
  have eq_a := euclidean_domain.mul_div_cancel' hd
    (euclidean_domain.gcd_dvd_left a b),
  have eq_b := euclidean_domain.mul_div_cancel' hd
    (euclidean_domain.gcd_dvd_right a b),
  set d := euclidean_domain.gcd a b with def_d,
  rw [←eq_a, ←eq_b] at heq,
  simp_rw [mul_pow, ←mul_add] at heq,
  have dvd_dc := dvd_of_mul_right_eq _ heq,
  have hn' : 0 < n := by linarith,
  rw euclidean_domain.pow_n_dvd_pow_n_iff hd hc hn' at dvd_dc,
  have eq_c := euclidean_domain.mul_div_cancel' hd dvd_dc,
  rw [←eq_c, mul_pow, mul_right_inj' (pow_ne_zero _ hd)] at heq,
  have hab : is_coprime (a / d) (b / d),
  { have gcd_eq := euclidean_domain.gcd_eq_gcd_ab a b,
    rw [←def_d, ←eq_a, ←eq_b, mul_assoc, mul_assoc, ←mul_add] at gcd_eq,
    conv_lhs at gcd_eq {rw ←mul_one d},
    rw [mul_right_inj' hd, mul_comm (a / d), mul_comm (b / d)] at gcd_eq,
    existsi _, existsi _, exact gcd_eq.symm, },
  have flt := polynomial.flt_coprime hn
    (by rw [ring_char.eq_zero, zero_dvd_iff]; linarith)
    (right_ne_zero_of_mul (eq_a.trans_ne ha))
    (right_ne_zero_of_mul (eq_b.trans_ne hb))
    (right_ne_zero_of_mul (eq_c.trans_ne hc))
    hab heq,
  rcases flt with ⟨hda, hdb, hdc⟩,
  rw [polynomial.eq_C_of_derivative_eq_zero hda, mul_comm, eq_comm] at eq_a,
  rw [polynomial.eq_C_of_derivative_eq_zero hdb, mul_comm, eq_comm] at eq_b,
  rw [polynomial.eq_C_of_derivative_eq_zero hdc, mul_comm, eq_comm] at eq_c,
  refine ⟨_, _, _, _, eq_a, eq_b, eq_c⟩,
end


/-
Copyright (c) 2018 Chris Hughes. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Chris Hughes, Joey van Langen, Casper Putz
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.tactic.apply_fun
import Mathlib.data.equiv.ring
import Mathlib.data.zmod.basic
import Mathlib.linear_algebra.basis
import Mathlib.ring_theory.integral_domain
import Mathlib.field_theory.separable
import Mathlib.PostPort

universes u_2 u_1

namespace Mathlib

/-!
# Finite fields

This file contains basic results about finite fields.
Throughout most of this file, `K` denotes a finite field
and `q` is notation for the cardinality of `K`.

See `ring_theory.integral_domain` for the fact that the unit group of a finite field is a
cyclic group, as well as the fact that every finite integral domain is a field (`field_of_integral_domain`).

## Main results

1. `card_units`: The unit group of a finite field is has cardinality `q - 1`.
2. `sum_pow_units`: The sum of `x^i`, where `x` ranges over the units of `K`, is
   - `q-1` if `q-1 ∣ i`
   - `0`   otherwise
3. `finite_field.card`: The cardinality `q` is a power of the characteristic of `K`.
   See `card'` for a variant.

## Notation

Throughout most of this file, `K` denotes a finite field
and `q` is notation for the cardinality of `K`.

-/

namespace finite_field

/-- The cardinality of a field is at most `n` times the cardinality of the image of a degree `n`
  polynomial -/
theorem card_image_polynomial_eval {R : Type u_2} [integral_domain R] [DecidableEq R] [fintype R] {p : polynomial R} (hp : 0 < polynomial.degree p) : fintype.card R ≤ polynomial.nat_degree p * finset.card (finset.image (fun (x : R) => polynomial.eval x p) finset.univ) := sorry

/-- If `f` and `g` are quadratic polynomials, then the `f.eval a + g.eval b = 0` has a solution. -/
theorem exists_root_sum_quadratic {R : Type u_2} [integral_domain R] [fintype R] {f : polynomial R} {g : polynomial R} (hf2 : polynomial.degree f = bit0 1) (hg2 : polynomial.degree g = bit0 1) (hR : fintype.card R % bit0 1 = 1) : ∃ (a : R), ∃ (b : R), polynomial.eval a f + polynomial.eval b g = 0 := sorry

theorem card_units {K : Type u_1} [field K] [fintype K] : fintype.card (units K) = fintype.card K - 1 := sorry

theorem prod_univ_units_id_eq_neg_one {K : Type u_1} [field K] [fintype K] : (finset.prod finset.univ fun (x : units K) => x) = -1 := sorry

theorem pow_card_sub_one_eq_one {K : Type u_1} [field K] [fintype K] (a : K) (ha : a ≠ 0) : a ^ (fintype.card K - 1) = 1 := sorry

theorem pow_card {K : Type u_1} [field K] [fintype K] (a : K) : a ^ fintype.card K = a := sorry

theorem card (K : Type u_1) [field K] [fintype K] (p : ℕ) [char_p K p] : ∃ (n : ℕ+), nat.prime p ∧ fintype.card K = p ^ ↑n := sorry

theorem card' {K : Type u_1} [field K] [fintype K] : ∃ (p : ℕ), ∃ (n : ℕ+), nat.prime p ∧ fintype.card K = p ^ ↑n := sorry

@[simp] theorem cast_card_eq_zero (K : Type u_1) [field K] [fintype K] : ↑(fintype.card K) = 0 := sorry

theorem forall_pow_eq_one_iff (K : Type u_1) [field K] [fintype K] (i : ℕ) : (∀ (x : units K), x ^ i = 1) ↔ fintype.card K - 1 ∣ i := sorry

/-- The sum of `x ^ i` as `x` ranges over the units of a finite field of cardinality `q`
is equal to `0` unless `(q - 1) ∣ i`, in which case the sum is `q - 1`. -/
theorem sum_pow_units (K : Type u_1) [field K] [fintype K] (i : ℕ) : (finset.sum finset.univ fun (x : units K) => ↑x ^ i) = ite (fintype.card K - 1 ∣ i) (-1) 0 := sorry

/-- The sum of `x ^ i` as `x` ranges over a finite field of cardinality `q`
is equal to `0` if `i < q - 1`. -/
theorem sum_pow_lt_card_sub_one {K : Type u_1} [field K] [fintype K] (i : ℕ) (h : i < fintype.card K - 1) : (finset.sum finset.univ fun (x : K) => x ^ i) = 0 := sorry

theorem frobenius_pow {K : Type u_1} [field K] [fintype K] {p : ℕ} [fact (nat.prime p)] [char_p K p] {n : ℕ} (hcard : fintype.card K = p ^ n) : frobenius K p ^ n = 1 := sorry

theorem expand_card {K : Type u_1} [field K] [fintype K] (f : polynomial K) : coe_fn (polynomial.expand K (fintype.card K)) f = f ^ fintype.card K := sorry

end finite_field

namespace zmod

theorem sum_two_squares (p : ℕ) [hp : fact (nat.prime p)] (x : zmod p) : ∃ (a : zmod p), ∃ (b : zmod p), a ^ bit0 1 + b ^ bit0 1 = x := sorry

end zmod

namespace char_p

theorem sum_two_squares (R : Type u_1) [integral_domain R] (p : ℕ) [fact (0 < p)] [char_p R p] (x : ℤ) : ∃ (a : ℕ), ∃ (b : ℕ), ↑a ^ bit0 1 + ↑b ^ bit0 1 = ↑x := sorry

end char_p

/-- The Fermat-Euler totient theorem. `nat.modeq.pow_totient` is an alternative statement
  of the same theorem. -/
@[simp] theorem zmod.pow_totient {n : ℕ} [fact (0 < n)] (x : units (zmod n)) : x ^ nat.totient n = 1 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (x ^ nat.totient n = 1)) (Eq.symm (zmod.card_units_eq_totient n))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (x ^ fintype.card (units (zmod n)) = 1)) (pow_card_eq_one x))) (Eq.refl 1))

/-- The Fermat-Euler totient theorem. `zmod.pow_totient` is an alternative statement
  of the same theorem. -/
theorem nat.modeq.pow_totient {x : ℕ} {n : ℕ} (h : nat.coprime x n) : nat.modeq n (x ^ nat.totient n) 1 := sorry

namespace zmod

/-- A variation on Fermat's little theorem. See `zmod.pow_card_sub_one_eq_one` -/
@[simp] theorem pow_card {p : ℕ} [fact (nat.prime p)] (x : zmod p) : x ^ p = x :=
  eq.mp (Eq._oldrec (Eq.refl (x ^ fintype.card (zmod p) = x)) (card p)) (finite_field.pow_card x)

@[simp] theorem frobenius_zmod (p : ℕ) [hp : fact (nat.prime p)] : frobenius (zmod p) p = ring_hom.id (zmod p) := sorry

@[simp] theorem card_units (p : ℕ) [fact (nat.prime p)] : fintype.card (units (zmod p)) = p - 1 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (fintype.card (units (zmod p)) = p - 1)) finite_field.card_units))
    (eq.mpr (id (Eq._oldrec (Eq.refl (fintype.card (zmod p) - 1 = p - 1)) (card p))) (Eq.refl (p - 1)))

/-- Fermat's Little Theorem: for every unit `a` of `zmod p`, we have `a ^ (p - 1) = 1`. -/
theorem units_pow_card_sub_one_eq_one (p : ℕ) [fact (nat.prime p)] (a : units (zmod p)) : a ^ (p - 1) = 1 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (a ^ (p - 1) = 1)) (Eq.symm (card_units p))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (a ^ fintype.card (units (zmod p)) = 1)) (pow_card_eq_one a))) (Eq.refl 1))

/-- Fermat's Little Theorem: for all nonzero `a : zmod p`, we have `a ^ (p - 1) = 1`. -/
theorem pow_card_sub_one_eq_one {p : ℕ} [fact (nat.prime p)] {a : zmod p} (ha : a ≠ 0) : a ^ (p - 1) = 1 :=
  eq.mp (Eq._oldrec (Eq.refl (a ^ (fintype.card (zmod p) - 1) = 1)) (card p)) (finite_field.pow_card_sub_one_eq_one a ha)

theorem expand_card {p : ℕ} [fact (nat.prime p)] (f : polynomial (zmod p)) : coe_fn (polynomial.expand (zmod p) p) f = f ^ p := sorry


import .love07_metaprogramming_demo

/-! # LoVe Exercise 7: Metaprogramming -/

set_option pp.beta true

namespace LoVe

/-! ## ## Question 1: A Term Exploder

In this exercise, we develop a string format for the `expr` metatype. By
default, there is no `has_repr` instance to print a nice string. For example: -/

#eval (expr.app (expr.var 0) (expr.var 1) : expr)   -- result: `[external]`
#eval (`(λx : ℕ, x + x) : expr)                     -- result: `[external]`

/-! 1.1. Define a metafunction `expr.repr` that converts an `expr` into a
`string`. It is acceptable to leave out some fields from the `expr`
constructors, such as the level `l` of a sort, the binder information `bi` of
a `λ` or `∀` binder, and the arguments of the `macro` constructor.

Hint: Use `name.to_string` to convert a name to a string, and `repr` for other
types that belong to the `has_repr` type class. -/

-- enter your definition here

/-! We register `expr.repr` in the `has_repr` type class, so that we can use
`repr` without qualification in the future, and so that it is available to
`#eval`. We need the `meta` keyword in front of the command we enter. -/

meta instance expr.has_repr : has_repr expr :=
{ repr := expr.repr }

/-! 1.2. Test your setup. -/

#eval (expr.app (expr.var 0) (expr.var 1) : expr)
#eval (`(λx : ℕ, x + x) : expr)

/-! 1.3. Compare your answer with `expr.to_raw_fmt`. -/

#check expr.to_raw_fmt

/-! ## Question 2: `destruct_and` on Steroids

Recall from the lecture that `destruct_and` fails on easy goals such as -/

#check abc_ac

/-! We will now address this by developing a new tactic called `destro_and`,
which applies both **des**truction and in**tro**duction rules for conjunction.
It will also go automatically through the hypotheses instead of taking an
argument. We will develop it in three steps.

2.1. Develop a tactic `intro_ands` that replaces all goals of the form
`a ∧ b` with two new goals `a` and `b` systematically, until all top-level
conjunctions are gone.

For this, we can use tactics such as `tactic.repeat` (which repeatedly applies a
tactic on all goals and subgoals until the tactic fails on each of the goal) and
`tactic.applyc` (which can be used to apply a rule, in connection with backtick
quoting). -/

meta def intro_ands : tactic unit :=
sorry

lemma abcd_bd (a b c d : Prop) (h : a ∧ (b ∧ c) ∧ d) :
  b ∧ d :=
begin
  intro_ands,
  /- The proof state should be as follows:

  2 goals
  a b c d : Prop,
  h : a ∧ (b ∧ c) ∧ d
  ⊢ b

  a b c d : Prop,
  h : a ∧ (b ∧ c) ∧ d
  ⊢ d -/
  repeat { sorry }
end

lemma abcd_bacb (a b c d : Prop) (h : a ∧ (b ∧ c) ∧ d) :
  b ∧ (a ∧ (c ∧ b)) :=
begin
  intro_ands,
  /- The proof state should be as follows:

  4 goals
  a b c d : Prop,
  h : a ∧ (b ∧ c) ∧ d
  ⊢ b

  a b c d : Prop,
  h : a ∧ (b ∧ c) ∧ d
  ⊢ a

  a b c d : Prop,
  h : a ∧ (b ∧ c) ∧ d
  ⊢ c

  a b c d : Prop,
  h : a ∧ (b ∧ c) ∧ d
  ⊢ b -/
  repeat { sorry }
end

/-! 2.2. Develop a tactic `destruct_ands` that replaces hypotheses of the form
`h : a ∧ b` by two new hypotheses `h_left : a` and `h_right : b` systematically,
until all top-level conjunctions are gone.

Here is imperative-style pseudocode that you can follow:

1. Retrieve the list of hypotheses from the context. This is provided by the
   metaconstant `local_context`.

2. Find the first hypothesis (= term) with a type (= proposition) of the form
   `_ ∧ _`. Here, you can use the `list.mfirst` function, in conjunction with
   pattern matching. You can use `infer_type` to query the type of a term.

3. Perform a case split on the first found hypothesis. This can be achieved
   using the `cases` metafunction.

4. Repeat.

The above procedure might fail if there exists no hypotheses of the required
form. Make sure to handle this failure gracefully, for example using
`tactic.try` or `<|> pure ()`. -/

meta def destruct_ands : tactic unit :=
sorry

lemma abcd_bd₂ (a b c d : Prop) (h : a ∧ (b ∧ c) ∧ d) :
  b ∧ d :=
begin
  destruct_ands,
  /- The proof state should be as follows:

  a b c d : Prop,
  h_left : a,
  h_right_right : d,
  h_right_left_left : b,
  h_right_left_right : c
  ⊢ b ∧ d -/
  sorry
end

/-! 2.3. Combine the two tactics developed above and `tactic.assumption` to
implement the desired `destro_and` tactic. -/

meta def destro_and : tactic unit :=
sorry

lemma abcd_bd₃ (a b c d : Prop) (h : a ∧ (b ∧ c) ∧ d) :
  b ∧ d :=
by destro_and

lemma abcd_bacb₂ (a b c d : Prop) (h : a ∧ (b ∧ c) ∧ d) :
  b ∧ (a ∧ (c ∧ b)) :=
by destro_and

lemma abd_bacb (a b c d : Prop) (h : a ∧ b ∧ d) :
  b ∧ (a ∧ (c ∧ b)) :=
begin
  destro_and,
  /- The proof state should be roughly as follows:

  a b c d : Prop,
  h_left : a,
  h_right_left : b,
  h_right_right : d
  ⊢ c -/
  sorry   -- unprovable
end

/-! 2.4. Provide some more examples for `destro_and` to convince yourself that
it works as expected also on more complicated examples. -/

-- enter your examples here

/-! ## Question 3 (**optional**): A Theorem Finder

We will implement a function that allows us to find theorems by constants
appearing in their statements. So given a list of constant names, the function
will list all theorems in which all these constants appear.

You can use the following metaconstants:

* `declaration` contains all data (name, type, value) associated with a
  declaration understood broadly (e.g., axiom, lemma, constant, etc.).

* `tactic.get_env` gives us access to the `environment`, a metatype thats lists
  all `declaration`s (including all theorems).

* `environment.fold` allows us to walk through the environment and collect data.

* `expr.fold` allows us to walk through an expression and collect data.

3.1 (**optional**). Write a metafunction that checks whether an expression
contains a specific constant.

You can use `expr.fold` to walk through the expression, `||` and `ff` for
Booleans, and `expr.is_constant_of` to check whether an expression is a
constant. -/

meta def term_contains (e : expr) (nam : name) : bool :=
sorry

/-! 3.2 (**optional**). Write a metafunction that checks whether an expression
contains **all** constants in a list.

You can use `list.band` (Boolean and). -/

meta def term_contains_all (nams : list name) (e : expr) : bool :=
sorry

/-! 3.3 (**optional**). Produce the list of all theorems that contain all
constants `nams` in their statement.

`environment.fold` allows you to walk over the list of declarations. With
`declaration.type`, you get the type of a theorem, and with
`declaration.to_name` you get the name. -/

meta def list_constants (nams : list name) (e : environment) : list name :=
sorry

/-! Finally, we develop a tactic that uses the above metafunctions to log all
found theorems: -/

meta def find_constants (nams : list name) : tactic unit :=
do
  env ← tactic.get_env,
  list.mmap' tactic.trace (list_constants nams env)

/-! We test the solution. -/

run_cmd find_constants []   -- lists all theorems
run_cmd find_constants [`list.map, `function.comp]

end LoVe


/-
Copyright (c) 2020 Heather Macbeth. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Heather Macbeth

! This file was ported from Lean 3 source module analysis.normed_space.dual
! leanprover-community/mathlib commit f2ce6086713c78a7f880485f7917ea547a215982
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Analysis.NormedSpace.HahnBanach.Extension
import Mathbin.Analysis.NormedSpace.IsROrC
import Mathbin.Analysis.LocallyConvex.Polar

/-!
# The topological dual of a normed space

In this file we define the topological dual `normed_space.dual` of a normed space, and the
continuous linear map `normed_space.inclusion_in_double_dual` from a normed space into its double
dual.

For base field `𝕜 = ℝ` or `𝕜 = ℂ`, this map is actually an isometric embedding; we provide a
version `normed_space.inclusion_in_double_dual_li` of the map which is of type a bundled linear
isometric embedding, `E →ₗᵢ[𝕜] (dual 𝕜 (dual 𝕜 E))`.

Since a lot of elementary properties don't require `eq_of_dist_eq_zero` we start setting up the
theory for `seminormed_add_comm_group` and we specialize to `normed_add_comm_group` when needed.

## Main definitions

* `inclusion_in_double_dual` and `inclusion_in_double_dual_li` are the inclusion of a normed space
  in its double dual, considered as a bounded linear map and as a linear isometry, respectively.
* `polar 𝕜 s` is the subset of `dual 𝕜 E` consisting of those functionals `x'` for which
  `‖x' z‖ ≤ 1` for every `z ∈ s`.

## Tags

dual
-/

noncomputable section

open Classical Topology

universe u v

namespace NormedSpace

section General

variable (𝕜 : Type _) [NontriviallyNormedField 𝕜]

variable (E : Type _) [SeminormedAddCommGroup E] [NormedSpace 𝕜 E]

variable (F : Type _) [NormedAddCommGroup F] [NormedSpace 𝕜 F]

/- ./././Mathport/Syntax/Translate/Command.lean:42:9: unsupported derive handler normed_space[normed_space] 𝕜 -/
/-- The topological dual of a seminormed space `E`. -/
def Dual :=
  E →L[𝕜] 𝕜 deriving Inhabited, SeminormedAddCommGroup,
  «./././Mathport/Syntax/Translate/Command.lean:42:9: unsupported derive handler normed_space[normed_space] 𝕜»
#align normed_space.dual NormedSpace.Dual

instance : ContinuousLinearMapClass (Dual 𝕜 E) 𝕜 E 𝕜 :=
  ContinuousLinearMap.continuousSemilinearMapClass

instance : CoeFun (Dual 𝕜 E) fun _ => E → 𝕜 :=
  ContinuousLinearMap.toFun

instance : NormedAddCommGroup (Dual 𝕜 F) :=
  ContinuousLinearMap.toNormedAddCommGroup

instance [FiniteDimensional 𝕜 E] : FiniteDimensional 𝕜 (Dual 𝕜 E) :=
  ContinuousLinearMap.finiteDimensional

/-- The inclusion of a normed space in its double (topological) dual, considered
   as a bounded linear map. -/
def inclusionInDoubleDual : E →L[𝕜] Dual 𝕜 (Dual 𝕜 E) :=
  ContinuousLinearMap.apply 𝕜 𝕜
#align normed_space.inclusion_in_double_dual NormedSpace.inclusionInDoubleDual

@[simp]
theorem dual_def (x : E) (f : Dual 𝕜 E) : inclusionInDoubleDual 𝕜 E x f = f x :=
  rfl
#align normed_space.dual_def NormedSpace.dual_def

theorem inclusionInDoubleDual_norm_eq :
    ‖inclusionInDoubleDual 𝕜 E‖ = ‖ContinuousLinearMap.id 𝕜 (Dual 𝕜 E)‖ :=
  ContinuousLinearMap.op_norm_flip _
#align normed_space.inclusion_in_double_dual_norm_eq NormedSpace.inclusionInDoubleDual_norm_eq

theorem inclusionInDoubleDual_norm_le : ‖inclusionInDoubleDual 𝕜 E‖ ≤ 1 :=
  by
  rw [inclusion_in_double_dual_norm_eq]
  exact ContinuousLinearMap.norm_id_le
#align normed_space.inclusion_in_double_dual_norm_le NormedSpace.inclusionInDoubleDual_norm_le

theorem double_dual_bound (x : E) : ‖(inclusionInDoubleDual 𝕜 E) x‖ ≤ ‖x‖ := by
  simpa using ContinuousLinearMap.le_of_op_norm_le _ (inclusion_in_double_dual_norm_le 𝕜 E) x
#align normed_space.double_dual_bound NormedSpace.double_dual_bound

/-- The dual pairing as a bilinear form. -/
def dualPairing : Dual 𝕜 E →ₗ[𝕜] E →ₗ[𝕜] 𝕜 :=
  ContinuousLinearMap.coeLm 𝕜
#align normed_space.dual_pairing NormedSpace.dualPairing

@[simp]
theorem dualPairing_apply {v : Dual 𝕜 E} {x : E} : dualPairing 𝕜 E v x = v x :=
  rfl
#align normed_space.dual_pairing_apply NormedSpace.dualPairing_apply

theorem dualPairing_separatingLeft : (dualPairing 𝕜 E).SeparatingLeft :=
  by
  rw [LinearMap.separatingLeft_iff_ker_eq_bot, LinearMap.ker_eq_bot]
  exact ContinuousLinearMap.coe_injective
#align normed_space.dual_pairing_separating_left NormedSpace.dualPairing_separatingLeft

end General

section BidualIsometry

variable (𝕜 : Type v) [IsROrC 𝕜] {E : Type u} [NormedAddCommGroup E] [NormedSpace 𝕜 E]

/-- If one controls the norm of every `f x`, then one controls the norm of `x`.
    Compare `continuous_linear_map.op_norm_le_bound`. -/
theorem norm_le_dual_bound (x : E) {M : ℝ} (hMp : 0 ≤ M) (hM : ∀ f : Dual 𝕜 E, ‖f x‖ ≤ M * ‖f‖) :
    ‖x‖ ≤ M := by
  classical
    by_cases h : x = 0
    · simp only [h, hMp, norm_zero]
    · obtain ⟨f, hf₁, hfx⟩ : ∃ f : E →L[𝕜] 𝕜, ‖f‖ = 1 ∧ f x = ‖x‖ := exists_dual_vector 𝕜 x h
      calc
        ‖x‖ = ‖(‖x‖ : 𝕜)‖ := is_R_or_C.norm_coe_norm.symm
        _ = ‖f x‖ := by rw [hfx]
        _ ≤ M * ‖f‖ := (hM f)
        _ = M := by rw [hf₁, mul_one]

#align normed_space.norm_le_dual_bound NormedSpace.norm_le_dual_bound

theorem eq_zero_of_forall_dual_eq_zero {x : E} (h : ∀ f : Dual 𝕜 E, f x = (0 : 𝕜)) : x = 0 :=
  norm_le_zero_iff.mp (norm_le_dual_bound 𝕜 x le_rfl fun f => by simp [h f])
#align normed_space.eq_zero_of_forall_dual_eq_zero NormedSpace.eq_zero_of_forall_dual_eq_zero

theorem eq_zero_iff_forall_dual_eq_zero (x : E) : x = 0 ↔ ∀ g : Dual 𝕜 E, g x = 0 :=
  ⟨fun hx => by simp [hx], fun h => eq_zero_of_forall_dual_eq_zero 𝕜 h⟩
#align normed_space.eq_zero_iff_forall_dual_eq_zero NormedSpace.eq_zero_iff_forall_dual_eq_zero

/-- See also `geometric_hahn_banach_point_point`. -/
theorem eq_iff_forall_dual_eq {x y : E} : x = y ↔ ∀ g : Dual 𝕜 E, g x = g y :=
  by
  rw [← sub_eq_zero, eq_zero_iff_forall_dual_eq_zero 𝕜 (x - y)]
  simp [sub_eq_zero]
#align normed_space.eq_iff_forall_dual_eq NormedSpace.eq_iff_forall_dual_eq

/-- The inclusion of a normed space in its double dual is an isometry onto its image.-/
def inclusionInDoubleDualLi : E →ₗᵢ[𝕜] Dual 𝕜 (Dual 𝕜 E) :=
  { inclusionInDoubleDual 𝕜 E with
    norm_map' := by
      intro x
      apply le_antisymm
      · exact double_dual_bound 𝕜 E x
      rw [ContinuousLinearMap.norm_def]
      refine' le_cinfₛ ContinuousLinearMap.bounds_nonempty _
      rintro c ⟨hc1, hc2⟩
      exact norm_le_dual_bound 𝕜 x hc1 hc2 }
#align normed_space.inclusion_in_double_dual_li NormedSpace.inclusionInDoubleDualLi

end BidualIsometry

section PolarSets

open Metric Set NormedSpace

/-- Given a subset `s` in a normed space `E` (over a field `𝕜`), the polar
`polar 𝕜 s` is the subset of `dual 𝕜 E` consisting of those functionals which
evaluate to something of norm at most one at all points `z ∈ s`. -/
def polar (𝕜 : Type _) [NontriviallyNormedField 𝕜] {E : Type _} [SeminormedAddCommGroup E]
    [NormedSpace 𝕜 E] : Set E → Set (Dual 𝕜 E) :=
  (dualPairing 𝕜 E).flip.polar
#align normed_space.polar NormedSpace.polar

variable (𝕜 : Type _) [NontriviallyNormedField 𝕜]

variable {E : Type _} [SeminormedAddCommGroup E] [NormedSpace 𝕜 E]

theorem mem_polar_iff {x' : Dual 𝕜 E} (s : Set E) : x' ∈ polar 𝕜 s ↔ ∀ z ∈ s, ‖x' z‖ ≤ 1 :=
  Iff.rfl
#align normed_space.mem_polar_iff NormedSpace.mem_polar_iff

@[simp]
theorem polar_univ : polar 𝕜 (univ : Set E) = {(0 : dual 𝕜 E)} :=
  (dualPairing 𝕜 E).flip.polar_univ
    (LinearMap.flip_separatingRight.mpr (dualPairing_separatingLeft 𝕜 E))
#align normed_space.polar_univ NormedSpace.polar_univ

theorem isClosed_polar (s : Set E) : IsClosed (polar 𝕜 s) :=
  by
  dsimp only [NormedSpace.polar]
  simp only [LinearMap.polar_eq_interᵢ, LinearMap.flip_apply]
  refine' isClosed_binterᵢ fun z hz => _
  exact is_closed_Iic.preimage (ContinuousLinearMap.apply 𝕜 𝕜 z).Continuous.norm
#align normed_space.is_closed_polar NormedSpace.isClosed_polar

@[simp]
theorem polar_closure (s : Set E) : polar 𝕜 (closure s) = polar 𝕜 s :=
  ((dualPairing 𝕜 E).flip.polar_antitone subset_closure).antisymm <|
    (dualPairing 𝕜 E).flip.polar_gc.l_le <|
      closure_minimal ((dualPairing 𝕜 E).flip.polar_gc.le_u_l s) <| by
        simpa [LinearMap.flip_flip] using
          (is_closed_polar _ _).Preimage (inclusion_in_double_dual 𝕜 E).Continuous
#align normed_space.polar_closure NormedSpace.polar_closure

variable {𝕜}

/-- If `x'` is a dual element such that the norms `‖x' z‖` are bounded for `z ∈ s`, then a
small scalar multiple of `x'` is in `polar 𝕜 s`. -/
theorem smul_mem_polar {s : Set E} {x' : Dual 𝕜 E} {c : 𝕜} (hc : ∀ z, z ∈ s → ‖x' z‖ ≤ ‖c‖) :
    c⁻¹ • x' ∈ polar 𝕜 s := by
  by_cases c_zero : c = 0
  · simp only [c_zero, inv_zero, zero_smul]
    exact (dual_pairing 𝕜 E).flip.zero_mem_polar _
  have eq : ∀ z, ‖c⁻¹ • x' z‖ = ‖c⁻¹‖ * ‖x' z‖ := fun z => norm_smul c⁻¹ _
  have le : ∀ z, z ∈ s → ‖c⁻¹ • x' z‖ ≤ ‖c⁻¹‖ * ‖c‖ :=
    by
    intro z hzs
    rw [Eq z]
    apply mul_le_mul (le_of_eq rfl) (hc z hzs) (norm_nonneg _) (norm_nonneg _)
  have cancel : ‖c⁻¹‖ * ‖c‖ = 1 := by
    simp only [c_zero, norm_eq_zero, Ne.def, not_false_iff, inv_mul_cancel, norm_inv]
  rwa [cancel] at le
#align normed_space.smul_mem_polar NormedSpace.smul_mem_polar

theorem polar_ball_subset_closedBall_div {c : 𝕜} (hc : 1 < ‖c‖) {r : ℝ} (hr : 0 < r) :
    polar 𝕜 (ball (0 : E) r) ⊆ closedBall (0 : Dual 𝕜 E) (‖c‖ / r) :=
  by
  intro x' hx'
  rw [mem_polar_iff] at hx'
  simp only [polar, mem_set_of_eq, mem_closedBall_zero_iff, mem_ball_zero_iff] at *
  have hcr : 0 < ‖c‖ / r := div_pos (zero_lt_one.trans hc) hr
  refine' ContinuousLinearMap.op_norm_le_of_shell hr hcr.le hc fun x h₁ h₂ => _
  calc
    ‖x' x‖ ≤ 1 := hx' _ h₂
    _ ≤ ‖c‖ / r * ‖x‖ := (inv_pos_le_iff_one_le_mul' hcr).1 (by rwa [inv_div])

#align normed_space.polar_ball_subset_closed_ball_div NormedSpace.polar_ball_subset_closedBall_div

variable (𝕜)

theorem closedBall_inv_subset_polar_closedBall {r : ℝ} :
    closedBall (0 : Dual 𝕜 E) r⁻¹ ⊆ polar 𝕜 (closedBall (0 : E) r) := fun x' hx' x hx =>
  calc
    ‖x' x‖ ≤ ‖x'‖ * ‖x‖ := x'.le_opNorm x
    _ ≤ r⁻¹ * r :=
      (mul_le_mul (mem_closedBall_zero_iff.1 hx') (mem_closedBall_zero_iff.1 hx) (norm_nonneg _)
        (dist_nonneg.trans hx'))
    _ = r / r := (inv_mul_eq_div _ _)
    _ ≤ 1 := div_self_le_one r

#align normed_space.closed_ball_inv_subset_polar_closed_ball NormedSpace.closedBall_inv_subset_polar_closedBall

/-- The `polar` of closed ball in a normed space `E` is the closed ball of the dual with
inverse radius. -/
theorem polar_closedBall {𝕜 E : Type _} [IsROrC 𝕜] [NormedAddCommGroup E] [NormedSpace 𝕜 E] {r : ℝ}
    (hr : 0 < r) : polar 𝕜 (closedBall (0 : E) r) = closedBall (0 : Dual 𝕜 E) r⁻¹ :=
  by
  refine' subset.antisymm _ (closed_ball_inv_subset_polar_closed_ball _)
  intro x' h
  simp only [mem_closedBall_zero_iff]
  refine' ContinuousLinearMap.op_norm_le_of_ball hr (inv_nonneg.mpr hr.le) fun z hz => _
  simpa only [one_div] using LinearMap.bound_of_ball_bound' hr 1 x'.to_linear_map h z
#align normed_space.polar_closed_ball NormedSpace.polar_closedBall

/-- Given a neighborhood `s` of the origin in a normed space `E`, the dual norms
of all elements of the polar `polar 𝕜 s` are bounded by a constant. -/
theorem bounded_polar_of_mem_nhds_zero {s : Set E} (s_nhd : s ∈ 𝓝 (0 : E)) : Bounded (polar 𝕜 s) :=
  by
  obtain ⟨a, ha⟩ : ∃ a : 𝕜, 1 < ‖a‖ := NormedField.exists_one_lt_norm 𝕜
  obtain ⟨r, r_pos, r_ball⟩ : ∃ (r : ℝ)(hr : 0 < r), ball 0 r ⊆ s := Metric.mem_nhds_iff.1 s_nhd
  exact
    bounded_closed_ball.mono
      (((dual_pairing 𝕜 E).flip.polar_antitone r_ball).trans <|
        polar_ball_subset_closed_ball_div ha r_pos)
#align normed_space.bounded_polar_of_mem_nhds_zero NormedSpace.bounded_polar_of_mem_nhds_zero

end PolarSets

end NormedSpace


/-
Copyright (c) 2020 Yury Kudryashov All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yury Kudryashov, Frédéric Dupuis
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.linear_algebra.linear_pmap
import Mathlib.analysis.convex.basic
import Mathlib.order.zorn
import Mathlib.PostPort

universes u_1 l u_2 u_3 u_4

namespace Mathlib

/-!
# Convex cones

In a vector space `E` over `ℝ`, we define a convex cone as a subset `s` such that
`a • x + b • y ∈ s` whenever `x, y ∈ s` and `a, b > 0`. We prove that convex cones form
a `complete_lattice`, and define their images (`convex_cone.map`) and preimages
(`convex_cone.comap`) under linear maps.

We define pointed, blunt, flat and salient cones, and prove the correspondence between
convex cones and ordered semimodules.

We also define `convex.to_cone` to be the minimal cone that includes a given convex set.

## Main statements

We prove two extension theorems:

* `riesz_extension`:
  [M. Riesz extension theorem](https://en.wikipedia.org/wiki/M._Riesz_extension_theorem) says that
  if `s` is a convex cone in a real vector space `E`, `p` is a submodule of `E`
  such that `p + s = E`, and `f` is a linear function `p → ℝ` which is
  nonnegative on `p ∩ s`, then there exists a globally defined linear function
  `g : E → ℝ` that agrees with `f` on `p`, and is nonnegative on `s`.

* `exists_extension_of_le_sublinear`:
  Hahn-Banach theorem: if `N : E → ℝ` is a sublinear map, `f` is a linear map
  defined on a subspace of `E`, and `f x ≤ N x` for all `x` in the domain of `f`,
  then `f` can be extended to the whole space to a linear map `g` such that `g x ≤ N x`
  for all `x`

## Implementation notes

While `convex` is a predicate on sets, `convex_cone` is a bundled convex cone.

## References

* https://en.wikipedia.org/wiki/Convex_cone

## TODO

* Define the dual cone.
-/

/-!
### Definition of `convex_cone` and basic properties
-/

/-- A convex cone is a subset `s` of a vector space over `ℝ` such that `a • x + b • y ∈ s`
whenever `a, b > 0` and `x, y ∈ s`. -/
structure convex_cone (E : Type u_1) [add_comm_group E] [vector_space ℝ E] where
  carrier : set E
  smul_mem' : ∀ {c : ℝ}, 0 < c → ∀ {x : E}, x ∈ carrier → c • x ∈ carrier
  add_mem' : ∀ {x : E}, x ∈ carrier → ∀ {y : E}, y ∈ carrier → x + y ∈ carrier

namespace convex_cone

protected instance set.has_coe {E : Type u_1} [add_comm_group E] [vector_space ℝ E] :
    has_coe (convex_cone E) (set E) :=
  has_coe.mk carrier

protected instance has_mem {E : Type u_1} [add_comm_group E] [vector_space ℝ E] :
    has_mem E (convex_cone E) :=
  has_mem.mk fun (m : E) (S : convex_cone E) => m ∈ carrier S

protected instance has_le {E : Type u_1} [add_comm_group E] [vector_space ℝ E] :
    HasLessEq (convex_cone E) :=
  { LessEq := fun (S T : convex_cone E) => carrier S ⊆ carrier T }

protected instance has_lt {E : Type u_1} [add_comm_group E] [vector_space ℝ E] :
    HasLess (convex_cone E) :=
  { Less := fun (S T : convex_cone E) => carrier S ⊂ carrier T }

@[simp] theorem mem_coe {E : Type u_1} [add_comm_group E] [vector_space ℝ E] (S : convex_cone E)
    {x : E} : x ∈ ↑S ↔ x ∈ S :=
  iff.rfl

@[simp] theorem mem_mk {E : Type u_1} [add_comm_group E] [vector_space ℝ E] {s : set E}
    {h₁ : ∀ {c : ℝ}, 0 < c → ∀ {x : E}, x ∈ s → c • x ∈ s}
    {h₂ : ∀ {x : E}, x ∈ s → ∀ {y : E}, y ∈ s → x + y ∈ s} {x : E} : x ∈ mk s h₁ h₂ ↔ x ∈ s :=
  iff.rfl

/-- Two `convex_cone`s are equal if the underlying subsets are equal. -/
theorem ext' {E : Type u_1} [add_comm_group E] [vector_space ℝ E] {S : convex_cone E}
    {T : convex_cone E} (h : ↑S = ↑T) : S = T :=
  sorry

/-- Two `convex_cone`s are equal if and only if the underlying subsets are equal. -/
protected theorem ext'_iff {E : Type u_1} [add_comm_group E] [vector_space ℝ E] {S : convex_cone E}
    {T : convex_cone E} : ↑S = ↑T ↔ S = T :=
  { mp := ext', mpr := fun (h : S = T) => h ▸ rfl }

/-- Two `convex_cone`s are equal if they have the same elements. -/
theorem ext {E : Type u_1} [add_comm_group E] [vector_space ℝ E] {S : convex_cone E}
    {T : convex_cone E} (h : ∀ (x : E), x ∈ S ↔ x ∈ T) : S = T :=
  ext' (set.ext h)

theorem smul_mem {E : Type u_1} [add_comm_group E] [vector_space ℝ E] (S : convex_cone E) {c : ℝ}
    {x : E} (hc : 0 < c) (hx : x ∈ S) : c • x ∈ S :=
  smul_mem' S hc hx

theorem add_mem {E : Type u_1} [add_comm_group E] [vector_space ℝ E] (S : convex_cone E) {x : E}
    (hx : x ∈ S) {y : E} (hy : y ∈ S) : x + y ∈ S :=
  add_mem' S hx hy

theorem smul_mem_iff {E : Type u_1} [add_comm_group E] [vector_space ℝ E] (S : convex_cone E)
    {c : ℝ} (hc : 0 < c) {x : E} : c • x ∈ S ↔ x ∈ S :=
  sorry

theorem convex {E : Type u_1} [add_comm_group E] [vector_space ℝ E] (S : convex_cone E) :
    convex ↑S :=
  iff.mpr convex_iff_forall_pos
    fun (x y : E) (hx : x ∈ ↑S) (hy : y ∈ ↑S) (a b : ℝ) (ha : 0 < a) (hb : 0 < b)
      (hab : a + b = 1) => add_mem S (smul_mem S ha hx) (smul_mem S hb hy)

protected instance has_inf {E : Type u_1} [add_comm_group E] [vector_space ℝ E] :
    has_inf (convex_cone E) :=
  has_inf.mk fun (S T : convex_cone E) => mk (↑S ∩ ↑T) sorry sorry

theorem coe_inf {E : Type u_1} [add_comm_group E] [vector_space ℝ E] (S : convex_cone E)
    (T : convex_cone E) : ↑(S ⊓ T) = ↑S ∩ ↑T :=
  rfl

theorem mem_inf {E : Type u_1} [add_comm_group E] [vector_space ℝ E] (S : convex_cone E)
    (T : convex_cone E) {x : E} : x ∈ S ⊓ T ↔ x ∈ S ∧ x ∈ T :=
  iff.rfl

protected instance has_Inf {E : Type u_1} [add_comm_group E] [vector_space ℝ E] :
    has_Inf (convex_cone E) :=
  has_Inf.mk
    fun (S : set (convex_cone E)) =>
      mk (set.Inter fun (s : convex_cone E) => set.Inter fun (H : s ∈ S) => ↑s) sorry sorry

theorem mem_Inf {E : Type u_1} [add_comm_group E] [vector_space ℝ E] {x : E}
    {S : set (convex_cone E)} : x ∈ Inf S ↔ ∀ (s : convex_cone E), s ∈ S → x ∈ s :=
  set.mem_bInter_iff

protected instance has_bot {E : Type u_1} [add_comm_group E] [vector_space ℝ E] :
    has_bot (convex_cone E) :=
  has_bot.mk (mk ∅ sorry sorry)

theorem mem_bot {E : Type u_1} [add_comm_group E] [vector_space ℝ E] (x : E) : x ∈ ⊥ = False := rfl

protected instance has_top {E : Type u_1} [add_comm_group E] [vector_space ℝ E] :
    has_top (convex_cone E) :=
  has_top.mk (mk set.univ sorry sorry)

theorem mem_top {E : Type u_1} [add_comm_group E] [vector_space ℝ E] (x : E) : x ∈ ⊤ :=
  set.mem_univ x

protected instance complete_lattice {E : Type u_1} [add_comm_group E] [vector_space ℝ E] :
    complete_lattice (convex_cone E) :=
  complete_lattice.mk
    (fun (a b : convex_cone E) => Inf (set_of fun (x : convex_cone E) => a ≤ x ∧ b ≤ x)) LessEq Less
    sorry sorry sorry sorry sorry sorry has_inf.inf sorry sorry sorry ⊤ sorry ⊥ sorry
    (fun (s : set (convex_cone E)) =>
      Inf (set_of fun (T : convex_cone E) => ∀ (S : convex_cone E), S ∈ s → S ≤ T))
    Inf sorry sorry sorry sorry

protected instance inhabited {E : Type u_1} [add_comm_group E] [vector_space ℝ E] :
    Inhabited (convex_cone E) :=
  { default := ⊥ }

/-- The image of a convex cone under an `ℝ`-linear map is a convex cone. -/
def map {E : Type u_1} [add_comm_group E] [vector_space ℝ E] {F : Type u_2} [add_comm_group F]
    [vector_space ℝ F] (f : linear_map ℝ E F) (S : convex_cone E) : convex_cone F :=
  mk (⇑f '' ↑S) sorry sorry

theorem map_map {E : Type u_1} [add_comm_group E] [vector_space ℝ E] {F : Type u_2}
    [add_comm_group F] [vector_space ℝ F] {G : Type u_3} [add_comm_group G] [vector_space ℝ G]
    (g : linear_map ℝ F G) (f : linear_map ℝ E F) (S : convex_cone E) :
    map g (map f S) = map (linear_map.comp g f) S :=
  ext' (set.image_image ⇑g ⇑f ↑S)

@[simp] theorem map_id {E : Type u_1} [add_comm_group E] [vector_space ℝ E] (S : convex_cone E) :
    map linear_map.id S = S :=
  ext' (set.image_id ↑S)

/-- The preimage of a convex cone under an `ℝ`-linear map is a convex cone. -/
def comap {E : Type u_1} [add_comm_group E] [vector_space ℝ E] {F : Type u_2} [add_comm_group F]
    [vector_space ℝ F] (f : linear_map ℝ E F) (S : convex_cone F) : convex_cone E :=
  mk (⇑f ⁻¹' ↑S) sorry sorry

@[simp] theorem comap_id {E : Type u_1} [add_comm_group E] [vector_space ℝ E] (S : convex_cone E) :
    comap linear_map.id S = S :=
  ext' set.preimage_id

theorem comap_comap {E : Type u_1} [add_comm_group E] [vector_space ℝ E] {F : Type u_2}
    [add_comm_group F] [vector_space ℝ F] {G : Type u_3} [add_comm_group G] [vector_space ℝ G]
    (g : linear_map ℝ F G) (f : linear_map ℝ E F) (S : convex_cone G) :
    comap f (comap g S) = comap (linear_map.comp g f) S :=
  ext' (Eq.symm set.preimage_comp)

@[simp] theorem mem_comap {E : Type u_1} [add_comm_group E] [vector_space ℝ E] {F : Type u_2}
    [add_comm_group F] [vector_space ℝ F] {f : linear_map ℝ E F} {S : convex_cone F} {x : E} :
    x ∈ comap f S ↔ coe_fn f x ∈ S :=
  iff.rfl

/--
Constructs an ordered semimodule given an `ordered_add_comm_group`, a cone, and a proof that
the order relation is the one defined by the cone.
-/
theorem to_ordered_semimodule {M : Type u_1} [ordered_add_comm_group M] [semimodule ℝ M]
    (S : convex_cone M) (h : ∀ (x y : M), x ≤ y ↔ y - x ∈ S) : ordered_semimodule ℝ M :=
  sorry

/-! ### Convex cones with extra properties -/

/-- A convex cone is pointed if it includes 0. -/
def pointed {E : Type u_1} [add_comm_group E] [vector_space ℝ E] (S : convex_cone E) := 0 ∈ S

/-- A convex cone is blunt if it doesn't include 0. -/
def blunt {E : Type u_1} [add_comm_group E] [vector_space ℝ E] (S : convex_cone E) := ¬0 ∈ S

/-- A convex cone is flat if it contains some nonzero vector `x` and its opposite `-x`. -/
def flat {E : Type u_1} [add_comm_group E] [vector_space ℝ E] (S : convex_cone E) :=
  ∃ (x : E), ∃ (H : x ∈ S), x ≠ 0 ∧ -x ∈ S

/-- A convex cone is salient if it doesn't include `x` and `-x` for any nonzero `x`. -/
def salient {E : Type u_1} [add_comm_group E] [vector_space ℝ E] (S : convex_cone E) :=
  ∀ (x : E), x ∈ S → x ≠ 0 → ¬-x ∈ S

theorem pointed_iff_not_blunt {E : Type u_1} [add_comm_group E] [vector_space ℝ E]
    (S : convex_cone E) : pointed S ↔ ¬blunt S :=
  { mp := fun (h₁ : pointed S) (h₂ : blunt S) => h₂ h₁,
    mpr := fun (h : ¬blunt S) => iff.mp not_not h }

theorem salient_iff_not_flat {E : Type u_1} [add_comm_group E] [vector_space ℝ E]
    (S : convex_cone E) : salient S ↔ ¬flat S :=
  sorry

/-- A blunt cone (one not containing 0) is always salient. -/
theorem salient_of_blunt {E : Type u_1} [add_comm_group E] [vector_space ℝ E] (S : convex_cone E) :
    blunt S → salient S :=
  sorry

/-- A pointed convex cone defines a preorder. -/
def to_preorder {E : Type u_1} [add_comm_group E] [vector_space ℝ E] (S : convex_cone E)
    (h₁ : pointed S) : preorder E :=
  preorder.mk (fun (x y : E) => y - x ∈ S) (fun (a b : E) => b - a ∈ S ∧ ¬a - b ∈ S) sorry sorry

/-- A pointed and salient cone defines a partial order. -/
def to_partial_order {E : Type u_1} [add_comm_group E] [vector_space ℝ E] (S : convex_cone E)
    (h₁ : pointed S) (h₂ : salient S) : partial_order E :=
  partial_order.mk preorder.le preorder.lt sorry sorry sorry

/-- A pointed and salient cone defines an `ordered_add_comm_group`. -/
def to_ordered_add_comm_group {E : Type u_1} [add_comm_group E] [vector_space ℝ E]
    (S : convex_cone E) (h₁ : pointed S) (h₂ : salient S) : ordered_add_comm_group E :=
  ordered_add_comm_group.mk add_comm_group.add sorry add_comm_group.zero sorry sorry
    add_comm_group.neg add_comm_group.sub sorry sorry partial_order.le partial_order.lt sorry sorry
    sorry sorry

/-! ### Positive cone of an ordered semimodule -/

/--
The positive cone is the convex cone formed by the set of nonnegative elements in an ordered
semimodule.
-/
def positive_cone (M : Type u_4) [ordered_add_comm_group M] [semimodule ℝ M]
    [ordered_semimodule ℝ M


import .lia.main

example : ∃ (z : int), z + 5 = 1223 := by lia
example : ∃ (x : int), 3 ∣ (x + 5)  := by lia

example : ∀ x : int, 31 * x > 0 → x > 0 := by lia
example : ∀ x y : int, (x ≤ 5 ∧ y ≤ 3) → x + y ≤ 8 := by lia
example : ∀ x y : int, ¬(2 * x + 1 = 2 * y) := by lia
example : ∀ x y : int, x + 2 = y → ∃ z : int, z + 1 = y := by lia

example : ∃ x y : int, 7 * x = 5 * y := by lia
example : ∃ x y : int, 5 * x + 3 * y = 11 := by lia
example : ∀ x : int, x < 349 ∨ x > 123 := by lia
example : ∀ x : int, ∃ y : int, x = 2 * y ∨ x = (2 * y) + 1 := by lia
example : ∀ x y : int, ∃ z : int, (x < z ∧ y < z) := by lia

example : ∀ x y z w : int, x ≤ y → y ≤ z → z ≤ w → x ≤ w := by lia
example : ∀ x y : int, x ≤ 3 * y → 3 * x ≤ 9 * y := by lia
example : ∀ x y : int, x + 2 < y → ∃ z w : int, (x < z ∧ z < w ∧ w < y) := by lia
example : ∀ x y : int, 6 * x = 5 * y → ∃ z : int, y = 2 * z := by lia

-- Large numbers can be handled with lia_vm, but requires you to trust VM evaluation
example : ∀ x : int, x > 5000 → ∃ y : int, y ≥ 1000 ∧ 5 * y < x := by lia_vm


/-
Copyright (c) 2022 Robert Y. Lewis. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Robert Y. Lewis, Heather Macbeth

! This file was ported from Lean 3 source module ring_theory.witt_vector.mul_coeff
! leanprover-community/mathlib commit 43a289b563d533cfb00f58b812d222371f6f6fac
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.RingTheory.WittVector.Truncated
import Mathbin.Data.MvPolynomial.Supported

/-!
# Leading terms of Witt vector multiplication

The goal of this file is to study the leading terms of the formula for the `n+1`st coefficient
of a product of Witt vectors `x` and `y` over a ring of characteristic `p`.
We aim to isolate the `n+1`st coefficients of `x` and `y`, and express the rest of the product
in terms of a function of the lower coefficients.

For most of this file we work with terms of type `mv_polynomial (fin 2 × ℕ) ℤ`.
We will eventually evaluate them in `k`, but first we must take care of a calculation
that needs to happen in characteristic 0.

## Main declarations

* `witt_vector.nth_mul_coeff`: expresses the coefficient of a product of Witt vectors
  in terms of the previous coefficients of the multiplicands.

-/

noncomputable section

namespace WittVector

variable (p : ℕ) [hp : Fact p.Prime]

variable {k : Type _} [CommRing k]

-- mathport name: expr𝕎
local notation "𝕎" => WittVector p

open Finset MvPolynomial

open BigOperators

/-- ```
(∑ i in range n, (y.coeff i)^(p^(n-i)) * p^i.val) *
  (∑ i in range n, (y.coeff i)^(p^(n-i)) * p^i.val)
```
-/
def wittPolyProd (n : ℕ) : MvPolynomial (Fin 2 × ℕ) ℤ :=
  rename (Prod.mk (0 : Fin 2)) (wittPolynomial p ℤ n) *
    rename (Prod.mk (1 : Fin 2)) (wittPolynomial p ℤ n)
#align witt_vector.witt_poly_prod WittVector.wittPolyProd

include hp

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
theorem wittPolyProd_vars (n : ℕ) : (wittPolyProd p n).vars ⊆ univ ×ˢ range (n + 1) :=
  by
  rw [witt_poly_prod]
  apply subset.trans (vars_mul _ _)
  apply union_subset <;>
    · apply subset.trans (vars_rename _ _)
      simp [wittPolynomial_vars, image_subset_iff]
#align witt_vector.witt_poly_prod_vars WittVector.wittPolyProd_vars

/-- The "remainder term" of `witt_vector.witt_poly_prod`. See `mul_poly_of_interest_aux2`. -/
def wittPolyProdRemainder (n : ℕ) : MvPolynomial (Fin 2 × ℕ) ℤ :=
  ∑ i in range n, p ^ i * wittMul p i ^ p ^ (n - i)
#align witt_vector.witt_poly_prod_remainder WittVector.wittPolyProdRemainder

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
theorem wittPolyProdRemainder_vars (n : ℕ) : (wittPolyProdRemainder p n).vars ⊆ univ ×ˢ range n :=
  by
  rw [witt_poly_prod_remainder]
  apply subset.trans (vars_sum_subset _ _)
  rw [bUnion_subset]
  intro x hx
  apply subset.trans (vars_mul _ _)
  apply union_subset
  · apply subset.trans (vars_pow _ _)
    have : (p : MvPolynomial (Fin 2 × ℕ) ℤ) = C (p : ℤ) := by simp only [Int.cast_ofNat, eq_intCast]
    rw [this, vars_C]
    apply empty_subset
  · apply subset.trans (vars_pow _ _)
    apply subset.trans (witt_mul_vars _ _)
    apply product_subset_product (subset.refl _)
    simp only [mem_range, range_subset] at hx⊢
    exact hx
#align witt_vector.witt_poly_prod_remainder_vars WittVector.wittPolyProdRemainder_vars

omit hp

/-- `remainder p n` represents the remainder term from `mul_poly_of_interest_aux3`.
`witt_poly_prod p (n+1)` will have variables up to `n+1`,
but `remainder` will only have variables up to `n`.
-/
def remainder (n : ℕ) : MvPolynomial (Fin 2 × ℕ) ℤ :=
  (∑ x : ℕ in range (n + 1),
      (rename (Prod.mk 0)) ((monomial (Finsupp.single x (p ^ (n + 1 - x)))) (↑p ^ x))) *
    ∑ x : ℕ in range (n + 1),
      (rename (Prod.mk 1)) ((monomial (Finsupp.single x (p ^ (n + 1 - x)))) (↑p ^ x))
#align witt_vector.remainder WittVector.remainder

include hp

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
theorem remainder_vars (n : ℕ) : (remainder p n).vars ⊆ univ ×ˢ range (n + 1) :=
  by
  rw [remainder]
  apply subset.trans (vars_mul _ _)
  apply union_subset <;>
    · apply subset.trans (vars_sum_subset _ _)
      rw [bUnion_subset]
      intro x hx
      rw [rename_monomial, vars_monomial, Finsupp.mapDomain_single]
      · apply subset.trans Finsupp.support_single_subset
        simp [hx]
      · apply pow_ne_zero
        exact_mod_cast hp.out.ne_zero
#align witt_vector.remainder_vars WittVector.remainder_vars

/-- This is the polynomial whose degree we want to get a handle on. -/
def polyOfInterest (n : ℕ) : MvPolynomial (Fin 2 × ℕ) ℤ :=
  wittMul p (n + 1) + p ^ (n + 1) * X (0, n + 1) * X (1, n + 1) -
      X (0, n + 1) * rename (Prod.mk (1 : Fin 2)) (wittPolynomial p ℤ (n + 1)) -
    X (1, n + 1) * rename (Prod.mk (0 : Fin 2)) (wittPolynomial p ℤ (n + 1))
#align witt_vector.poly_of_interest WittVector.polyOfInterest

theorem mul_poly_of_interest_aux1 (n : ℕ) :
    (∑ i in range (n + 1), p ^ i * wittMul p i ^ p ^ (n - i) : MvPolynomial (Fin 2 × ℕ) ℤ) =
      wittPolyProd p n :=
  by
  simp only [witt_poly_prod]
  convert wittStructureInt_prop p (X (0 : Fin 2) * X 1) n using 1
  · simp only [wittPolynomial, witt_mul]
    rw [AlgHom.map_sum]
    congr 1 with i
    congr 1
    have hsupp : (Finsupp.single i (p ^ (n - i))).support = {i} :=
      by
      rw [Finsupp.support_eq_singleton]
      simp only [and_true_iff, Finsupp.single_eq_same, eq_self_iff_true, Ne.def]
      exact pow_ne_zero _ hp.out.ne_zero
    simp only [bind₁_monomial, hsupp, Int.cast_ofNat, prod_singleton, eq_intCast,
      Finsupp.single_eq_same, C_pow, mul_eq_mul_left_iff, true_or_iff, eq_self_iff_true]
  · simp only [map_mul, bind₁_X_right]
#align witt_vector.mul_poly_of_interest_aux1 WittVector.mul_poly_of_interest_aux1

theorem mul_poly_of_interest_aux2 (n : ℕ) :
    (p ^ n * wittMul p n : MvPolynomial (Fin 2 × ℕ) ℤ) + wittPolyProdRemainder p n =
      wittPolyProd p n :=
  by
  convert mul_poly_of_interest_aux1 p n
  rw [sum_range_succ, add_comm, Nat.sub_self, pow_zero, pow_one]
  rfl
#align witt_vector.mul_poly_of_interest_aux2 WittVector.mul_poly_of_interest_aux2

omit hp

theorem mul_poly_of_interest_aux3 (n : ℕ) :
    wittPolyProd p (n + 1) =
      -(p ^ (n + 1) * X (0, n + 1)) * (p ^ (n + 1) * X (1, n + 1)) +
            p ^ (n + 1) * X (0, n + 1) * rename (Prod.mk (1 : Fin 2)) (wittPolynomial p ℤ (n + 1)) +
          p ^ (n + 1) * X (1, n + 1) * rename (Prod.mk (0 : Fin 2)) (wittPolynomial p ℤ (n + 1)) +
        remainder p n :=
  by
  -- a useful auxiliary fact
  have mvpz : (p ^ (n + 1) : MvPolynomial (Fin 2 × ℕ) ℤ) = MvPolynomial.C (↑p ^ (n + 1)) := by
    simp only [Int.cast_ofNat, eq_intCast, C_pow, eq_self_iff_true]
  -- unfold definitions and peel off the last entries of the sums.
  rw [witt_poly_prod, wittPolynomial, AlgHom.map_sum, AlgHom.map_sum, sum_range_succ]
  -- these are sums up to `n+2`, so be careful to only unfold to `n+1`.
  conv_lhs =>
    congr
    skip
    rw [sum_range_succ]
  simp only [add_mul, mul_add, tsub_self, pow_zero, AlgHom.map_sum]
  -- rearrange so that the first summand on rhs and lhs is `remainder`, and peel off
  conv_rhs => rw [add_comm]
  simp only [add_assoc]
  apply congr_arg (Add.add _)
  conv_rhs => rw [sum_range_succ]
  -- the rest is equal with proper unfolding and `ring`
  simp only [rename_monomial, ← C_mul_X_pow_eq_monomial, map_mul, rename_C, pow_one, rename_X]
  simp only [mvpz, Int.cast_ofNat, map_pow, eq_intCast, rename_X, pow_one, tsub_self, pow_zero]
  ring1
#align witt_vector.mul_poly_of_interest_aux3 WittVector.mul_poly_of_interest_aux3

include hp

theorem mul_poly_of_interest_aux4 (n : ℕ) :
    (p ^ (n + 1) * wittMul p (n + 1) : MvPolynomial (Fin 2 × ℕ) ℤ) =
      -(p ^ (n + 1) * X (0, n + 1)) * (p ^ (n + 1) * X (1, n + 1)) +
            p ^ (n + 1) * X (0, n + 1) * rename (Prod.mk (1 : Fin 2)) (wittPolynomial p ℤ (n + 1)) +
          p ^ (n + 1) * X (1, n + 1) * rename (Prod.mk (0 : Fin 2)) (wittPolynomial p ℤ (n + 1)) +
        (remainder p n - wittPolyProdRemainder p (n + 1)) :=
  by
  rw [← add_sub_assoc, eq_sub_iff_add_eq, mul_poly_of_interest_aux2]
  exact mul_poly_of_interest_aux3 _ _
#align witt_vector.mul_poly_of_interest_aux4 WittVector.mul_poly_of_interest_aux4

theorem mul_polyOfInterest_aux5 (n : ℕ) :
    (p ^ (n + 1) : MvPolynomial (Fin 2 × ℕ) ℤ) * polyOfInterest p n =
      remainder p n - wittPolyProdRemainder p (n + 1) :=
  by
  simp only [poly_of_interest, mul_sub, mul_add, sub_eq_iff_eq_add']
  rw [mul_poly_of_interest_aux4 p n]
  ring
#align witt_vector.mul_poly_of_interest_aux5 WittVector.mul_polyOfInterest_aux5

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
theorem mul_polyOfInterest_vars (n : ℕ) :
    ((p ^ (n + 1) : MvPolynomial (Fin 2 × ℕ) ℤ) * polyOfInterest p n).vars ⊆
      univ ×ˢ range (n + 1) :=
  by
  rw [mul_poly_of_interest_aux5]
  apply subset.trans (vars_sub_subset _ _)
  apply union_subset
  · apply remainder_vars
  · apply witt_poly_prod_remainder_vars
#align witt_vector.mul_poly_of_interest_vars WittVector.mul_polyOfInterest_vars

theorem polyOfInterest_vars_eq (n : ℕ) :
    (polyOfInterest p n).vars =
      ((p ^ (n + 1) : MvPolynomial (Fin 2 × ℕ) ℤ) *
          (wittMul p (n + 1) + p ^ (n + 1) * X (0, n + 1) * X (1, n + 1) -
              X (0, n + 1) * rename (Prod.mk (1 : Fin 2)) (wittPolynomial p ℤ (n + 1)) -
            X (1, n + 1) * rename (Prod.mk (0 : Fin 2)) (wittPolynomial p ℤ (n + 1)))).vars :=
  by
  have : (p ^ (n + 1) : MvPolynomial (Fin 2 × ℕ) ℤ) = C (p ^ (n + 1) : ℤ) := by
    simp only [Int.cast_ofNat, eq_intCast, C_pow, eq_self_iff_true]
  rw [poly_of_interest, this, vars_C_mul]
  apply pow_ne_zero
  exact_mod_cast hp.out.ne_zero
#align witt_vector.poly_of_interest_vars_eq WittVector.polyOfInterest_vars_eq

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
theorem polyOfInterest_vars (n : ℕ) : (polyOfInterest p n).vars ⊆ univ ×ˢ range (n + 1) := by
  rw [poly_of_interest_vars_eq] <;> apply mul_poly_of_interest_vars
#align witt_vector.poly_of_interest_vars WittVector.polyOfInterest_vars

theorem peval_polyOfInterest (n : ℕ) (x y : 𝕎 k) :
    peval (polyOfInterest p n) ![fun i => x.coeff i, fun i => y.coeff i] =
      ((x * y).coeff (n + 1) + p ^ (n + 1) * x.coeff (n + 1) * y.coeff (n + 1) -
          y.coeff (n + 1) * ∑ i in range (n + 1 + 1), p ^ i * x.coeff i ^ p ^ (n + 1 - i)) -
        x.coeff (n + 1) * ∑ i in range (n + 1 + 1), p ^ i * y.coeff i ^ p ^ (n + 1 - i) :=
  by
  simp only [poly_of_interest, peval, map_natCast, Matrix.head_cons, map_pow,
    Function.uncurry_apply_pair, aeval_X, Matrix.cons_val_one, map_mul, Matrix.cons_val_zero,
    map_sub]
  rw [sub_sub, add_comm (_ * _), ← sub_sub]
  have mvpz : (p : MvPolynomial ℕ ℤ) = MvPolynomial.C ↑p := by rw [eq_intCast, Int.cast_ofNat]
  have : ∀ (f : ℤ →+* k) (g : ℕ → k), eval₂ f g p = f p :=
    by
    intros
    rw [mvpz, MvPolynomial.eval₂_C]
  simp [wittPolynomial_eq_sum_c_mul_x_pow, aeval, eval₂_rename, this, mul_coeff, peval, map_natCast,
    map_add, map_pow, map_mul]
#align witt_vector.peval_poly_of_interest WittVector.peval_polyOfInterest

variable [CharP k p]

/-- The characteristic `p` version of `peval_poly_of_interest` -/
theorem peval_poly_of_interest' (n : ℕ) (x y : 𝕎 k) :
    peval (polyOfInterest p n) ![fun i => x.coeff i, fun i => y.coeff i] =
      (x * y).coeff (n + 1) - y.coeff (n + 1) * x.coeff 0 ^ p ^ (n + 1) -
        x.coeff (n + 1) * y.coeff 0 ^ p ^ (n + 1) :=
  by
  rw [peval_poly_of_interest]
  have : (p : k) = 0 := CharP.cast_eq_zero k p
  simp only [this, add_zero, MulZeroClass.zero_mul, Nat.succ_ne_zero, Ne.def, not_false_iff,
    zero_pow']
  have sum_zero_pow_mul_pow_p :
    ∀ y : 𝕎 k,
      (∑ x : ℕ in range (n + 1 + 1), 0 ^ x * y.coeff x ^ p ^ (n + 1 - x)) =
        y.coeff 0 ^ p ^ (n + 1) :=
    by
    intro y
    rw [Finset.sum_eq_single_of_mem 0]
    · simp
    · simp
    · intro j _ hj
      simp [zero_pow (zero_lt_iff.mpr hj)]
  congr <;> apply sum_zero_pow_mul_pow_p
#align witt_vector.peval_poly_of_interest' WittVector.peval_poly_of_interest'

variable (k)

theorem nth_mul_coeff' (n : ℕ) :
    ∃ f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k,
      ∀ x y : 𝕎 k,
        f (truncateFun (n + 1) x) (truncateFun (n + 1) y) =
          (x * y).coeff (n + 1) - y.coeff (n + 1) * x.coeff 0 ^ p ^ (n + 1) -
            x.coeff (n + 1) * y.coeff 0 ^ p ^ (n + 1) :=
  by
  simp only [← peval_poly_of_interest']
  obtain ⟨f₀, hf₀⟩ := exists_restrict_to_vars k (poly_of_interest_vars p n)
  let f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k :=
    by
    intro x y
    apply f₀
    rintro ⟨a, ha⟩
    apply Function.uncurry ![x, y]
    simp only [true_and_iff, Multiset.mem_cons, range_val, product_val, Multiset.mem_range,
      Multiset.mem_product, Multiset.range_succ, mem_univ_val] at ha
    refine' ⟨a.fst, ⟨a.snd, _⟩⟩
    cases' ha with ha ha <;> linarith only [ha]
  use f
  intro x y
  dsimp [peval]
  rw [← hf₀]
  simp only [f, Function.uncurry_apply_pair]
  congr
  ext a
  cases' a with a ha
  cases' a with i m
  simp only [true_and_iff, Multiset.mem_cons, range_val, product_val, Multiset.mem_range,
    Multiset.mem_product, Multiset.range_succ, mem_univ_val] at ha
  have ha' : m < n + 1 := by cases' ha with ha ha <;> linarith only [ha]
  fin_cases i <;>-- surely this case split is not necessary
    · simpa only using x.coeff_truncate_fun ⟨m, ha'⟩
#align witt_vector.nth_mul_coeff' WittVector.nth_mul_coeff'

theorem nth_mul_coeff (n : ℕ) :
    ∃ f : TruncatedWittVector p (n + 1) k → TruncatedWittVector p (n + 1) k → k,
      ∀ x y : 𝕎 k,
        (x * y).coeff (n + 1) =
          x.coeff (n + 1) * y.coeff 0 ^ p ^ (n + 1) + y.coeff (n + 1) * x.coeff 0 ^ p ^ (n + 1) +
            f (truncateFun (n + 1) x) (truncateFun (n + 1) y) :=
  by
  obtain ⟨f, hf⟩ := nth_mul_coeff' p k n
  use f
  intro x y
  rw [hf x y]
  ring
#align witt_vector.nth_mul_coeff WittVector.nth_mul_coeff

variable {k}

/--
Produces the "remainder function" of the `n+1`st coefficient, which does not depend on the `n+1`st
coefficients of the inputs. -/
def nthRemainder (n : ℕ) : (Fin (n + 1) → k) → (Fin (n + 1) → k) → k :=
  Classical.choose (nth_mul_coeff p k n)
#align witt_vector.nth_remainder WittVector.nthRemainder

theorem nthRemainder_spec (n : ℕ) (x y : 𝕎 k) :
    (x * y).coeff (n + 1) =
      x.coeff (n + 1) * y.coeff 0 ^ p ^ (n + 1) + y.coeff (n + 1) * x.coeff 0 ^ p ^ (n + 1) +
        nthRemainder p n (truncateFun (n + 1) x) (truncateFun (n + 1) y) :=
  Classical.choose_spec (nth_mul_coeff p k n) _ _
#align witt_vector.nth_remainder_spec WittVector.nthRemainder_spec

end WittVector


variable U : Type
variables A B : U -> Prop
example : (forall x, A x /\ B x) -> forall x, A x :=
assume h1 : forall x, A x /\ B x,
assume t,
have h2 : A t /\ B t, from h1 t,
show A t, from and.left h2


import data.real.basic
import data.real.nnreal
import data.real.sqrt

theorem exo (x y: nnreal):
  1/(1 + nnreal.sqrt x)^2 + 1/(1 + nnreal.sqrt y)^2 >= 2 / (x + y + 2)
:=
  sorry