import order.galois_connection

universe u
variables X Y : Type u

def VR {X Y} (R: X → Y → Prop) (S : set X) : set Y := {y : Y | ∀ (s ∈ S), R s y}
def IR {X Y} (R: X → Y → Prop) (T : set Y) : set X := {x : X | ∀ (t ∈ T), R x t}

def subsetrel {X} : preorder (set X) := {
  le := λ A B, A ⊆ B,
  le_refl := set.subset.refl,
  le_trans := begin
    intros A B C,
      dsimp,
      exact set.subset.trans,
  end,
}
def supsetrel {Y} : preorder (set Y) := {
  le := λ A B, A ⊇ B,
  le_refl := set.subset.refl,
  le_trans := begin
    intros A B C,
      dsimp,
      intros hAB hBC,
      apply set.subset.trans,
      exact hBC,
      exact hAB,
  end,
}

-- exercise 1a
-- use @galois_connection to be able to provide all arguments explicitly
-- in particular we want to provide the preorders that way to be able to use different ones on P(X) and P(Y)
lemma ex1a (R : X → Y → Prop): @galois_connection (set X) (set Y) subsetrel supsetrel (VR R) (IR R) := begin
  intros S T,
  split,
  {
    show VR R S ⊇ T → S ⊆ IR R T,
    intro h,
    intros s hs,
    intros t ht,
    specialize h ht,
    specialize h s hs,
    exact h,
  },
  {
    show S ⊆ IR R T → VR R S ⊇ T,
    intro h,
    intros t ht,
    intros s hs,
    specialize h hs,
    specialize h t ht,
    exact h,
  },
end


/-
Copyright (c) 2021 Nicolò Cavalleri. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Nicolò Cavalleri
-/
import topology.homeomorph

/-!
# Topological space structure on the opposite monoid and on the units group

> THIS FILE IS SYNCHRONIZED WITH MATHLIB4.
> Any changes to this file require a corresponding PR to mathlib4.

In this file we define `topological_space` structure on `Mᵐᵒᵖ`, `Mᵃᵒᵖ`, `Mˣ`, and `add_units M`.
This file does not import definitions of a topological monoid and/or a continuous multiplicative
action, so we postpone the proofs of `has_continuous_mul Mᵐᵒᵖ` etc till we have these definitions.

## Tags

topological space, opposite monoid, units
-/

variables {M X : Type*}

open filter
open_locale topology

namespace mul_opposite

/-- Put the same topological space structure on the opposite monoid as on the original space. -/
@[to_additive "Put the same topological space structure on the opposite monoid as on the original
space."]
instance [topological_space M] : topological_space Mᵐᵒᵖ :=
topological_space.induced (unop : Mᵐᵒᵖ → M) ‹_›

variables [topological_space M]

@[continuity, to_additive] lemma continuous_unop : continuous (unop : Mᵐᵒᵖ → M) :=
continuous_induced_dom

@[continuity, to_additive] lemma continuous_op : continuous (op : M → Mᵐᵒᵖ) :=
continuous_induced_rng.2 continuous_id

/-- `mul_opposite.op` as a homeomorphism. -/
@[to_additive "`add_opposite.op` as a homeomorphism.", simps]
def op_homeomorph : M ≃ₜ Mᵐᵒᵖ :=
{ to_equiv := op_equiv,
  continuous_to_fun := continuous_op,
  continuous_inv_fun := continuous_unop }

@[to_additive] instance [t2_space M] : t2_space Mᵐᵒᵖ :=
op_homeomorph.symm.embedding.t2_space

@[simp, to_additive] lemma map_op_nhds (x : M) : map (op : M → Mᵐᵒᵖ) (𝓝 x) = 𝓝 (op x) :=
op_homeomorph.map_nhds_eq x

@[simp, to_additive] lemma map_unop_nhds (x : Mᵐᵒᵖ) : map (unop : Mᵐᵒᵖ → M) (𝓝 x) = 𝓝 (unop x) :=
op_homeomorph.symm.map_nhds_eq x

@[simp, to_additive] lemma comap_op_nhds (x : Mᵐᵒᵖ) : comap (op : M → Mᵐᵒᵖ) (𝓝 x) = 𝓝 (unop x) :=
op_homeomorph.comap_nhds_eq x

@[simp, to_additive]

end mul_opposite

namespace units

open mul_opposite

variables [topological_space M] [monoid M] [topological_space X]

/-- The units of a monoid are equipped with a topology, via the embedding into `M × M`. -/
@[to_additive "The additive units of a monoid are equipped with a topology, via the embedding into
`M × M`."]
instance : topological_space Mˣ := prod.topological_space.induced (embed_product M)

@[to_additive] lemma inducing_embed_product : inducing (embed_product M) := ⟨rfl⟩

@[to_additive] lemma embedding_embed_product : embedding (embed_product M) :=
⟨inducing_embed_product, embed_product_injective M⟩

@[to_additive] lemma topology_eq_inf :
  units.topological_space = topological_space.induced (coe : Mˣ → M) ‹_› ⊓
    topological_space.induced (λ u, ↑u⁻¹ : Mˣ → M) ‹_› :=
by simp only [inducing_embed_product.1, prod.topological_space, induced_inf,
  mul_opposite.topological_space, induced_compose]; refl

/-- An auxiliary lemma that can be used to prove that coercion `Mˣ → M` is a topological embedding.
Use `units.coe_embedding₀`, `units.coe_embedding`, or `to_units_homeomorph` instead. -/
@[to_additive "An auxiliary lemma that can be used to prove that coercion `add_units M → M` is a
topological embedding. Use `add_units.coe_embedding` or `to_add_units_homeomorph` instead."]
lemma embedding_coe_mk {M : Type*} [division_monoid M] [topological_space M]
  (h : continuous_on has_inv.inv {x : M | is_unit x}) : embedding (coe : Mˣ → M) :=
begin
  refine ⟨⟨_⟩, ext⟩,
  rw [topology_eq_inf, inf_eq_left, ← continuous_iff_le_induced, continuous_iff_continuous_at],
  intros u s hs,
  simp only [coe_inv, nhds_induced, filter.mem_map] at hs ⊢,
  exact ⟨_, mem_inf_principal.1 (h u u.is_unit hs), λ u' hu', hu' u'.is_unit⟩
end

@[to_additive] lemma continuous_embed_product : continuous (embed_product M) :=
continuous_induced_dom

@[to_additive] lemma continuous_coe : continuous (coe : Mˣ → M) :=
(@continuous_embed_product M _ _).fst

@[to_additive] protected lemma continuous_iff {f : X → Mˣ} :
  continuous f ↔ continuous (coe ∘ f : X → M) ∧ continuous (λ x, ↑(f x)⁻¹ : X → M) :=
by simp only [inducing_embed_product.continuous_iff, embed_product_apply, (∘), continuous_prod_mk,
  op_homeomorph.symm.inducing.continuous_iff, op_homeomorph_symm_apply, unop_op]

@[to_additive] lemma continuous_coe_inv : continuous (λ u, ↑u⁻¹ : Mˣ → M) :=
(units.continuous_iff.1 continuous_id).2

end units


import xenalib.zmod algebra.group_power tactic.norm_num algebra.big_operators

local infix ` ^ ` := monoid.pow

-- sheet 5 solns

def Fib : ℕ → ℕ
| 0 := 0
| 1 := 1
| (n+2) := Fib n + Fib (n+1)

--#eval Fib 10
--#reduce Fib 10

def is_even (n : ℕ) : Prop := ∃ k, n=2*k
def is_odd (n : ℕ) : Prop := ∃ k, n=2*k+1

lemma even_of_even_add_even (a b : ℕ) : is_even a → is_even b → is_even (a+b) :=
begin
intros Ha Hb,
cases Ha with k Hk,
cases Hb with l Hl,
existsi k+l,
simp [Hk,Hl,add_mul],
end

lemma odd_of_odd_add_even {a b : ℕ} : is_odd a → is_even b → is_odd (a+b) :=
begin
intros Ha Hb,
cases Ha with k Hk,
cases Hb with l Hl,
existsi k+l,
simp [Hk,Hl,add_mul],
end

lemma odd_of_even_add_odd {a b : ℕ} : is_even a → is_odd b → is_odd (a+b) :=
λ h1 h2, (add_comm b a) ▸ (odd_of_odd_add_even h2 h1)

lemma even_of_odd_add_odd {a b : ℕ} : is_odd a → is_odd b → is_even (a+b) :=
begin
intros Ha Hb,
cases Ha with k Hk,
cases Hb with l Hl,
existsi k+l+1,
-- simp [mul_add,Hk,Hl,one_add_one_eq_two] -- fails!
rw [Hk,Hl,mul_add,mul_add],
change 2 with 1+1,
simp [mul_add,add_mul],
end

theorem Q1a : ∀ n : ℕ, n ≥ 1 →
  is_odd (Fib (3*n-2)) ∧ is_odd (Fib (3*n-1)) ∧ is_even (Fib (3*n)) :=
begin
intros n Hn,
cases n with m,
have : ¬ (0 ≥ 1) := dec_trivial,
exfalso,
exact (this Hn),
induction m with d Hd,
  exact ⟨⟨0,rfl⟩,⟨0,rfl⟩,⟨1,rfl⟩⟩,
change 3*nat.succ d-2 with 3*d+1 at Hd,
change 3*nat.succ d -1 with 3*d+2 at Hd,
change 3*nat.succ d with 3*d+3 at Hd,
change 3*nat.succ (nat.succ d)-2 with 3*d+4,
change 3*nat.succ (nat.succ d)-1 with 3*d+5,
change 3*nat.succ (nat.succ d) with 3*d+6,

let Hyp := Hd (begin apply nat.succ_le_succ,exact nat.zero_le d,end),
let H1 := Hyp.left,
let H2 := Hyp.right.left,
let H3 := Hyp.right.right,
have H4 : is_odd (Fib (3*d+4)),
  change Fib (3*d+4) with Fib (3*d+2)+Fib(3*d+3),
  exact odd_of_odd_add_even H2 H3,
have H5 : is_odd (Fib (3*d+5)),
  change Fib (3*d+5) with Fib (3*d+3)+Fib(3*d+4),
  exact odd_of_even_add_odd H3 H4,
have H6 : is_even (Fib (3*d+6)),
  change Fib (3*d+6) with Fib (3*d+4)+Fib(3*d+5),
  exact even_of_odd_add_odd H4 H5,
exact ⟨H4,H5,H6⟩,
end

theorem Q1b : is_odd (Fib (2017)) :=
begin
have H : 2017 = 3*673-2 := dec_trivial,
rw [H],
exact (Q1a 673 (dec_trivial)).left,
end

theorem Q2 (n : ℕ) : n ≥ 2 → nat.pow 4 n > nat.pow 3 n + nat.pow 2 n :=
begin
intro H_n_ge_2,
cases n with n1,
  exfalso,revert H_n_ge_2, exact dec_trivial,
cases n1 with n2,
  exfalso,revert H_n_ge_2, exact dec_trivial,
clear H_n_ge_2,
induction n2 with d Hd,
  exact dec_trivial,
let e := nat.succ (nat.succ d),
show nat.pow 4 e*4>nat.pow 3 e*3+nat.pow 2 e*2,
change nat.pow 4 (nat.succ (nat.succ d)) > nat.pow 3 (nat.succ (nat.succ d)) + nat.pow 2 (nat.succ (nat.succ d))
with nat.pow 4 e>nat.pow 3 e+nat.pow 2 e at Hd,
exact calc
nat.pow 4 e * 4 > (nat.pow 3 e + nat.pow 2 e) * 4 : mul_lt_mul_of_pos_right Hd (dec_trivial)
... = nat.pow 3 e*4+nat.pow 2 e*4 : add_mul _ _ _
... ≥ nat.pow 3 e*3+nat.pow 2 e*4 : add_le_add_right (nat.mul_le_mul_left _ (dec_trivial)) _
... ≥ nat.pow 3 e*3+nat.pow 2 e*2 : add_le_add_left (nat.mul_le_mul_left _ (dec_trivial)) _,
end

theorem Q3a (n : ℕ) : ∃ k, (n+1)+(n+2)+(n+3)+(n+4) = 4*k+2 :=
begin
induction n with d Hd,
  existsi 2,trivial,
cases Hd with k Hk, -- why is this so horrible!
existsi k+1,
rw [nat.succ_eq_add_one],
have : ((d + 1 + (d + 2) + (d + 3) + (d + 4))+4)=(4*k+2)+4
        := Hk ▸ @eq.refl ℕ ((d + 1 + (d + 2) + (d + 3) + (d + 4))+4),
simp, -- have given in -- anything will now do.
simp at this,
rw this,
simp [add_mul],
end
--#check @eq_of_add_eq_add_right
--#check @eq.refl

--#check (by apply_instance : has_mul (fin 4))
lemma of_nat_pow : ∀ a b: ℕ, int.of_nat(a^b)=(int.of_nat a)^b :=
begin
intros a b,
induction b with k Hk,
  unfold monoid.pow,
  simp [int.of_nat_one],
unfold monoid.pow nat.pow, -- pow_nat nat.pow has_pow_nat.pow_nat monoid.pow,
--show int.of_nat (a^k*a) = (int.of_nat a) * (int.of_nat a)^k,
rw [int.of_nat_mul,mul_comm,Hk,mul_comm],
end

theorem Q3helpful_lemma : ∀ (e : ℕ), 11^e ≥ 3^e :=
begin
intro e,
induction e with d Hd,
  exact dec_trivial,
exact calc 11^(nat.succ d) = 11*11^d : rfl
... ≥ 3*11^d : nat.mul_le_mul_right (11^d) (dec_trivial)
... ≥ 3*3^d : nat.mul_le_mul_left 3 Hd
... = 3^(nat.succ d) : rfl
end

theorem Q3helpful_lemma2 : ∀ (e : ℕ ), (((11^e-3^e):ℕ):ℤ) = (11:ℤ)^e-(3:ℤ)^e :=
begin
intro e,
show int.of_nat (11^e - 3^e) = (11:ℤ)^e - (3:ℤ)^e,
rw [int.of_nat_sub (Q3helpful_lemma e)],
rw [of_nat_pow,of_nat_pow],
refl,
end

theorem Q3b (n : ℕ) : 8 ∣ 11^n - 3^n :=
begin
have H : ∀ (e : ℕ), 11^e ≥ 3^e := Q3helpful_lemma,

have H311 : @Zmod.of_int 8 3 = Zmod.of_int 11,
apply quot.sound,
existsi (1:ℤ),
exact dec_trivial,

induction n with d Hd,
  exact ⟨0,rfl⟩,

cases Hd with k Hk,
rw [←int.of_nat_eq_of_nat_iff] at Hk,
rw [int.of_nat_sub (H d),int.of_nat_mul] at Hk,
rw [of_nat_pow,of_nat_pow] at Hk,
--have : (int.of_nat 11)^d - (int.of_nat 3)^d = 8 * (int.of_nat k),
--exact Hk,
have Heq : @Zmod.of_int 8 ((3:ℤ)^d) = Zmod.of_int ((11:ℤ)^d),
apply quot.sound,
existsi (k:ℤ),
rw [mul_comm],
exact eq.symm Hk,
have H1 : @Zmod.of_int 8 ((3:ℤ)^(nat.succ d)) = Zmod.of_int (11^(nat.succ d)) := calc
Zmod.of_int ((3:ℤ)^(nat.succ d)) = (Zmod.of_int 3)^(nat.succ d) : eq.symm Zmod.of_int_pow
... = (Zmod.of_int 3)^(d+1) : rfl
... = (Zmod.of_int 3) * (Zmod.of_int 3)^d : pow_succ (Zmod.of_int 3) d
... = (Zmod.of_int 3) * (Zmod.of_int (3^d)) : by rw [@Zmod.of_int_pow 8]
... = Zmod.of_int 3 * (Zmod.of_int (11^d)) : by rw [Heq]
... = Zmod.of_int 11 * (Zmod.of_int (11^d)) : by rw [H311]
... = Zmod.of_int (11^(nat.succ d)) : rfl,

unfold Zmod.of_int at H1,
have H2 := @quotient.exact ℤ (@Z_setoid 8) _ _ H1,
cases H2 with k2 Hk2,
--show (8:ℤ) ∣ 11^nat.succ d - 3^nat.succ d,
have H3 : k2 * ↑8 = (int.of_nat 11) ^ nat.succ d - (int.of_nat 3) ^ nat.succ d,
exact Hk2,
rw [←of_nat_pow,←of_nat_pow] at H3,
rw [←int.of_nat_sub (H (nat.succ d))] at H3,
have Hpos : k2 * ↑8 ≥ 0,
rw [H3], exact int.of_nat_nonneg (11^nat.succ d - 3^nat.succ d),
have Hpos2 : k2 ≥ 0 := nonneg_of_mul_nonneg_right Hpos (dec_trivial),
let k3 : ℕ := int.nat_abs k2,
have H4 : ↑k3 = k2 := int.nat_abs_of_nonneg Hpos2,
existsi k3,
apply int.of_nat_inj,
rw [eq.symm H3],
rw [←H4,int.of_nat_mul,mul_comm],
rw int.of_nat_eq_coe,
rw int.of_nat_eq_coe,
end

theorem Q3ca (n : ℕ) : ∃ k, (n+1)+(n+2)+(n+3)+(n+4) = 4*k+2 :=
begin
existsi (n+2),
change 4 with 1+1+1+1,
rw [add_mul,add_mul,add_mul],
simp,
end

def Q3sum (d : ℕ) (a b : ℤ) := finset.sum (finset.range (d+1)) (λ n, a^n*b^(d-n))

--finset.sum_image :
--  ∀ {α : Type u_1} {β : Type u_2} {γ : Type u_3} {f : α → β} [_inst_1 : add_comm_monoid β]
--  [_inst_2 : decidable_eq α] [_inst_3 : decidable_eq γ] {s : finset γ} {g : γ → α},
--    (∀ (x : γ), x ∈ s → ∀ (y : γ), y ∈ s → g x = g y → x = y) →
--    finset.sum (finset.image g s) f = finset.sum s (λ (x : γ), f (g x))

lemma lt_of_in_finset {x y: ℕ} : x ∈ finset.range y → x < y := by simp
#print multiset.to_finset
#print finset.range
#check multiset.erase
#check finset.eq_of_veq
#print multiset.erase_dup
#check multiset.nodup_range
#check multiset.erase_dup_eq_self
#print multiset.map
#check multiset.coe_reverse
#print multiset.range
#check multiset.coe_map
#print list.reverse
#print list.iota._main
#print list.range_core
#check list.range_core_range'
#print list.range'._main
#eval list.range' 5 8 -- [5,6,7,8,9,10,11,12]
#check list.range'
--set_option pp.notation false
lemma H8 (d : ℕ) : list.map (λ (i : ℕ), d - i) (list.range (d + 1))
    = list.reverse (list.range (d + 1)) :=
begin
admit, -- and ask Mario
end

lemma H7 : ∀ d : ℕ, (finset.range (d+1)).image (λ i,d-i) = finset.range (d+1) :=
begin
-- statement about finsets
intro d,
unfold finset.image,
unfold finset.range,
show multiset.to_finset (multiset.map (λ (i : ℕ), d - i)
      (multiset.range (d + 1))) =
    {val := multiset.range (d + 1), nodup := _},
unfold multiset.to_finset,
apply finset.eq_of_veq,
show multiset.erase_dup (multiset.map (λ (i : ℕ), d - i) (multiset.range (d + 1)))
    = multiset.range (d + 1),
-- now a statement about multisets
have this2 := multiset.nodup_range (d+1),
suffices : multiset.erase_dup (multiset.map (λ (i : ℕ), d - i) (multiset.range (d + 1)))
   = multiset.erase_dup (multiset.range (d + 1)),
  rw [this],
  exact multiset.erase_dup_eq_self.2 this2,
apply congr_arg,
clear this2,
show multiset.map (λ (i : ℕ), d - i) (multiset.range (d + 1))
    = ↑(list.range (d + 1)),
rw ←multiset.coe_reverse,
unfold multiset.range,
rw multiset.coe_map,
apply congr_arg,
-- now a statement about lists
exact H8 d,
end

lemma H0 (d : ℕ) (a b : ℤ) : Q3sum d a b = Q3sum d b a :=
begin
unfold Q3sum,
have : (finset.range (d+1)).image (λ i,d-i) = finset.range (d+1),
  exact H7 d,
/-
  unfold finset.range,
  unfold multiset.range,
  rw ←multiset.coe_reverse,
-/
rw ←this, -- aargh, rewrites both!
have H53 : ∀ (x : ℕ),
    x ∈ finset.range(d+1) → ∀ (y : ℕ), y ∈ finset.range (d+1) → d-x = d-y → x=y,
  introv Hx Hy Hxy,
  have : (d-x)+x=d,
    exact nat.sub_add_cancel (nat.le_of_lt_succ (lt_of_in_finset Hx)),
  rw Hxy at this,
  rw ←nat.sub_add_comm (nat.le_of_lt_succ (lt_of_in_finset Hy)) at this,
  have this2 : (d+x-y)+y=d+x,
    exact nat.sub_add_cancel (le_trans (nat.le_of_lt_succ (lt_of_in_finset Hy)) (nat.le_add_right _ _)),
  rw this at this2,
  exact eq.symm (nat.add_left_cancel this2),
rw [@finset.sum_image ℕ ℤ ℕ (λ (n : ℕ), a ^ n * b ^ (d - n)) _ _ _
     (finset.range (d+1)) (λ i : ℕ, d-i) H53],
rw [this],
apply finset.sum_congr,
introv H,
have := lt_of_in_finset H,
show a ^ (d - x) * b ^ (d - (d - x)) = b ^ x * a ^ (d - x),
rw [nat.sub_sub_self (nat.le_of_lt_succ this)],
apply mul_comm,
end

lemma H5 (e : ℕ) : finset.range (nat.succ e) = insert e (finset.range e) :=
begin
exact finset.range_succ,
end

lemma H1 (d : ℕ) (aa b : ℤ) : b * Q3sum d aa b = Q3sum (d+1) aa b - aa^(d+1) :=
begin
unfold Q3sum,
rw finset.mul_sum,
change d+1+1 with nat.succ(d+1),
rw (@finset.range_succ (d+1)),
rw finset.sum_insert,
  tactic.swap,
  intro H,
  apply lt_irrefl (d+1),
  exact lt_of_in_finset H,
rw [nat.sub_self (d+1)],
have : ∀ x : ℕ, x<(d+1) → b*(aa^x*b^(d-x))= aa^x*b^(d+1-x),
  intros x Hx,
  rw [mul_comm],
  have : d+1-x = (d-x)+1,
    rw [add_comm,nat.add_sub_assoc (nat.le_of_lt_succ Hx),add_comm],
  rw [this],
  rw [pow_succ],simp,
rw [pow_zero,mul_one],
rw [add_comm (aa^(d+1)),add_sub_cancel],
apply finset.sum_congr,
intros,
apply this x,
exact lt_of_in_finset H,
end

lemma H3 (d : ℕ) : 11 * Q3sum d 3 11 = Q3sum (d+1) 3 11 - 3^(d+1) := H1 d 3 11
lemma H2 (d : ℕ) : 3 * Q3sum d 3 11 = Q3sum (d+1) 3 11 - 11^(d+1) :=
begin
rw [H0],
rw H1 d 11 3,
rw [H0]
end

lemma Q3cb_helper : ∀ d : ℕ, 8*Q3sum d 3 11 = 11^(d+1)-3^(d+1) :=
begin
intro d,
change (8:ℤ) with (11:ℤ)-3,
rw [sub_mul,H3,H2],
simp,
end

theorem Q3cbint (n : ℕ) : 8 ∣ (11:ℤ)^n - 3^n :=
begin
cases n with d,
  simp,
show 8 ∣ (11:ℤ)^(d+1) - 3^(d+1),
rw [←Q3cb_helper d],
existsi Q3sum d 3 11,
refl,
end

theorem Q3cb (n : ℕ) : 8 ∣ 11^n - 3^n := -- non-inductive proof
begin
rw ←int.coe_nat_dvd,
rw [Q3helpful_lemma2],
apply Q3cbint,
end

--#check @finset.prod_image
/-

What's a sensible way to do 1+x+x^2+...+x^n?

I'm still working on the details, but big_operations.lean deals with this stuff

finset.sum is the one you want (or list.sum, but the finset version has more algebraic properties on it)

-/

def factorial : ℕ → ℕ
| 0 := 1
| (n+1) := (factorial n) * (n+1)

theorem Q4 (n : ℕ) (H : n > 0) : factorial n < 3^n ↔ n ≤ 6 :=
begin
cases n with m,
  revert H,
  exact dec_trivial,
clear H,

cases m with m,split;exact dec_trivial,
cases m with m,split;exact dec_trivial,
cases m with m,split;exact dec_trivial,

cases m with m,split;intros,exact dec_trivial,
repeat {unfold factorial monoid.pow},{norm_num},

cases m with m,split;intros,exact dec_trivial,
repeat {unfold factorial monoid.pow},{norm_num},

cases m with m,split;exact dec_trivial,

split,
  tactic.swap,
  intro H,
  exfalso,
  revert H,
  exact dec_trivial,

intro this,exfalso,revert this,
apply not_lt_of_ge,
induction m with d Hd,
repeat {unfold factorial monoid.pow},{norm_num},

let e := nat.succ (nat.succ (nat.succ (nat.succ (nat.succ (nat.succ (nat.succ d)))))),
change nat.succ (nat.succ (nat.succ (nat.succ (nat.succ (nat.succ (nat.succ d)))))) with e,
change nat.succ (nat.succ (nat.succ (nat.succ (nat.succ (nat.succ (nat.succ d)))))) with e at Hd,

have He_ge_3 : nat.succ e ≥ 3 := by exact dec_trivial,
exact calc
factorial (nat.succ e) = factorial e * (nat.succ e) : by unfold factorial
... ≥ factorial e * 3 : nat.mul_le_mul_left _ He_ge_3
... ≥ 3^e*3 : nat.mul_le_mul_right 3 Hd
... = 3*3^e : by rw mul_comm
... = 3^nat.succ e : rfl,
end

theorem Q5 : (¬ (∃ a b c : ℕ, 6*a+9*b+20*c = 43))
             ∧ ∀ m, m ≥ 44 → ∃ a b c : ℕ, 6*a+9*b+20*c = m :=
begin
split,
  intro H,
  cases H with a H,
  cases H with b H,
  cases H with c H,
  revert H,
  cases c with c,tactic.swap,
    cases c with c,tactic.swap,
      cases c with c,tactic.swap,
        show 6*a+9*b+20*(c+3) = 43 → false,
        rw [mul_add],
        apply ne_of_gt,
        exact calc 6 * a + 9 * b + (20 * c + 20 * 3)
                = 6*a + (9*b+(20*c+20*3)) : by simp
            ... ≥ 9 * b + (20 * c + 20 * 3) : nat.le_add_left (9 * b + (20 * c + 20 * 3)) (6*a)
            ... ≥  20 * c + 20 * 3 : nat.le_add_left _ _
            ... ≥ 20*3 : nat.le_add_left _ _
            ... > 43 : dec_trivial,
      cases b with b,tactic.swap,
        show (6*a+9*(b+1)+20*2=43 → false),
        rw [mul_add],
        apply ne_of_gt,
        exact calc 6 * a + (9 * b + 9 * 1) + 20 * 2
            = (6*a+9*b)+9*1+20*2 : by simp
        ... ≥ 9*1+20*2 : nat.le_add_left _ _
        ... > 43 : dec_trivial,
      cases a with a,
        exact dec_trivial,
      show 6*(a+1) + 9 * 0 + 20 * 2 = 43 → false,
      rw [mul_add],
      apply ne_of_gt,
      exact calc 6 * a + 6 * 1 + 9 * 0 + 20 * 2
              ≥  6 * 1 + 9 * 0 + 20 * 2 : nat.le_add_left _ _
          ... > 43 : dec_trivial,

    cases b with b,tactic.swap,
      cases b with b,tactic.swap,
        cases b with b,tactic.swap,
          show (6*a+9*(b+3)+20*1=43 → false),
          rw [mul_add],
          apply ne_of_gt,
          exact calc 6 * a + (9 * b + 9 * 3) + 20 * 1
            = (6*a+9*b)+9*3+20*1 : by simp
        ... ≥ 9*3+20*1 : nat.le_add_left _ _
        ... > 43 : dec_trivial,
      cases a with a,
        exact dec_trivial,
      show 6*(a+1) + 9 * 2 + 20 * 1 = 43 → false,
      rw [mul_add],
      apply ne_of_gt,
      exact calc 6 * a + 6 * 1 + 9 * 2 + 20 * 1
              ≥  6 * 1 + 9 * 2 + 20 * 1 : nat.le_add_left _ _
          ... > 43 : dec_trivial,
      cases a with a,
        exact dec_trivial,
      cases a with a,
        exact dec_trivial,
      cases a with a,
        exact dec_trivial,
      show 6*(a+3) + 9 * 1 + 20 * 1 = 43 → false,
      rw [mul_add],
      apply ne_of_gt,
      exact calc 6 * a + 6 * 3 + 9 * 1 + 20 * 1
              ≥  6 * 3 + 9 * 1 + 20 * 1 : nat.le_add_left _ _
          ... > 43 : dec_trivial,
    cases a with a,
      exact dec_trivial,
    cases a with a,
      exact dec_trivial,
    cases a with a,
      exact dec_trivial,
    cases a with a,
      exact dec_trivial,
    show 6*(a+4) + 9 * 0 + 20 * 1 = 43 → false,
    rw [mul_add],
    apply ne_of_gt,
    exact calc 6 * a + 6 * 4 + 9 * 0 + 20 * 1
            ≥  6 * 4 + 9 * 0 + 20 * 1 : nat.le_add_left _ _
        ... > 43 : dec_trivial,

  cases b with b,tactic.swap,
    cases b with b,tactic.swap,
      cases b with b,tactic.swap,
        cases b with b,tactic.swap,
          cases b with b,tactic.swap,
            show (6*a+9*(b+5)+20*0=43 → false),
            rw [mul_add],
            apply ne_of_gt,
            exact calc 6 * a + (9 * b + 9 * 5) + 20 * 0
              = (6*a+9*b)+9*5+20*0 : by simp
          ... ≥ 9*5+20*0 : nat.le_add_left _ _
          ... > 43 : dec_trivial,
      cases a with a,
        exact dec_trivial,
      cases a with a,
        exact dec_trivial,
      show 6*(a+2) + 9 * 4 + 20 * 0 = 43 → false,
      rw [mul_add],
      apply ne_of_gt,
      exact calc 6 * a + 6 * 2 + 9 * 4 + 20 * 0
              ≥  6 * 2 + 9 * 4 + 20 * 0 : nat.le_add_left _ _
          ... > 43 : dec_trivial,
        cases a with a,exact dec_trivial,
        cases a with a,exact dec_trivial,
        cases a with a,exact dec_trivial,
        show 6*(a+3) + 9 * 3 + 20 * 0 = 43 → false,
        rw [mul_add],
        apply ne_of_gt,
        exact calc 6 * a + 6 * 3 + 9 * 3 + 20 * 0
              ≥  6 * 3 + 9 * 3 + 20 * 0 : nat.le_add_left _ _
          ... > 43 : dec_trivial,
    cases a with a,exact dec_trivial,
    cases a with a,exact dec_trivial,
    cases a with a,exact dec_trivial,
    cases a with a,exact dec_trivial,
    cases a with a,exact dec_trivial,
    show 6*(a+5) + 9 * 2 + 20 * 0 = 43 → false,
    rw [mul_add],
    apply ne_of_gt,
    exact calc 6 * a + 6 * 5 + 9 * 2 + 20 * 0
            ≥  6 * 5 + 9 * 2 + 20 * 0 : nat.le_add_left _ _
        ... > 43 : dec_trivial,
    cases a with a,exact dec_trivial,
    cases a with a,exact dec_trivial,
    cases a with a,exact dec_trivial,
    cases a with a,exact dec_trivial,
    cases a with a,exact dec_trivial,
    cases a with a,exact dec_trivial,
    show 6*(a+6) + 9 * 1 + 20 * 0 = 43 → false,
    rw [mul_add],
    apply ne_of_gt,
    exact calc 6 * a + 6 * 6 + 9 * 1 + 20 * 0
            ≥  6 * 6 + 9 * 1 + 20 * 0 : nat.le_add_left _ _
        ... > 43 : dec_trivial,
  cases a with a,exact dec_trivial,
  cases a with a,exact dec_trivial,
  cases a with a,exact dec_trivial,
  cases a with a,exact dec_trivial,
  cases a with a,exact dec_trivial,
  cases a with a,exact dec_trivial,
  cases a with a,exact dec_trivial,
  cases a with a,exact dec_trivial,
  show 6*(a+8) + 9 * 0 + 20 * 0 = 43 → false,
  rw [mul_add],
  apply ne_of_gt,
  exact calc 6 * a + 6 * 8 + 9 * 0 + 20 * 0
          ≥  6 * 8 + 9 * 0 + 20 * 0 : nat.le_add_left _ _
      ... > 43 : dec_trivial,

-- now the opposite

-- proof that if m>=44 then it's 44+n
-- probably would have been easier to do by induction on 44

intros m Hm,
have H44 : ∃ n : ℕ, m=44+n,
  let c:ℤ := m-((44:ℕ):ℤ),
  have Hc_nonneg : c ≥ 0 := calc
  c = ↑m - ↑44 : rfl
  ... = ↑m + -↑44 : sub_eq_add_neg _ _
  ... ≥ ↑44 + -↑44 : add_le_add_right ((int.coe_nat_le_coe_nat_iff _ _).2 Hm) _
  ... = 0 : add_neg_self _,
  have H := int.nat_abs_of_nonneg Hc_nonneg,
  let n := int.nat_abs c,
  existsi n,
  apply (int.of_nat_eq_of_nat_iff _ _).1,
  rw [←int.coe_nat_eq,←int.coe_nat_eq,int.coe_nat_add,H,add_comm,sub_add_cancel],

cases H44 with n H,
rw [H],
clear Hm H m,

/- State now

n : ℕ
⊢ ∃ (a b c : ℕ), 6 * a + 9 * b + 20 * c = 44 + n

-/

-- Now need division with remainder

have H6 : ∃ q r : ℕ, n=6*q+r ∧ (r=0 ∨ r=1 ∨ r=2 ∨ r=3 ∨ r=4 ∨ r=5),
  induction n with d Hd,
    existsi 0,
    existsi 0,
    split,
      simp,
    simp,
  cases Hd with q Hd',
  cases Hd' with r HI,
  cases HI.right with H0 H15,
    existsi q,
    existsi 1,
    split,
      simp [HI.left,H0,nat.succ_eq_add_one],
    simp,
  cases H15 with H H25,
    existsi q,
    existsi 2,
    split,
--      simp [nat.succ_eq_add_one,HI.left,H1],
      rw [nat.succ_eq_add_one,HI.left,H],
    simp,
  cases H25 with H H35,
    existsi q,
    existsi 3,
    split,
      rw [nat.succ_eq_add_one,HI.left,H],
    simp,
  cases H35 with H H45,
    existsi q,
    existsi 4,
    split,
      rw [nat.succ_eq_add_one,HI.left,H],
    simp,
  cases H45 with H H5,
    existsi q,
    existsi 5,
    split,
      rw [nat.succ_eq_add_one,HI.left,H],
    simp,
  existsi q+1,
  existsi 0,
  split,
    rw [nat.succ_eq_add_one,HI.left,H5,mul_add,mul_one],
  simp,

cases H6 with q H6',
cases H6' with r H,
rw [H.left],
have Hrsmall := H.right,
clear H n,

/-

State now

q r : ℕ
Hrsmall : r = 0 ∨ r = 1 ∨ r = 2 ∨ r = 3 ∨ r = 4 ∨ r = 5
⊢ ∃ (a b c : ℕ), 6 * a + 9 * b + 20 * c = 44 + (6 * q + r)

-/

induction q with d Hd,
  cases Hrsmall with H H15,
    existsi 4,existsi 0,existsi 1,
    rw [H],exact dec_trivial,
 cases H15 with H H25,
    existsi 0,existsi 5,existsi 0,
    rw [H],exact dec_trivial,
 cases H25 with H H35,
    existsi 1,existsi 0,existsi 2,
    rw [H],exact dec_trivial,
 cases H35 with H H45,
    existsi 0,existsi 3,existsi 1,
    rw [H],exact dec_trivial,
 cases H45 with H H5,
    existsi 8,existsi 0,existsi 0,
    rw [H],exact dec_trivial,
  existsi 0,existsi 1,existsi 2,
    rw [H5],exact dec_trivial,

clear Hrsmall,

cases Hd with a Hd',
cases Hd' with b Hd'',
cases Hd'' with c H,
existsi (a+1),
existsi b,
existsi c,
rw [nat.succ_eq_add_one,mul_add,mul_add,mul_one],
rw [add_comm (6*a) 6,add_assoc,add_assoc],
rw add_assoc at H,
rw H,
simp,
end

theorem Q6 : 1=1 := sorry -- blue-eyed islanders


/-
Copyright (c) 2021 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Gabriel Ebner
-/
prelude
import Init.NotationExtra

namespace Nat

private theorem log2_terminates : ∀ n, n ≥ 2 → n / 2 < n
  | 2, _ => by decide
  | 3, _ => by decide
  | n+4, _ => by
    rw [div_eq, if_pos]
    refine succ_lt_succ (Nat.lt_trans ?_ (lt_succ_self _))
    exact log2_terminates (n+2) (succ_lt_succ (zero_lt_succ _))
    exact ⟨by decide, succ_lt_succ (zero_lt_succ _)⟩

/--
Computes `⌊max 0 (log₂ n)⌋`.

`log2 0 = log2 1 = 0`, `log2 2 = 1`, ..., `log2 (2^i) = i`, etc.
-/
@[extern "lean_nat_log2"]
def log2 (n : @& Nat) : Nat :=
  if h : n ≥ 2 then log2 (n / 2) + 1 else 0
termination_by _ => n
decreasing_by exact log2_terminates _ h


/-
Copyright (c) 2022 Antoine Labelle. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Antoine Labelle
-/
import representation_theory.basic
import representation_theory.fdRep

/-!
# Subspace of invariants a group representation

This file introduces the subspace of invariants of a group representation
and proves basic results about it.
The main tool used is the average of all elements of the group, seen as an element of
`monoid_algebra k G`. The action of this special element gives a projection onto the
subspace of invariants.
In order for the definition of the average element to make sense, we need to assume for most of the
results that the order of `G` is invertible in `k` (e. g. `k` has characteristic `0`).
-/

open_locale big_operators
open monoid_algebra
open representation

namespace group_algebra

variables (k G : Type*) [comm_semiring k] [group G]
variables [fintype G] [invertible (fintype.card G : k)]

/--
The average of all elements of the group `G`, considered as an element of `monoid_algebra k G`.
-/
noncomputable def average : monoid_algebra k G :=
  ⅟(fintype.card G : k) • ∑ g : G, of k G g

/--
`average k G` is invariant under left multiplication by elements of `G`.
-/
@[simp]
theorem mul_average_left (g : G) :
  (finsupp.single g 1 * average k G : monoid_algebra k G) = average k G :=
begin
  simp only [mul_one, finset.mul_sum, algebra.mul_smul_comm, average, monoid_algebra.of_apply,
    finset.sum_congr, monoid_algebra.single_mul_single],
  set f : G → monoid_algebra k G := λ x, finsupp.single x 1,
  show ⅟ ↑(fintype.card G) • ∑ (x : G), f (g * x) = ⅟ ↑(fintype.card G) • ∑ (x : G), f x,
  rw function.bijective.sum_comp (group.mul_left_bijective g) _,
end

/--
`average k G` is invariant under right multiplication by elements of `G`.
-/
@[simp]
theorem mul_average_right (g : G) :
  average k G * finsupp.single g 1 = average k G :=
begin
  simp only [mul_one, finset.sum_mul, algebra.smul_mul_assoc, average, monoid_algebra.of_apply,
    finset.sum_congr, monoid_algebra.single_mul_single],
  set f : G → monoid_algebra k G := λ x, finsupp.single x 1,
  show ⅟ ↑(fintype.card G) • ∑ (x : G), f (x * g) = ⅟ ↑(fintype.card G) • ∑ (x : G), f x,
  rw function.bijective.sum_comp (group.mul_right_bijective g) _,
end

end group_algebra

namespace representation

section invariants

open group_algebra

variables {k G V : Type*} [comm_semiring k] [group G] [add_comm_monoid V] [module k V]
variables (ρ : representation k G V)

/--
The subspace of invariants, consisting of the vectors fixed by all elements of `G`.
-/
def invariants : submodule k V :=
{ carrier := set_of (λ v, ∀ (g : G), ρ g v = v),
  zero_mem' := λ g, by simp only [map_zero],
  add_mem' := λ v w hv hw g, by simp only [hv g, hw g, map_add],
  smul_mem' := λ r v hv g, by simp only [hv g, linear_map.map_smulₛₗ, ring_hom.id_apply]}

@[simp]
lemma mem_invariants (v : V) : v ∈ invariants ρ ↔ ∀ (g: G), ρ g v = v := by refl

lemma invariants_eq_inter :
  (invariants ρ).carrier = ⋂ g : G, function.fixed_points (ρ g) :=
by {ext, simp [function.is_fixed_pt]}

variables [fintype G] [invertible (fintype.card G : k)]

/--
The action of `average k G` gives a projection map onto the subspace of invariants.
-/
@[simp]
noncomputable def average_map : V →ₗ[k] V := as_algebra_hom ρ (average k G)

/--
The `average_map` sends elements of `V` to the subspace of invariants.
-/
theorem average_map_invariant (v : V) : average_map ρ v ∈ invariants ρ :=
λ g, by rw [average_map, ←as_algebra_hom_single_one, ←linear_map.mul_apply,
  ←map_mul (as_algebra_hom ρ), mul_average_left]

/--
The `average_map` acts as the identity on the subspace of invariants.
-/
theorem average_map_id (v : V) (hv : v ∈ invariants ρ) : average_map ρ v = v :=
begin
  rw mem_invariants at hv,
  simp [average, map_sum, hv, finset.card_univ, nsmul_eq_smul_cast k _ v, smul_smul],
end

theorem is_proj_average_map : linear_map.is_proj ρ.invariants ρ.average_map :=
⟨ρ.average_map_invariant, ρ.average_map_id⟩

end invariants

namespace lin_hom

universes u

open category_theory Action

section Rep

variables {k : Type u} [comm_ring k] {G : Group.{u}}

lemma mem_invariants_iff_comm {X Y : Rep k G} (f : X.V →ₗ[k] Y.V) (g : G) :
  (lin_hom X.ρ Y.ρ) g f = f ↔ f.comp (X.ρ g) = (Y.ρ g).comp f :=
begin
  dsimp,
  erw [←ρ_Aut_apply_inv],
  rw [←linear_map.comp_assoc, ←Module.comp_def, ←Module.comp_def, iso.inv_comp_eq, ρ_Aut_apply_hom],
  exact comm,
end

/-- The invariants of the representation `lin_hom X.ρ Y.ρ` correspond to the the representation
homomorphisms from `X` to `Y` -/
@[simps]
def invariants_equiv_Rep_hom (X Y : Rep k G) : (lin_hom X.ρ Y.ρ).invariants ≃ₗ[k] (X ⟶ Y) :=
{ to_fun := λ f, ⟨f.val, λ g, (mem_invariants_iff_comm _ g).1 (f.property g)⟩,
  map_add' := λ _ _, rfl,
  map_smul' := λ _ _, rfl,
  inv_fun := λ f, ⟨f.hom, λ g, (mem_invariants_iff_comm _ g).2 (f.comm g)⟩,
  left_inv := λ _, by { ext, refl },
  right_inv := λ _, by { ext, refl } }

end Rep

section fdRep

variables {k : Type u} [field k] {G : Group.{u}}

/-- The invariants of the representation `lin_hom X.ρ Y.ρ` correspond to the the representation
homomorphisms from `X` to `Y` -/
def invariants_equiv_fdRep_hom (X Y : fdRep k G) : (lin_hom X.ρ Y.ρ).invariants ≃ₗ[k] (X ⟶ Y) :=
begin
  rw [←fdRep.forget₂_ρ, ←fdRep.forget₂_ρ],
  exact (lin_hom.invariants_equiv_Rep_hom _ _) ≪≫ₗ (fdRep.forget₂_hom_linear_equiv X Y),
end

end fdRep

end lin_hom

end representation


/-
Copyright (c) 2021 Anne Baanen. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Anne Baanen
-/
import number_theory.class_number.admissible_card_pow_degree
import number_theory.class_number.finite
import number_theory.function_field

/-!
# Class numbers of function fields

This file defines the class number of a function field as the (finite) cardinality of
the class group of its ring of integers. It also proves some elementary results
on the class number.

## Main definitions
- `function_field.class_number`: the class number of a function field is the (finite)
cardinality of the class group of its ring of integers
-/

namespace function_field
open_locale polynomial

variables (Fq F : Type) [field Fq] [fintype Fq] [field F]
variables [algebra Fq[X] F] [algebra (ratfunc Fq) F]
variables [is_scalar_tower Fq[X] (ratfunc Fq) F]
variables [function_field Fq F] [is_separable (ratfunc Fq) F]

open_locale classical

namespace ring_of_integers

open function_field

noncomputable instance  : fintype (class_group (ring_of_integers Fq F) F) :=
class_group.fintype_of_admissible_of_finite (ratfunc Fq) F
  (polynomial.card_pow_degree_is_admissible : absolute_value.is_admissible
    (polynomial.card_pow_degree : absolute_value Fq[X] ℤ))

end ring_of_integers

/-- The class number in a function field is the (finite) cardinality of the class group. -/
noncomputable def class_number : ℕ := fintype.card (class_group (ring_of_integers Fq F) F)

/-- The class number of a function field is `1` iff the ring of integers is a PID. -/
theorem class_number_eq_one_iff :
  class_number Fq F = 1 ↔ is_principal_ideal_ring (ring_of_integers Fq F) :=
card_class_group_eq_one_iff

end function_field


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

! This file was ported from Lean 3 source module data.polynomial.mirror
! leanprover-community/mathlib commit 2196ab363eb097c008d4497125e0dde23fb36db2
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Algebra.BigOperators.NatAntidiagonal
import Mathlib.Data.Polynomial.RingDivision

/-!
# "Mirror" of a univariate polynomial

In this file we define `Polynomial.mirror`, a variant of `Polynomial.reverse`. The difference
between `reverse` and `mirror` is that `reverse` will decrease the degree if the polynomial is
divisible by `X`.

## Main definitions

- `Polynomial.mirror`

## Main results

- `Polynomial.mirror_mul_of_domain`: `mirror` preserves multiplication.
- `Polynomial.irreducible_of_mirror`: an irreducibility criterion involving `mirror`

-/

namespace Polynomial

open Polynomial

section Semiring

variable {R : Type _} [Semiring R] (p q : R[X])

/-- mirror of a polynomial: reverses the coefficients while preserving `Polynomial.natDegree` -/
noncomputable def mirror :=
  p.reverse * X ^ p.natTrailingDegree
#align polynomial.mirror Polynomial.mirror

@[simp]
theorem mirror_zero : (0 : R[X]).mirror = 0 := by simp [mirror]
#align polynomial.mirror_zero Polynomial.mirror_zero

theorem mirror_monomial (n : ℕ) (a : R) : (monomial n a).mirror = monomial n a := by
  classical
    by_cases ha : a = 0
    · rw [ha, monomial_zero_right, mirror_zero]
    · rw [mirror, reverse, natDegree_monomial n a, if_neg ha, natTrailingDegree_monomial ha, ←
        C_mul_X_pow_eq_monomial, reflect_C_mul_X_pow, revAt_le (le_refl n), tsub_self, pow_zero,
        mul_one]
#align polynomial.mirror_monomial Polynomial.mirror_monomial

theorem mirror_C (a : R) : (C a).mirror = C a :=
  mirror_monomial 0 a
set_option linter.uppercaseLean3 false in
#align polynomial.mirror_C Polynomial.mirror_C

theorem mirror_X : X.mirror = (X : R[X]) :=
  mirror_monomial 1 (1 : R)
set_option linter.uppercaseLean3 false in
#align polynomial.mirror_X Polynomial.mirror_X

theorem mirror_natDegree : p.mirror.natDegree = p.natDegree := by
  by_cases hp : p = 0
  · rw [hp, mirror_zero]
  --Porting note: below two lines were `nontriviality R` in Lean3
  have : p.leadingCoeff ≠ 0 := by simpa
  let _ : Nontrivial R := nontrivial_of_ne _ _ this
  rw [mirror, natDegree_mul', reverse_natDegree, natDegree_X_pow,
    tsub_add_cancel_of_le p.natTrailingDegree_le_natDegree]
  rwa [leadingCoeff_X_pow, mul_one, reverse_leadingCoeff, Ne, trailingCoeff_eq_zero]
#align polynomial.mirror_nat_degree Polynomial.mirror_natDegree

theorem mirror_natTrailingDegree : p.mirror.natTrailingDegree = p.natTrailingDegree := by
  by_cases hp : p = 0
  · rw [hp, mirror_zero]
  ·
    rw [mirror, natTrailingDegree_mul_X_pow ((mt reverse_eq_zero.mp) hp),
      reverse_natTrailingDegree, zero_add]
#align polynomial.mirror_nat_trailing_degree Polynomial.mirror_natTrailingDegree

theorem coeff_mirror (n : ℕ) :
    p.mirror.coeff n = p.coeff (revAt (p.natDegree + p.natTrailingDegree) n) := by
  by_cases h2 : p.natDegree < n
  · rw [coeff_eq_zero_of_natDegree_lt (by rwa [mirror_natDegree])]
    by_cases h1 : n ≤ p.natDegree + p.natTrailingDegree
    · rw [revAt_le h1, coeff_eq_zero_of_lt_natTrailingDegree]
      exact (tsub_lt_iff_left h1).mpr (Nat.add_lt_add_right h2 _)
    · rw [← revAtFun_eq, revAtFun, if_neg h1, coeff_eq_zero_of_natDegree_lt h2]
  rw [not_lt] at h2
  rw [revAt_le (h2.trans (Nat.le_add_right _ _))]
  by_cases h3 : p.natTrailingDegree ≤ n
  · rw [← tsub_add_eq_add_tsub h2, ← tsub_tsub_assoc h2 h3, mirror, coeff_mul_X_pow', if_pos h3,
      coeff_reverse, revAt_le (tsub_le_self.trans h2)]
  rw [not_le] at h3
  rw [coeff_eq_zero_of_natDegree_lt (lt_tsub_iff_right.mpr (Nat.add_lt_add_left h3 _))]
  exact coeff_eq_zero_of_lt_natTrailingDegree (by rwa [mirror_natTrailingDegree])
#align polynomial.coeff_mirror Polynomial.coeff_mirror

--TODO: Extract `finset.sum_range_rev_at` lemma.
theorem mirror_eval_one : p.mirror.eval 1 = p.eval 1 := by
  simp_rw [eval_eq_sum_range, one_pow, mul_one, mirror_natDegree]
  refine' Finset.sum_bij_ne_zero _ _ _ _ _
  · exact fun n _ _ => revAt (p.natDegree + p.natTrailingDegree) n
  · intro n hn hp
    rw [Finset.mem_range_succ_iff] at *
    rw [revAt_le (hn.trans (Nat.le_add_right _ _))]
    rw [tsub_le_iff_tsub_le, add_comm, add_tsub_cancel_right, ← mirror_natTrailingDegree]
    exact natTrailingDegree_le_of_ne_zero hp
  · exact fun n₁ n₂ _ _ _ _ h => by rw [← @revAt_invol _ n₁, h, revAt_invol]
  · intro n hn hp
    use revAt (p.natDegree + p.natTrailingDegree) n
    refine' ⟨_, _, revAt_invol.symm⟩
    · rw [Finset.mem_range_succ_iff] at *
      rw [revAt_le (hn.trans (Nat.le_add_right _ _))]
      rw [tsub_le_iff_tsub_le, add_comm, add_tsub_cancel_right]
      exact natTrailingDegree_le_of_ne_zero hp
    · change p.mirror.coeff _ ≠ 0
      rwa [coeff_mirror, revAt_invol]
  · exact fun n _ _ => p.coeff_mirror n
#align polynomial.mirror_eval_one Polynomial.mirror_eval_one

theorem mirror_mirror : p.mirror.mirror = p :=
  Polynomial.ext fun n => by
    rw [coeff_mirror, coeff_mirror, mirror_natDegree, mirror_natTrailingDegree, revAt_invol]
#align polynomial.mirror_mirror Polynomial.mirror_mirror

variable {p q}

theorem mirror_involutive : Function.Involutive (mirror : R[X] → R[X]) :=
  mirror_mirror
#align polynomial.mirror_involutive Polynomial.mirror_involutive

theorem mirror_eq_iff : p.mirror = q ↔ p = q.mirror :=
  mirror_involutive.eq_iff
#align polynomial.mirror_eq_iff Polynomial.mirror_eq_iff

@[simp]
theorem mirror_inj : p.mirror = q.mirror ↔ p = q :=
  mirror_involutive.injective.eq_iff
#align polynomial.mirror_inj Polynomial.mirror_inj

@[simp]
theorem mirror_eq_zero : p.mirror = 0 ↔ p = 0 :=
  ⟨fun h => by rw [← p.mirror_mirror, h, mirror_zero], fun h => by rw [h, mirror_zero]⟩
#align polynomial.mirror_eq_zero Polynomial.mirror_eq_zero

variable (p q)

@[simp]
theorem mirror_trailingCoeff : p.mirror.trailingCoeff = p.leadingCoeff := by
  rw [leadingCoeff, trailingCoeff, mirror_natTrailingDegree, coeff_mirror,
    revAt_le (Nat.le_add_left _ _), add_tsub_cancel_right]
#align polynomial.mirror_trailing_coeff Polynomial.mirror_trailingCoeff

@[simp]
theorem mirror_leadingCoeff : p.mirror.leadingCoeff = p.trailingCoeff := by
  rw [← p.mirror_mirror, mirror_trailingCoeff, p.mirror_mirror]
#align polynomial.mirror_leading_coeff Polynomial.mirror_leadingCoeff

theorem coeff_mul_mirror :
    (p * p.mirror).coeff (p.natDegree + p.natTrailingDegree) = p.sum fun n => (· ^ 2) := by
  rw [coeff_mul, Finset.Nat.sum_antidiagonal_eq_sum_range_succ_mk]
  refine'
    (Finset.sum_congr rfl fun n hn => _).trans
      (p.sum_eq_of_subset (fun _ => (· ^ 2)) (fun _ => zero_pow zero_lt_two) _ fun n hn =>
          Finset.mem_range_succ_iff.mpr
            ((le_natDegree_of_mem_supp n hn).trans (Nat.le_add_right _ _))).symm
  rw [coeff_mirror, ← revAt_le (Finset.mem_range_succ_iff.mp hn), revAt_invol, ← sq]
#align polynomial.coeff_mul_mirror Polynomial.coeff_mul_mirror

variable [NoZeroDivisors R]

theorem natDegree_mul_mirror : (p * p.mirror).natDegree = 2 * p.natDegree := by
  by_cases hp : p = 0
  · rw [hp, MulZeroClass.zero_mul, natDegree_zero, MulZeroClass.mul_zero]
  rw [natDegree_mul hp (mt mirror_eq_zero.mp hp), mirror_natDegree, two_mul]
#align polynomial.nat_degree_mul_mirror Polynomial.natDegree_mul_mirror

theorem natTrailingDegree_mul_mirror : (p * p.mirror).natTrailingDegree = 2 * p.natTrailingDegree :=
  by
  by_cases hp : p = 0
  · rw [hp, MulZeroClass.zero_mul, natTrailingDegree_zero, MulZeroClass.mul_zero]
  rw [natTrailingDegree_mul hp (mt mirror_eq_zero.mp hp), mirror_natTrailingDegree, two_mul]
#align polynomial.nat_trailing_degree_mul_mirror Polynomial.natTrailingDegree_mul_mirror

end Semiring

section Ring

variable {R : Type _} [Ring R] (p q : R[X])

theorem mirror_neg : (-p).mirror = -p.mirror := by
  rw [mirror, mirror, reverse_neg, natTrailingDegree_neg, neg_mul_eq_neg_mul]
#align polynomial.mirror_neg Polynomial.mirror_neg

variable [NoZeroDivisors R]

theorem mirror_mul_of_domain : (p * q).mirror = p.mirror * q.mirror := by
  by_cases hp : p = 0
  · rw [hp, MulZeroClass.zero_mul, mirror_zero, MulZeroClass.zero_mul]
  by_cases hq : q = 0
  · rw [hq, MulZeroClass.mul_zero, mirror_zero, MulZeroClass.mul_zero]
  rw [mirror, mirror, mirror, reverse_mul_of_domain, natTrailingDegree_mul hp hq, pow_add]
  rw [mul_assoc, ← mul_assoc q.reverse, ← X_pow_mul (p := reverse q)]
  repeat' rw [mul_assoc]
#align polynomial.mirror_mul_of_domain Polynomial.mirror_mul_of_domain

theorem mirror_smul (a : R) : (a • p).mirror = a • p.mirror := by
  rw [← C_mul', ← C_mul', mirror_mul_of_domain, mirror_C]
#align polynomial.mirror_smul Polynomial.mirror_smul

end Ring

section CommRing

variable {R : Type _} [CommRing R] [NoZeroDivisors R] {f : R[X]}

theorem irreducible_of_mirror (h1 : ¬IsUnit f)
    (h2 : ∀ k, f * f.mirror = k * k.mirror → k = f ∨ k = -f ∨ k = f.mirror ∨ k = -f.mirror)
    (h3 : ∀ g, g ∣ f → g ∣ f.mirror → IsUnit g) : Irreducible f := by
  constructor
  · exact h1
  · intro g h fgh
    let k := g * h.mirror
    have key : f * f.mirror = k * k.mirror := by
      rw [fgh, mirror_mul_of_domain, mirror_mul_of_domain, mirror_mirror, mul_assoc, mul_comm h,
        mul_comm g.mirror, mul_assoc, ← mul_assoc]
    have g_dvd_f : g ∣ f := by
      rw [fgh]
      exact dvd_mul_right g h
    have h_dvd_f : h ∣ f := by
      rw [fgh]
      exact dvd_mul_left h g
    have g_dvd_k : g ∣ k := dvd_mul_right g h.mirror
    have h_dvd_k_rev : h ∣ k.mirror := by
      rw [mirror_mul_of_domain, mirror_mirror]
      exact dvd_mul_left h g.mirror
    have hk := h2 k key
    rcases hk with (hk | hk | hk | hk)
    · exact Or.inr (h3 h h_dvd_f (by rwa [← hk]))
    · exact Or.inr (h3 h h_dvd_f (by rwa [← neg_eq_iff_eq_neg.mpr hk, mirror_neg, dvd_neg]))
    · exact Or.inl (h3 g g_dvd_f (by rwa [← hk]))
    · exact Or.inl (h3 g g_dvd_f (by rwa [← neg_eq_iff_eq_neg.mpr hk, dvd_neg]))
#align polynomial.irreducible_of_mirror Polynomial.irreducible_of_mirror

end CommRing

end Polynomial


/-
Copyright (c) 2021 Yakov Pechersky. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yakov Pechersky

! This file was ported from Lean 3 source module algebra.tropical.big_operators
! leanprover-community/mathlib commit d6fad0e5bf2d6f48da9175d25c3dc5706b3834ce
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Algebra.BigOperators.Basic
import Mathlib.Data.List.MinMax
import Mathlib.Algebra.Tropical.Basic
import Mathlib.Order.ConditionallyCompleteLattice.Finset

/-!

# Tropicalization of finitary operations

This file provides the "big-op" or notation-based finitary operations on tropicalized types.
This allows easy conversion between sums to Infs and prods to sums. Results here are important
for expressing that evaluation of tropical polynomials are the minimum over a finite piecewise
collection of linear functions.

## Main declarations

* `untrop_sum`

## Implementation notes

No concrete (semi)ring is used here, only ones with inferrable order/lattice structure, to support
`Real`, `Rat`, `EReal`, and others (`ERat` is not yet defined).

Minima over `List α` are defined as producing a value in `WithTop α` so proofs about lists do not
directly transfer to minima over multisets or finsets.

-/

open BigOperators

variable {R S : Type _}

open Tropical Finset

theorem List.trop_sum [AddMonoid R] (l : List R) : trop l.sum = List.prod (l.map trop) := by
  induction' l with hd tl IH
  · simp
  · simp [← IH]
#align list.trop_sum List.trop_sum

theorem Multiset.trop_sum [AddCommMonoid R] (s : Multiset R) :
    trop s.sum = Multiset.prod (s.map trop) :=
  Quotient.inductionOn s (by simpa using List.trop_sum)
#align multiset.trop_sum Multiset.trop_sum

theorem trop_sum [AddCommMonoid R] (s : Finset S) (f : S → R) :
    trop (∑ i in s, f i) = ∏ i in s, trop (f i) := by
  convert Multiset.trop_sum (s.val.map f)
  simp only [Multiset.map_map, Function.comp_apply]
  rfl
#align trop_sum trop_sum

theorem List.untrop_prod [AddMonoid R] (l : List (Tropical R)) :
    untrop l.prod = List.sum (l.map untrop) := by
  induction' l with hd tl IH
  · simp
  · simp [← IH]
#align list.untrop_prod List.untrop_prod

theorem Multiset.untrop_prod [AddCommMonoid R] (s : Multiset (Tropical R)) :
    untrop s.prod = Multiset.sum (s.map untrop) :=
  Quotient.inductionOn s (by simpa using List.untrop_prod)
#align multiset.untrop_prod Multiset.untrop_prod

theorem untrop_prod [AddCommMonoid R] (s : Finset S) (f : S → Tropical R) :
    untrop (∏ i in s, f i) = ∑ i in s, untrop (f i) := by
  convert Multiset.untrop_prod (s.val.map f)
  simp only [Multiset.map_map, Function.comp_apply]
  rfl
#align untrop_prod untrop_prod

-- Porting note: replaced `coe` with `WithTop.some` in statement
theorem List.trop_minimum [LinearOrder R] (l : List R) :
    trop l.minimum = List.sum (l.map (trop ∘ WithTop.some)) := by
  induction' l with hd tl IH
  · simp
  · simp [List.minimum_cons, ← IH]
#align list.trop_minimum List.trop_minimum

theorem Multiset.trop_inf [LinearOrder R] [OrderTop R] (s : Multiset R) :
    trop s.inf = Multiset.sum (s.map trop) := by
  induction' s using Multiset.induction with s x IH
  · simp
  · simp [← IH]
#align multiset.trop_inf Multiset.trop_inf

theorem Finset.trop_inf [LinearOrder R] [OrderTop R] (s : Finset S) (f : S → R) :
    trop (s.inf f) = ∑ i in s, trop (f i) := by
  convert Multiset.trop_inf (s.val.map f)
  simp only [Multiset.map_map, Function.comp_apply]
  rfl
#align finset.trop_inf Finset.trop_inf

theorem trop_infₛ_image [ConditionallyCompleteLinearOrder R] (s : Finset S) (f : S → WithTop R) :
    trop (infₛ (f '' s)) = ∑ i in s, trop (f i) := by
  rcases s.eq_empty_or_nonempty with (rfl | h)
  · simp only [Set.image_empty, coe_empty, sum_empty, WithTop.infₛ_empty, trop_top]
  rw [← inf'_eq_cinfₛ_image _ h, inf'_eq_inf, s.trop_inf]
#align trop_Inf_image trop_infₛ_image

theorem trop_infᵢ [ConditionallyCompleteLinearOrder R] [Fintype S] (f : S → WithTop R) :
    trop (⨅ i : S, f i) = ∑ i : S, trop (f i) := by
  rw [infᵢ, ← Set.image_univ, ← coe_univ, trop_infₛ_image]
#align trop_infi trop_infᵢ

theorem Multiset.untrop_sum [LinearOrder R] [OrderTop R] (s : Multiset (Tropical R)) :
    untrop s.sum = Multiset.inf (s.map untrop) := by
  induction' s using Multiset.induction with s x IH
  · simp
  · simp only [sum_cons, ge_iff_le, untrop_add, untrop_le_iff, map_cons, inf_cons, ← IH]
    rfl
#align multiset.untrop_sum Multiset.untrop_sum

theorem Finset.untrop_sum' [LinearOrder R] [OrderTop R] (s : Finset S) (f : S → Tropical R) :
    untrop (∑ i in s, f i) = s.inf (untrop ∘ f) := by
  convert Multiset.untrop_sum (s.val.map f)
  simp only [Multiset.map_map, Function.comp_apply]
  rfl
#align finset.untrop_sum' Finset.untrop_sum'

theorem untrop_sum_eq_infₛ_image [ConditionallyCompleteLinearOrder R] (s : Finset S)
    (f : S → Tropical (WithTop R)) : untrop (∑ i in s, f i) = infₛ (untrop ∘ f '' s) := by
  rcases s.eq_empty_or_nonempty with (rfl | h)
  · simp only [Set.image_empty, coe_empty, sum_empty, WithTop.infₛ_empty, untrop_zero]
  · rw [← inf'_eq_cinfₛ_image _ h, inf'_eq_inf, Finset.untrop_sum']
#align untrop_sum_eq_Inf_image untrop_sum_eq_infₛ_image

theorem untrop_sum [ConditionallyCompleteLinearOrder R] [Fintype S] (f : S → Tropical (WithTop R)) :
    untrop (∑ i : S, f i) = ⨅ i : S, untrop (f i) := by
  rw [infᵢ,← Set.image_univ,← coe_univ, untrop_sum_eq_infₛ_image]
  rfl
#align untrop_sum untrop_sum

/-- Note we cannot use `i ∈ s` instead of `i : s` here
as it is simply not true on conditionally complete lattices! -/
theorem Finset.untrop_sum [ConditionallyCompleteLinearOrder R] (s : Finset S)
    (f : S → Tropical (WithTop R)) : untrop (∑ i in s, f i) = ⨅ i : s, untrop (f i) := by
  simpa [← _root_.untrop_sum] using sum_attach.symm
#align finset.untrop_sum Finset.untrop_sum


/-
3. Prove ¬(p ↔ ¬p) without using classical logic.
-/

variables p : Prop

example : ¬(p ↔ ¬p) :=
    assume hpiffnp : p ↔ ¬p,
    have hpimplnp : p → ¬p, from iff.elim_left hpiffnp,
    have hnpimplp : ¬p → p, from iff.elim_right hpiffnp,
    have hnp : ¬p, from
        assume hp : p,
        show false, from absurd hp (hpimplnp hp),
    have hp : p, from hnpimplp hnp,
    absurd hp hnp
-- short version
example : ¬(p ↔ ¬p) :=
    λ h,
    have hnp : ¬p, from λ hp, absurd hp (h.mp hp),
    absurd (h.mpr hnp) hnp


/-
Copyright (c) 2015 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Jeremy Avigad

Extensional equality for functions, and a proof of function extensionality from quotients.

! This file was ported from Lean 3 source module init.funext
! leanprover-community/mathlib commit 855e0efed3137762a7ba9aca242499b3cce59406
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
prelude
import Leanbin.Init.Data.Quot
import Leanbin.Init.Logic

open Quotient

universe u v

variable {α : Sort u} {β : α → Sort v}

namespace Function

/-- The relation stating that two functions are pointwise equal. -/
protected def Equiv (f₁ f₂ : ∀ x : α, β x) : Prop :=
  ∀ x, f₁ x = f₂ x
#align function.equiv Function.Equiv

-- mathport name: «expr ~ »
local infixl:50 " ~ " => Function.Equiv

protected theorem Equiv.refl (f : ∀ x : α, β x) : f ~ f := fun x => rfl
#align function.equiv.refl Function.Equiv.refl

protected theorem Equiv.symm {f₁ f₂ : ∀ x : α, β x} : f₁ ~ f₂ → f₂ ~ f₁ := fun h x => Eq.symm (h x)
#align function.equiv.symm Function.Equiv.symm

protected theorem Equiv.trans {f₁ f₂ f₃ : ∀ x : α, β x} : f₁ ~ f₂ → f₂ ~ f₃ → f₁ ~ f₃ :=
  fun h₁ h₂ x => Eq.trans (h₁ x) (h₂ x)
#align function.equiv.trans Function.Equiv.trans

protected theorem Equiv.is_equivalence (α : Sort u) (β : α → Sort v) :
    Equivalence (@Function.Equiv α β) :=
  Equivalence.mk (@Function.Equiv α β) (@Equiv.refl α β) (@Equiv.symm α β) (@Equiv.trans α β)
#align function.equiv.is_equivalence Function.Equiv.is_equivalence

/-- The setoid generated by pointwise equality. -/
@[local instance]
def funSetoid (α : Sort u) (β : α → Sort v) : Setoid (∀ x : α, β x) :=
  Setoid.mk (@Function.Equiv α β) (Function.Equiv.is_equivalence α β)
#align function.fun_setoid Function.funSetoid

/-- The quotient of the function type by pointwise equality. -/
def Extfun (α : Sort u) (β : α → Sort v) : Sort imax u v :=
  Quotient (funSetoid α β)
#align function.extfun Function.Extfun

/-- The map from functions into the qquotient by pointwise equality. -/
def funToExtfun (f : ∀ x : α, β x) : Extfun α β :=
  ⟦f⟧
#align function.fun_to_extfun Function.funToExtfun

/-- From an element of `extfun` we can retrieve an actual function. -/
def extfunApp (f : Extfun α β) : ∀ x : α, β x := fun x =>
  Quot.liftOn f (fun f : ∀ x : α, β x => f x) fun f₁ f₂ h => h x
#align function.extfun_app Function.extfunApp

end Function

open Function

attribute [local instance] fun_setoid

#print funext /-
/-- Function extensionality, proven using quotients. -/
theorem funext {f₁ f₂ : ∀ x : α, β x} (h : ∀ x, f₁ x = f₂ x) : f₁ = f₂ :=
  show extfunApp ⟦f₁⟧ = extfunApp ⟦f₂⟧ from congr_arg extfunApp (sound h)
#align funext funext
-/

attribute [intro!] funext

-- mathport name: «expr ~ »
local infixl:50 " ~ " => Function.Equiv

instance Pi.subsingleton [∀ a, Subsingleton (β a)] : Subsingleton (∀ a, β a) :=
  ⟨fun f₁ f₂ => funext fun a => Subsingleton.elim (f₁ a) (f₂ a)⟩
#align pi.subsingleton Pi.subsingleton


/-
Copyright (c) 2021 Devon Tuma. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Devon Tuma
-/
import analysis.asymptotics.asymptotics
import analysis.normed.order.basic
import data.polynomial.eval
import topology.algebra.order.liminf_limsup

/-!
# Super-Polynomial Function Decay

This file defines a predicate `asymptotics.superpolynomial_decay f` for a function satisfying
  one of following equivalent definitions (The definition is in terms of the first condition):

* `x ^ n * f` tends to `𝓝 0` for all (or sufficiently large) naturals `n`
* `|x ^ n * f|` tends to `𝓝 0` for all naturals `n` (`superpolynomial_decay_iff_abs_tendsto_zero`)
* `|x ^ n * f|` is bounded for all naturals `n` (`superpolynomial_decay_iff_abs_is_bounded_under`)
* `f` is `o(x ^ c)` for all integers `c` (`superpolynomial_decay_iff_is_o`)
* `f` is `O(x ^ c)` for all integers `c` (`superpolynomial_decay_iff_is_O`)

These conditions are all equivalent to conditions in terms of polynomials, replacing `x ^ c` with
  `p(x)` or `p(x)⁻¹` as appropriate, since asymptotically `p(x)` behaves like `X ^ p.nat_degree`.
These further equivalences are not proven in mathlib but would be good future projects.

The definition of superpolynomial decay for `f : α → β` is relative to a parameter `k : α → β`.
Super-polynomial decay then means `f x` decays faster than `(k x) ^ c` for all integers `c`.
Equivalently `f x` decays faster than `p.eval (k x)` for all polynomials `p : β[X]`.
The definition is also relative to a filter `l : filter α` where the decay rate is compared.

When the map `k` is given by `n ↦ ↑n : ℕ → ℝ` this defines negligible functions:
https://en.wikipedia.org/wiki/Negligible_function

When the map `k` is given by `(r₁,...,rₙ) ↦ r₁*...*rₙ : ℝⁿ → ℝ` this is equivalent
  to the definition of rapidly decreasing functions given here:
https://ncatlab.org/nlab/show/rapidly+decreasing+function

# Main Theorems

* `superpolynomial_decay.polynomial_mul` says that if `f(x)` is negligible,
    then so is `p(x) * f(x)` for any polynomial `p`.
* `superpolynomial_decay_iff_zpow_tendsto_zero` gives an equivalence between definitions in terms
    of decaying faster than `k(x) ^ n` for all naturals `n` or `k(x) ^ c` for all integer `c`.
-/

namespace asymptotics

open_locale topology polynomial
open filter

/-- `f` has superpolynomial decay in parameter `k` along filter `l` if
  `k ^ n * f` tends to zero at `l` for all naturals `n` -/
def superpolynomial_decay {α β : Type*} [topological_space β] [comm_semiring β]
  (l : filter α) (k : α → β) (f : α → β) :=
∀ (n : ℕ), tendsto (λ (a : α), (k a) ^ n * f a) l (𝓝 0)

variables {α β : Type*} {l : filter α} {k : α → β} {f g g' : α → β}

section comm_semiring

variables [topological_space β] [comm_semiring β]

lemma superpolynomial_decay.congr' (hf : superpolynomial_decay l k f)
  (hfg : f =ᶠ[l] g) : superpolynomial_decay l k g :=
λ z, (hf z).congr' (eventually_eq.mul (eventually_eq.refl l _) hfg)

lemma superpolynomial_decay.congr (hf : superpolynomial_decay l k f)
  (hfg : ∀ x, f x = g x) : superpolynomial_decay l k g :=
λ z, (hf z).congr (λ x, congr_arg (λ a, k x ^ z * a) $ hfg x)

@[simp]
lemma superpolynomial_decay_zero (l : filter α) (k : α → β) :
  superpolynomial_decay l k 0 :=
λ z, by simpa only [pi.zero_apply, mul_zero] using tendsto_const_nhds

lemma superpolynomial_decay.add [has_continuous_add β] (hf : superpolynomial_decay l k f)
  (hg : superpolynomial_decay l k g) : superpolynomial_decay l k (f + g) :=
λ z, by simpa only [mul_add, add_zero, pi.add_apply] using (hf z).add (hg z)

lemma superpolynomial_decay.mul [has_continuous_mul β] (hf : superpolynomial_decay l k f)
  (hg : superpolynomial_decay l k g) : superpolynomial_decay l k (f * g) :=
λ z, by simpa only [mul_assoc, one_mul, mul_zero, pow_zero] using (hf z).mul (hg 0)

lemma superpolynomial_decay.mul_const [has_continuous_mul β] (hf : superpolynomial_decay l k f)
  (c : β) : superpolynomial_decay l k (λ n, f n * c) :=
λ z, by simpa only [←mul_assoc, zero_mul] using tendsto.mul_const c (hf z)

lemma superpolynomial_decay.const_mul [has_continuous_mul β] (hf : superpolynomial_decay l k f)
  (c : β) : superpolynomial_decay l k (λ n, c * f n) :=
(hf.mul_const c).congr (λ _, mul_comm _ _)

lemma superpolynomial_decay.param_mul (hf : superpolynomial_decay l k f) :
  superpolynomial_decay l k (k * f) :=
λ z, tendsto_nhds.2 (λ s hs hs0, l.sets_of_superset ((tendsto_nhds.1 (hf $ z + 1)) s hs hs0)
  (λ x hx, by simpa only [set.mem_preimage, pi.mul_apply, ← mul_assoc, ← pow_succ'] using hx))

lemma superpolynomial_decay.mul_param (hf : superpolynomial_decay l k f) :
  superpolynomial_decay l k (f * k) :=
(hf.param_mul).congr (λ _, mul_comm _ _)

lemma superpolynomial_decay.param_pow_mul (hf : superpolynomial_decay l k f)
  (n : ℕ) : superpolynomial_decay l k (k ^ n * f) :=
begin
  induction n with n hn,
  { simpa only [one_mul, pow_zero] using hf },
  { simpa only [pow_succ, mul_assoc] using hn.param_mul }
end

lemma superpolynomial_decay.mul_param_pow (hf : superpolynomial_decay l k f)
  (n : ℕ) : superpolynomial_decay l k (f * k ^ n) :=
(hf.param_pow_mul n).congr (λ _, mul_comm _ _)

lemma superpolynomial_decay.polynomial_mul [has_continuous_add β] [has_continuous_mul β]
  (hf : superpolynomial_decay l k f) (p : β[X]) :
  superpolynomial_decay l k (λ x, (p.eval $ k x) * f x) :=
polynomial.induction_on' p (λ p q hp hq, by simpa [add_mul] using hp.add hq)
  (λ n c, by simpa [mul_assoc] using (hf.param_pow_mul n).const_mul c)

lemma superpolynomial_decay.mul_polynomial [has_continuous_add β] [has_continuous_mul β]
  (hf : superpolynomial_decay l k f) (p : β[X]) :
  superpolynomial_decay l k (λ x, f x * (p.eval $ k x)) :=
(hf.polynomial_mul p).congr (λ _, mul_comm _ _)

end comm_semiring

section ordered_comm_semiring

variables [topological_space β] [ordered_comm_semiring β] [order_topology β]

end ordered_comm_semiring

section linear_ordered_comm_ring

variables [topological_space β] [linear_ordered_comm_ring β] [order_topology β]

variables (l k f)

lemma superpolynomial_decay_iff_abs_tendsto_zero :
  superpolynomial_decay l k f ↔ ∀ (n : ℕ), tendsto (λ (a : α), |(k a) ^ n * f a|) l (𝓝 0) :=
⟨λ h z, (tendsto_zero_iff_abs_tendsto_zero _).1 (h z),
  λ h z, (tendsto_zero_iff_abs_tendsto_zero _).2 (h z)⟩

lemma superpolynomial_decay_iff_superpolynomial_decay_abs :
  superpolynomial_decay l k f ↔ superpolynomial_decay l (λ a, |k a|) (λ a, |f a|) :=
(superpolynomial_decay_iff_abs_tendsto_zero l k f).trans
  (by simp_rw [superpolynomial_decay, abs_mul, abs_pow])

variables {l k f}

lemma superpolynomial_decay.trans_eventually_abs_le (hf : superpolynomial_decay l k f)
  (hfg : abs ∘ g ≤ᶠ[l] abs ∘ f) : superpolynomial_decay l k g :=
begin
  rw superpolynomial_decay_iff_abs_tendsto_zero at hf ⊢,
  refine λ z, tendsto_of_tendsto_of_tendsto_of_le_of_le' (tendsto_const_nhds) (hf z)
    (eventually_of_forall $ λ x, abs_nonneg _) (hfg.mono $ λ x hx, _),
  calc |k x ^ z * g x| = |k x ^ z| * |g x| : abs_mul (k x ^ z) (g x)
    ... ≤ |k x ^ z| * |f x| : mul_le_mul le_rfl hx (abs_nonneg _) (abs_nonneg _)
    ... = |k x ^ z * f x| : (abs_mul (k x ^ z) (f x)).symm,
end

lemma superpolynomial_decay.trans_abs_le (hf : superpolynomial_decay l k f)
  (hfg : ∀ x, |g x| ≤ |f x|) : superpolynomial_decay l k g :=
hf.trans_eventually_abs_le (eventually_of_forall hfg)

end linear_ordered_comm_ring

section field

variables [topological_space β] [field β] (l k f)

lemma superpolynomial_decay_mul_const_iff [has_continuous_mul β] {c : β} (hc0 : c ≠ 0) :
  superpolynomial_decay l k (λ n, f n * c) ↔ superpolynomial_decay l k f :=
⟨λ h, (h.mul_const c⁻¹).congr (λ x, by simp [mul_assoc, mul_inv_cancel hc0]), λ h, h.mul_const c⟩

lemma superpolynomial_decay_const_mul_iff [has_continuous_mul β] {c : β} (hc0 : c ≠ 0) :
  superpolynomial_decay l k (λ n, c * f n) ↔ superpolynomial_decay l k f :=
⟨λ h, (h.const_mul c⁻¹).congr (λ x, by simp [← mul_assoc, inv_mul_cancel hc0]), λ h, h.const_mul c⟩

variables {l k f}

end field

section linear_ordered_field

variables [topological_space β] [linear_ordered_field β] [order_topology β]

variable (f)

lemma superpolynomial_decay_iff_abs_is_bounded_under (hk : tendsto k l at_top) :
  superpolynomial_decay l k f ↔ ∀ (z : ℕ), is_bounded_under (≤) l (λ (a : α), |(k a) ^ z * f a|) :=
begin
  refine ⟨λ h z, tendsto.is_bounded_under_le (tendsto.abs (h z)),
    λ h, (superpolynomial_decay_iff_abs_tendsto_zero l k f).2 (λ z, _)⟩,
  obtain ⟨m, hm⟩ := h (z + 1),
  have h1 : tendsto (λ (a : α), (0 : β)) l (𝓝 0) := tendsto_const_nhds,
  have h2 : tendsto (λ (a : α), |(k a)⁻¹| * m) l (𝓝 0) := (zero_mul m) ▸ tendsto.mul_const m
    ((tendsto_zero_iff_abs_tendsto_zero _).1 hk.inv_tendsto_at_top),
  refine tendsto_of_tendsto_of_tendsto_of_le_of_le' h1 h2
    (eventually_of_forall (λ x, abs_nonneg _)) ((eventually_map.1 hm).mp _),
  refine ((hk.eventually_ne_at_top 0).mono $ λ x hk0 hx, _),
  refine eq.trans_le _ (mul_le_mul_of_nonneg_left hx $ abs_nonneg (k x)⁻¹),
  rw [← abs_mul, ← mul_assoc, pow_succ, ← mul_assoc, inv_mul_cancel hk0, one_mul],
end

lemma superpolynomial_decay_iff_zpow_tendsto_zero (hk : tendsto k l at_top) :
  superpolynomial_decay l k f ↔ ∀ (z : ℤ), tendsto (λ (a : α), (k a) ^ z * f a) l (𝓝 0) :=
begin
  refine ⟨λ h z, _, λ h n, by simpa only [zpow_coe_nat] using h (n : ℤ)⟩,
  by_cases hz : 0 ≤ z,
  { lift z to ℕ using hz,
    simpa using h z },
  { have : tendsto (λ a, (k a) ^ z) l (𝓝 0) :=
      tendsto.comp (tendsto_zpow_at_top_zero (not_le.1 hz)) hk,
    have h : tendsto f l (𝓝 0) := by simpa using h 0,
    exact (zero_mul (0 : β)) ▸ this.mul h },
end

variable {f}

lemma superpolynomial_decay.param_zpow_mul (hk : tendsto k l at_top)
  (hf : superpolynomial_decay l k f) (z : ℤ) : superpolynomial_decay l k (λ a, k a ^ z * f a) :=
begin
  rw superpolynomial_decay_iff_zpow_tendsto_zero _ hk at hf ⊢,
  refine λ z', (hf $ z' + z).congr' ((hk.eventually_ne_at_top 0).mono (λ x hx, _)),
  simp [zpow_add₀ hx, mul_assoc, pi.mul_apply],
end

lemma superpolynomial_decay.mul_param_zpow (hk : tendsto k l at_top)
  (hf : superpolynomial_decay l k f) (z : ℤ) : superpolynomial_decay l k (λ a, f a * k a ^ z) :=
(hf.param_zpow_mul hk z).congr (λ _, mul_comm _ _)

lemma superpolynomial_decay.inv_param_mul (hk : tendsto k l at_top)
  (hf : superpolynomial_decay l k f) : superpolynomial_decay l k (k⁻¹ * f) :=
by simpa using (hf.param_zpow_mul hk (-1))

lemma superpolynomial_decay.param_inv_mul (hk : tendsto k l at_top)
  (hf : superpolynomial_decay l k f) : superpolynomial_decay l k (f * k⁻¹) :=
(hf.inv_param_mul hk).congr (λ _, mul_comm _ _)

variable (f)

lemma superpolynomial_decay_param_mul_iff (hk : tendsto k l at_top) :
  superpolynomial_decay l k (k * f) ↔ superpolynomial_decay l k f :=
⟨λ h, (h.inv_param_mul hk).congr' ((hk.eventually_ne_at_top 0).mono
  (λ x hx, by simp [← mul_assoc, inv_mul_cancel hx])), λ h, h.param_mul⟩

lemma superpolynomial_decay_mul_param_iff (hk : tendsto k l at_top) :
  superpolynomial_decay l k (f * k) ↔ superpolynomial_decay l k f :=
by simpa [mul_comm k] using superpolynomial_decay_param_mul_iff f hk

lemma superpolynomial_decay_param_pow_mul_iff (hk : tendsto k l at_top) (n : ℕ) :
  superpolynomial_decay l k (k ^ n * f) ↔ superpolynomial_decay l k f :=
begin
  induction n with n hn,
  { simp },
  { simpa [pow_succ, ← mul_comm k, mul_assoc,
      superpolynomial_decay_param_mul_iff (k ^ n * f) hk] using hn }
end

lemma superpolynomial_decay_mul_param_pow_iff (hk : tendsto k l at_top) (n : ℕ) :
  superpolynomial_decay l k (f * k ^ n) ↔ superpolynomial_decay l k f :=
by simpa [mul_comm f] using superpolynomial_decay_param_pow_mul_iff f hk n

variable {f}

end linear_ordered_field

section normed_linear_ordered_field

variable [normed_linear_ordered_field β]

variables (l k f)

lemma superpolynomial_decay_iff_norm_tendsto_zero :
  superpolynomial_decay l k f ↔ ∀ (n : ℕ), tendsto (λ (a : α), ‖(k a) ^ n * f a‖) l (𝓝 0) :=
⟨λ h z, tendsto_zero_iff_norm_tendsto_zero.1 (h z),
  λ h z, tendsto_zero_iff_norm_tendsto_zero.2 (h z)⟩

lemma superpolynomial_decay_iff_superpolynomial_decay_norm :
  superpolynomial_decay l k f ↔ superpolynomial_decay l (λ a, ‖k a‖) (λ a, ‖f a‖) :=
(superpolynomial_decay_iff_norm_tendsto_zero l k f).trans (by simp [superpolynomial_decay])

variables {l k}

variable [order_topology β]

lemma superpolynomial_decay_iff_is_O (hk : tendsto k l at_top) :
  superpolynomial_decay l k f ↔ ∀ (z : ℤ), f =O[l] (λ (a : α), (k a) ^ z) :=
begin
  refine (superpolynomial_decay_iff_zpow_tendsto_zero f hk).trans _,
  have hk0 : ∀ᶠ x in l, k x ≠ 0 := hk.eventually_ne_at_top 0,
  refine ⟨λ h z, _, λ h z, _⟩,
  { refine is_O_of_div_tendsto_nhds (hk0.mono (λ x hx hxz, absurd (zpow_eq_zero hxz) hx)) 0 _,
    have : (λ (a : α), k a ^ z)⁻¹ = (λ (a : α), k a ^ (- z)) := funext (λ x, by simp),
    rw [div_eq_mul_inv, mul_comm f, this],
    exact h (-z) },
  { suffices : (λ (a : α), k a ^ z * f a) =O[l] (λ (a : α), (k a)⁻¹),
      from is_O.trans_tendsto this hk.inv_tendsto_at_top,
    refine ((is_O_refl (λ a, (k a) ^ z) l).mul (h (- (z + 1)))).trans
      (is_O.of_bound 1 $ hk0.mono (λ a ha0, _)),
    simp only [one_mul, neg_add z 1, zpow_add₀ ha0, ← mul_assoc, zpow_neg,
      mul_inv_cancel (zpow_ne_zero z ha0), zpow_one] }
end

lemma superpolynomial_decay_iff_is_o (hk : tendsto k l at_top) :
  superpolynomial_decay l k f ↔ ∀ (z : ℤ), f =o[l] (λ (a : α), (k a) ^ z) :=
begin
  refine ⟨λ h z, _, λ h, (superpolynomial_decay_iff_is_O f hk).2 (λ z, (h z).is_O)⟩,
  have hk0 : ∀ᶠ x in l, k x ≠ 0 := hk.eventually_ne_at_top 0,
  have : (λ (x : α), (1 : β)) =o[l] k := is_o_of_tendsto'
    (hk0.mono (λ x hkx hkx', absurd hkx' hkx)) (by simpa using hk.inv_tendsto_at_top),
  have : f =o[l] (λ (x : α), k x * k x ^ (z - 1)),
    by simpa using this.mul_is_O (((superpolynomial_decay_iff_is_O f hk).1 h) $ z - 1),
  refine this.trans_is_O (is_O.of_bound 1 (hk0.mono $ λ x hkx, le_of_eq _)),
  rw [one_mul, zpow_sub_one₀ hkx, mul_comm (k x), mul_assoc, inv_mul_cancel hkx, mul_one],
end

end normed_linear_ordered_field

end asymptotics


/-
Copyright (c) 2014 Jeremy Avigad. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jeremy Avigad, Leonardo de Moura, Simon Hudon, Mario Carneiro
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.algebra.group.to_additive
import Mathlib.tactic.basic
import Mathlib.PostPort

universes u l

namespace Mathlib

/-!
# Typeclasses for (semi)groups and monoid

In this file we define typeclasses for algebraic structures with one binary operation.
The classes are named `(add_)?(comm_)?(semigroup|monoid|group)`, where `add_` means that
the class uses additive notation and `comm_` means that the class assumes that the binary
operation is commutative.

The file does not contain any lemmas except for

* axioms of typeclasses restated in the root namespace;
* lemmas required for instances.

For basic lemmas about these classes see `algebra.group.basic`.
-/

/- Additive "sister" structures.
   Example, add_semigroup mirrors semigroup.
   These structures exist just to help automation.
   In an alternative design, we could have the binary operation as an
   extra argument for semigroup, monoid, group, etc. However, the lemmas
   would be hard to index since they would not contain any constant.
   For example, mul_assoc would be

   lemma mul_assoc {α : Type u} {op : α → α → α} [semigroup α op] :
                   ∀ a b c : α, op (op a b) c = op a (op b c) :=
    semigroup.mul_assoc

   The simplifier cannot effectively use this lemma since the pattern for
   the left-hand-side would be

        ?op (?op ?a ?b) ?c

   Remark: we use a tactic for transporting theorems from the multiplicative fragment
   to the additive one.
-/

/-- `left_mul g` denotes left multiplication by `g` -/
def left_add {G : Type u} [Add G] : G → G → G := fun (g x : G) => g + x

/-- `right_mul g` denotes right multiplication by `g` -/
def right_mul {G : Type u} [Mul G] : G → G → G := fun (g x : G) => x * g

/-- A semigroup is a type with an associative `(*)`. -/
class semigroup (G : Type u) extends Mul G where
  mul_assoc : ∀ (a b c : G), a * b * c = a * (b * c)

/-- An additive semigroup is a type with an associative `(+)`. -/
class add_semigroup (G : Type u) extends Add G where
  add_assoc : ∀ (a b c : G), a + b + c = a + (b + c)

theorem mul_assoc {G : Type u} [semigroup G] (a : G) (b : G) (c : G) : a * b * c = a * (b * c) :=
  semigroup.mul_assoc

protected instance add_semigroup.to_is_associative {G : Type u} [add_semigroup G] :
    is_associative G Add.add :=
  is_associative.mk add_assoc

/-- A commutative semigroup is a type with an associative commutative `(*)`. -/
class comm_semigroup (G : Type u) extends semigroup G where
  mul_comm : ∀ (a b : G), a * b = b * a

/-- A commutative additive semigroup is a type with an associative commutative `(+)`. -/
class add_comm_semigroup (G : Type u) extends add_semigroup G where
  add_comm : ∀ (a b : G), a + b = b + a

theorem mul_comm {G : Type u} [comm_semigroup G] (a : G) (b : G) : a * b = b * a :=
  comm_semigroup.mul_comm

protected instance comm_semigroup.to_is_commutative {G : Type u} [comm_semigroup G] :
    is_commutative G Mul.mul :=
  is_commutative.mk mul_comm

/-- A `left_cancel_semigroup` is a semigroup such that `a * b = a * c` implies `b = c`. -/
class left_cancel_semigroup (G : Type u) extends semigroup G where
  mul_left_cancel : ∀ (a b c : G), a * b = a * c → b = c

/-- An `add_left_cancel_semigroup` is an additive semigroup such that
`a + b = a + c` implies `b = c`. -/
class add_left_cancel_semigroup (G : Type u) extends add_semigroup G where
  add_left_cancel : ∀ (a b c : G), a + b = a + c → b = c

theorem mul_left_cancel {G : Type u} [left_cancel_semigroup G] {a : G} {b : G} {c : G} :
    a * b = a * c → b = c :=
  left_cancel_semigroup.mul_left_cancel a b c

theorem mul_left_cancel_iff {G : Type u} [left_cancel_semigroup G] {a : G} {b : G} {c : G} :
    a * b = a * c ↔ b = c :=
  { mp := mul_left_cancel, mpr := congr_arg fun {b : G} => a * b }

theorem mul_right_injective {G : Type u} [left_cancel_semigroup G] (a : G) :
    function.injective (Mul.mul a) :=
  fun (b c : G) => mul_left_cancel

@[simp] theorem add_right_inj {G : Type u} [add_left_cancel_semigroup G] (a : G) {b : G} {c : G} :
    a + b = a + c ↔ b = c :=
  function.injective.eq_iff (add_right_injective a)

/-- A `right_cancel_semigroup` is a semigroup such that `a * b = c * b` implies `a = c`. -/
class right_cancel_semigroup (G : Type u) extends semigroup G where
  mul_right_cancel : ∀ (a b c : G), a * b = c * b → a = c

/-- An `add_right_cancel_semigroup` is an additive semigroup such that
`a + b = c + b` implies `a = c`. -/
class add_right_cancel_semigroup (G : Type u) extends add_semigroup G where
  add_right_cancel : ∀ (a b c : G), a + b = c + b → a = c

theorem mul_right_cancel {G : Type u} [right_cancel_semigroup G] {a : G} {b : G} {c : G} :
    a * b = c * b → a = c :=
  right_cancel_semigroup.mul_right_cancel a b c

theorem add_right_cancel_iff {G : Type u} [add_right_cancel_semigroup G] {a : G} {b : G} {c : G} :
    b + a = c + a ↔ b = c :=
  { mp := add_right_cancel, mpr := congr_arg fun {b : G} => b + a }

theorem add_left_injective {G : Type u} [add_right_cancel_semigroup G] (a : G) :
    function.injective fun (x : G) => x + a :=
  fun (b c : G) => add_right_cancel

@[simp] theorem add_left_inj {G : Type u} [add_right_cancel_semigroup G] (a : G) {b : G} {c : G} :
    b + a = c + a ↔ b = c :=
  function.injective.eq_iff (add_left_injective a)

/-- A `monoid` is a `semigroup` with an element `1` such that `1 * a = a * 1 = a`. -/
class monoid (M : Type u) extends semigroup M, HasOne M where
  one_mul : ∀ (a : M), 1 * a = a
  mul_one : ∀ (a : M), a * 1 = a

/-- An `add_monoid` is an `add_semigroup` with an element `0` such that `0 + a = a + 0 = a`. -/
class add_monoid (M : Type u) extends HasZero M, add_semigroup M where
  zero_add : ∀ (a : M), 0 + a = a
  add_zero : ∀ (a : M), a + 0 = a

@[simp] theorem one_mul {M : Type u} [monoid M] (a : M) : 1 * a = a := monoid.one_mul

@[simp] theorem add_zero {M : Type u} [add_monoid M] (a : M) : a + 0 = a := add_monoid.add_zero

protected instance monoid_to_is_left_id {M : Type u} [monoid M] : is_left_id M Mul.mul 1 :=
  is_left_id.mk monoid.one_mul

protected instance add_monoid_to_is_right_id {M : Type u} [add_monoid M] :
    is_right_id M Add.add 0 :=
  is_right_id.mk add_monoid.add_zero

theorem left_neg_eq_right_neg {M : Type u} [add_monoid M] {a : M} {b : M} {c : M} (hba : b + a = 0)
    (hac : a + c = 0) : b = c :=
  sorry

/-- A commutative monoid is a monoid with commutative `(*)`. -/
class comm_monoid (M : Type u) extends comm_semigroup M, monoid M where

/-- An additive commutative monoid is an additive monoid with commutative `(+)`. -/
class add_comm_monoid (M : Type u) extends add_comm_semigroup M, add_monoid M where

/-- An additive monoid in which addition is left-cancellative.
Main examples are `ℕ` and groups. This is the right typeclass for many sum lemmas, as having a zero
is useful to define the sum over the empty set, so `add_left_cancel_semigroup` is not enough. -/
-- TODO: I found 1 (one) lemma assuming `[add_left_cancel_monoid]`.

class add_left_cancel_monoid (M : Type u) extends add_left_cancel_semigroup M, add_monoid M where

-- Should we port more lemmas to this typeclass?

/-- A monoid in which multiplication is left-cancellative. -/
class left_cancel_monoid (M : Type u) extends left_cancel_semigroup M, monoid M where

/-- Commutative version of add_left_cancel_monoid. -/
class add_left_cancel_comm_monoid (M : Type u) extends add_left_cancel_monoid M, add_comm_monoid M
    where

/-- Commutative version of left_cancel_monoid. -/
class left_cancel_comm_monoid (M : Type u) extends left_cancel_monoid M, comm_monoid M where

/-- An additive monoid in which addition is right-cancellative.
Main examples are `ℕ` and groups. This is the right typeclass for many sum lemmas, as having a zero
is useful to define the sum over the empty set, so `add_right_cancel_semigroup` is not enough. -/
class add_right_cancel_monoid (M : Type u) extends add_monoid M, add_right_cancel_semigroup M where

/-- A monoid in which multiplication is right-cancellative. -/
class right_cancel_monoid (M : Type u) extends right_cancel_semigroup M, monoid M where

/-- Commutative version of add_right_cancel_monoid. -/
class add_right_cancel_comm_monoid (M : Type u) extends add_right_cancel_monoid M, add_comm_monoid M
    where

/-- Commutative version of right_cancel_monoid. -/
class right_cancel_comm_monoid (M : Type u) extends right_cancel_monoid M, comm_monoid M where

/-- An additive monoid in which addition is cancellative on both sides.
Main examples are `ℕ` and groups. This is the right typeclass for many sum lemmas, as having a zero
is useful to define the sum over the empty set, so `add_right_cancel_semigroup` is not enough. -/
class add_cancel_monoid (M : Type u) extends add_left_cancel_monoid M, add_right_cancel_monoid M
    where

/-- A monoid in which multiplication is cancellative. -/
class cancel_monoid (M : Type u) extends left_cancel_monoid M, right_cancel_monoid M where

/-- Commutative version of add_cancel_monoid. -/
class add_cancel_comm_monoid (M : Type u)
    extends add_left_cancel_comm_monoid M, add_right_cancel_comm_monoid M where

/-- Commutative version of cancel_monoid. -/
class cancel_comm_monoid (M : Type u) extends right_cancel_comm_monoid M, left_cancel_comm_monoid M
    where

/-- `try_refl_tac` solves goals of the form `∀ a b, f a b = g a b`,
if they hold by definition. -/
/-- A `div_inv_monoid` is a `monoid` with operations `/` and `⁻¹` satisfying
`div_eq_mul_inv : ∀ a b, a / b = a * b⁻¹`.

This is the immediate common ancestor of `group` and `group_with_zero`,
in order to deduplicate the name `div_eq_mul_inv`.
The default for `div` is such that `a / b = a * b⁻¹` holds by definition.

Adding `div` as a field rather than defining `a / b := a * b⁻¹` allows us to
avoid certain classes of unification failures, for example:
Let `foo X` be a type with a `∀ X, has_div (foo X)` instance but no
`∀ X, has_inv (foo X)`, e.g. when `foo X` is a `euclidean_domain`. Suppose we
also have an instance `∀ X [cromulent X], group_with_zero (foo X)`. Then the
`(/)` coming from `group_with_zero_has_div` cannot be definitionally equal to
the `(/)` coming from `foo.has_div`.
-/
class div_inv_monoid (G : Type u) extends Div G, monoid G, has_inv G where
  div_eq_mul_inv :
    autoParam (∀ (a b : G), a / b = a * (b⁻¹))
      (Lean.Syntax.ident Lean.SourceInfo.none (String.toSubstring "Mathlib.try_refl_tac")
        (Lean.Name.mkStr (Lean.Name.mkStr Lean.Name.anonymous "Mathlib") "try_refl_tac") [])

/-- A `sub_neg_monoid` is an `add_monoid` with unary `-` and binary `-` operations
satisfying `sub_eq_add_neg : ∀ a b, a - b = a + -b`.

The default for `sub` is such that `a - b = a + -b` holds by definition.

Adding `sub` as a field rather than defining `a - b := a + -b` allows us to
avoid certain classes of unification failures, for example:
Let `foo X` be a type with a `∀ X, has_sub (foo X)` instance but no
`∀ X, has_neg (foo X)`. Suppose we also have an instance
`∀ X [cromulent X], add_group (foo X)`. Then the `(-)` coming from
`add_group.has_sub` cannot be definitionally equal to the `(-)` coming from
`foo.has_sub`.
-/
class sub_neg_monoid (G : Type u) extends Sub G, Neg G, add_monoid G where
  sub_eq_add_neg :
    autoParam (∀ (a b : G), a - b = a + -b)
      (Lean.Syntax.ident Lean.SourceInfo.none (String.toSubstring "Mathlib.try_refl_tac")
        (Lean.Name.mkStr (Lean.Name.mkStr Lean.Name.anonymous "Mathlib") "try_refl_tac") [])

theorem sub_eq_add_neg {G : Type u} [sub_neg_monoid G] (a : G) (b : G) : a - b = a + -b :=
  sub_neg_monoid.sub_eq_add_neg

/-- A `group` is a `monoid` with an operation `⁻¹` satisfying `a⁻¹ * a = 1`.

There is also a division operation `/` such that `a / b = a * b⁻¹`,
with a default so that `a / b = a * b⁻¹` holds by definition.
-/
class group (G : Type u) extends div_inv_monoid G where
  mul_left_inv : ∀ (a : G), a⁻¹ * a = 1

/-- An `add_group` is an `add_monoid` with a unary `-` satisfying `-a + a = 0`.

There is also a binary operation `-` such that `a - b = a + -b`,
with a default so that `a - b = a + -b` holds by definition.
-/
class add_group (A : Type u) extends sub_neg_monoid A where
  add_left_neg : ∀ (a : A), -a + a = 0

/-- Abbreviation for `@div_inv_monoid.to_monoid _ (@group.to_div_inv_monoid _ _)`.

Useful because it corresponds to the fact that `Grp` is a subcategory of `Mon`.
Not an instance since it duplicates `@div_inv_monoid.to_monoid _ (@group.to_div_inv_monoid _ _)`.
-/
def group.to_monoid (G : Type u) [group G] : monoid G := div_inv_monoid.to_monoid G

@[simp] theorem mul_left_inv {G : Type u} [group G] (a : G) : a⁻¹ * a = 1 := group.mul_left_inv

theorem inv_mul_self {G : Type u} [group G] (a : G) : a⁻¹ * a = 1 := mul_left_inv a

@[simp] theorem neg_add_cancel_left {G : Type u} [add_group G] (a : G) (b : G) : -a + (a + b) = b :=
  eq.mpr (id (Eq._oldrec (Eq.refl (-a + (a + b) = b)) (Eq.symm (add_assoc (-a) a b))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (-a + a + b = b)) (add_left_neg a)))
      (eq.mpr (id (Eq._oldrec (Eq.refl (0 + b = b)) (zero_add b))) (Eq.refl b)))

@[simp] theorem inv_eq_of_mul_eq_one {G : Type u} [group G] {a : G} {b : G} (h : a * b = 1) :
    a⁻¹ = b :=
  left_inv_eq_right_inv (inv_mul_self a) h

@[simp] theorem inv_inv {G : Type u} [group G] (a : G) : a⁻¹⁻¹ = a :=
  inv_eq_of_mul_eq_one (mul_left_inv a)

@[simp] theorem add_right_neg {G : Type u} [add_group G] (a : G) : a + -a = 0 :=
  (fun (this : --a + -a = 0) => eq.mp (Eq._oldrec (Eq.refl ( --a + -a = 0)) (neg_neg a)) this)
    (add_left_neg (-a))

theorem add_neg_self {G : Type u} [add_group G] (a : G) : a + -a = 0 := add_right_neg a

@[simp] theorem mul_inv_cancel_right {G : Type u} [group G] (a : G) (b : G) : a * b * (b⁻¹) = a :=
  eq.mpr (id (Eq._oldrec (Eq.refl (a * b * (b⁻¹) = a)) (mul_assoc a b (b⁻¹))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (a * (b * (b⁻¹)) = a)) (mul_right_inv b)))
      (eq.mpr (id (Eq._oldrec (Eq.refl (a * 1 = a)) (mul_one a))) (Eq.refl a)))

protected instance add_group.to_cancel_add_monoid {G : Type u} [add_group G] :
    add_cancel_monoid G :=
  add_cancel_monoid.mk add_group.add add_group.add_assoc sorry add_group.zero add_group.zero_add
    add_group.add_zero sorry

/-- A commutative group is a group with commutative `(*)`. -/
/-- An additive commutative group is an additive group with commutative `(+)`. -/
class comm_group (G : Type u) extends group G, comm_monoid G where

class add_comm_group (G : Type u) extends add_group G, add_comm_monoid G where

protected instance comm_group.to_cancel_comm_monoid {G : Type u} [comm_group G] :
    cancel_comm_monoid G :=
  cancel_comm_monoid.mk comm_group.mul comm_group.mul_assoc sorry comm_group.one comm_group.one_mul
    comm_group.mul_one comm_group.mul_comm sorry

end Mathlib