-- Groups, a fundemental structure of algebra.
--
-- Lean has them built in of course, but I'm going to redefine them
-- myself for the hell of it in keeping with my "from first principles"
-- approach

-- These will be useful within this file
private variable {α : Type}
private variable {op : α → α → α}
private variable {id: α}
private variable {inv: α → α}
local infix `∘` := op
local postfix `⁻`:1025 := inv

-- The standard lean library uses lowercase names for all thse things, so to avoid confusion
-- let's use Camelcase

def Associative {α : Type} (f: α → α → α):= ∀ a b c : α, f (f a b) c = f a (f b c)
def Commutative {α : Type} (f: α → α → α):= ∀ a b : α, f a b = f b a
def LeftIdentity {α : Type} (f: α → α → α) (id: α) := ∀ a : α, f id a = a
def RightIdentity {α : Type} (f: α → α → α) (id: α) := ∀ a : α, f a id = a
def LeftInverse {α: Type} (f: α → α → α) (id: α) (inv: α → α) := ∀ a : α, f (inv a) a = id
def RightInverse {α: Type} (f: α → α → α) (id: α) (inv: α → α) := ∀ a : α, f a (inv a) = id

-- So we'll define our group as a class, so that it can be inferred when needed
-- I'll also create multiplicative and additive versions of the same, for convenience
--
-- We begin with the most basic of group-like objects, the semi-group
class Semigroup {α: Type} (op: α → α → α) := (assoc: (Associative op))

lemma Semigroup.op_assoc (s: Semigroup op) (a b c: α): (a∘b)∘c = a∘(b∘c) := Semigroup.assoc op a b c

-- And then the commutative semigroup
class CommSemigroup {α: Type} (op: α → α → α) extends Semigroup op := (comm: Commutative op)

def CommSemigroup.op_assoc (csg: CommSemigroup op) [s: Semigroup op] := s.op_assoc
lemma CommSemigroup.op_comm (csg: CommSemigroup op) (a b : α): a ∘ b = b ∘ a := CommSemigroup.comm op a b

-- Right so from a Semigroup we can form a Monoid
class Monoid {α: Type} (op: α → α → α) (id: α) extends Semigroup op := (left_id: LeftIdentity op id) (right_id: RightIdentity op id)

namespace Monoid
def op_assoc (m: Monoid op id) [s: Semigroup op] := s.op_assoc
lemma op_id (m:Monoid op id) (a: α): a∘id = a := Monoid.right_id op id a
lemma id_op (m: Monoid op id) (a: α): id∘a = a := Monoid.left_id op id a

lemma id_unique (m: Monoid op id) {e: α}: (∀ a:α, e∘a = a) → e = id :=
assume hl,
calc
    e   = e∘id  : by rw op_id m e
    ... = id    : by rw hl id

end Monoid

-- And a Commutative Monoid
class CommMonoid {α: Type} (op: α → α → α) (id: α) extends CommSemigroup op := (left_id: LeftIdentity op id)
instance CommMonoidIsMonoid {α: Type} (op: α → α → α) (id: α) [cm: CommMonoid op id]: Monoid op id := {assoc:=cm.assoc, left_id:=cm.left_id, right_id:=assume a, by rw [cm.comm, cm.left_id]}

def CommMonoid.op_assoc (m: CommMonoid op id) [m: Monoid op id] := m.op_assoc
def CommMonoid.op_comm (ag: CommMonoid op id) [csg: CommSemigroup op] := csg.op_comm
def CommMonoid.op_id (m: CommMonoid op id) [m: Monoid op id] := m.op_id
def CommMonoid.id_op (m: CommMonoid op id) [m: Monoid op id] := m.id_op

-- And from a monoid we can go to a full Group!
class Group {α: Type} (op: α → α → α) (id: α) (inv: α → α) extends Monoid op id := (left_inv: LeftInverse op id inv) (right_inv: RightInverse op id inv)

namespace Group
def op_assoc (g: Group op id inv) [s: Semigroup op] := s.op_assoc
def op_id (g: Group op id inv) [m: Monoid op id] := m.op_id
def id_op (g: Group op id inv) [m: Monoid op id] := m.id_op

lemma op_inv (g: Group op id inv) (a : α): a∘a⁻ = id := Group.right_inv op id inv a

lemma inv_op (g: Group op id inv) (a : α): a⁻∘a = id := Group.left_inv op id inv a

private lemma inv_op_elm_op (g: Group op id inv) (a b: α): a = b⁻ ∘ (b ∘ a) :=
calc
    a   = op id a       : by rw g.id_op
    ... = (b⁻∘b) ∘ a    : by rw g.inv_op
    ... = b⁻ ∘ (b ∘ a)  : by rw g.op_assoc
lemma cancel_left (g: Group op id inv) {a b c: α}: a∘b = a∘c ↔ b = c :=
iff.intro (
    assume h: a∘b = a∘c,
    by rw [inv_op_elm_op g b, h, ←inv_op_elm_op g c]
) (
    assume h: b = c,
    by rw h
)

private lemma op_elm_op_inv (g: Group op id inv) (a b: α): a = (a ∘ b) ∘ b⁻ :=
calc
    a   = a ∘ id        : by rw g.op_id
    ... = a ∘ (b ∘ b⁻)  : by rw g.op_inv
    ... = (a ∘ b) ∘ b⁻  : by rw g.op_assoc
lemma cancel_right (g: Group op id inv) {a b c: α}: b∘a = c∘a ↔ b = c :=
iff.intro (
    assume h: b∘a = c∘a,
    by rw [op_elm_op_inv g b, h, ←op_elm_op_inv g c]
) (
    assume h: b = c,
    by rw h
)

lemma id_unique (g: Group op id inv) [m: Monoid op id] {e: α}: (∀ a:α, e∘a = a) → e = id := m.id_unique

lemma inv_unique (g: Group op id inv) {a b: α}: b ∘ a = id → b = a⁻ :=
assume h: b ∘ a = id,
suffices b ∘ a = a⁻ ∘ a, from iff.elim_left (cancel_right g) this,
by rw [h, inv_op g]

lemma inv_inv (g: Group op id inv) (a : α): (a⁻)⁻ = a :=
calc
    (a⁻)⁻ = id ∘ (a⁻)⁻        : by rw g.id_op
    ...   = (a ∘ a⁻) ∘ (a⁻)⁻  : by rw g.op_inv
    ...   = a ∘ (a⁻ ∘ (a⁻)⁻)  : by rw g.op_assoc
    ...   = a ∘ id            : by rw g.op_inv
    ...   = a                 : by rw g.op_id

end Group

-- And now an abelian group
class AbelianGroup {α: Type} (op: α → α → α) (id: α) (inv: α → α) extends CommMonoid op id := (left_inv: LeftInverse op id inv)
def AbelianGroup.to_Group {α: Type} {op: α → α → α} {id: α} {inv: α → α} (ag: AbelianGroup op id inv): Group op id inv := {assoc:=ag.assoc, left_id:=ag.left_id, right_id:=assume a, by rw [ag.comm, ag.left_id], left_inv:=ag.left_inv, right_inv:=assume a, by rw [ag.comm, ag.left_inv]}
instance AbelianGroupIsGroup {α: Type} {op: α → α → α} {id: α} {inv: α → α} [ag: AbelianGroup op id inv]: Group op id inv := ag.to_Group

def AbelianGroup.op_assoc (ag: AbelianGroup op id inv) [s: Semigroup op] := s.op_assoc
def AbelianGroup.op_comm (ag: AbelianGroup op id inv) [cm: CommMonoid op id] := cm.op_comm
def AbelianGroup.op_id (ag: AbelianGroup op id inv) [m: Monoid op id] := m.op_id
def AbelianGroup.id_op (ag: AbelianGroup op id inv) [m: Monoid op id] := m.id_op
def AbelianGroup.op_inv (ag: AbelianGroup op id inv) [g: Group op id inv] := g.op_inv
def AbelianGroup.inv_op (ag: AbelianGroup op id inv) [g: Group op id inv] := g.inv_op
lemma AbelianGroup.cancel_left (ag: AbelianGroup op id inv) [g: Group op id inv] {a b c: α}: a∘b = a∘c ↔ b = c := g.cancel_left
lemma AbelianGroup.cancel_right (ag: AbelianGroup op id inv) [g: Group op id inv] {a b c: α}: b∘a = c∘a ↔ b = c := g.cancel_right

-- Some special versions for additive work
class AdditiveSemigroup (α: Type) extends has_add α := (add_assoc_: (Associative add))
instance AdditiveSemigroupIsSemigroup (α: Type) [asg: AdditiveSemigroup α]: Semigroup asg.add := {assoc := asg.add_assoc_}
lemma AdditiveSemigroup.add_assoc {α : Type} (asg: AdditiveSemigroup α) [sg: Semigroup asg.add] (a b c: α): (a+b)+c = a+(b+c) := sg.op_assoc a b c
class AdditiveMonoid (α: Type) extends has_zero α, has_add α := (assoc: Associative add) (left_id: LeftIdentity add 0) (right_id: RightIdentity add 0)
instance AdditiveMonoidIsMonoid (α: Type) [am: AdditiveMonoid α]: Monoid am.add am.zero := {assoc := am.assoc, left_id := am.left_id , right_id := am.right_id}
lemma AdditiveMonoid.add_zero {α: Type} (am: AdditiveMonoid α) [m: Monoid am.add am.zero] (a: α): a + 0 = a := m.op_id a
lemma AdditiveMonoid.zero_add {α: Type} (am: AdditiveMonoid α) [m: Monoid am.add am.zero] (a: α): 0 + a = a := m.id_op a
class AdditiveGroup (α: Type) extends AdditiveMonoid α, has_neg α := (left_inv: ∀ a : α, (-a) + a = 0) (right_inv: ∀ a : α, a  + (-a) = 0)
instance AdditiveGroupIsGroup (α: Type) [ag: AdditiveGroup α]: Group ag.add ag.zero ag.neg := {assoc:=ag.assoc, left_id:=ag.left_id, right_id:=ag.right_id, left_inv:=ag.left_inv, right_inv:=ag.right_inv}
lemma AdditiveGroup.add_neg {α: Type} (ag: AdditiveGroup α) [g: Group ag.add ag.zero ag.neg] (a: α): a + -a = 0 := g.op_inv a
lemma AdditiveGroup.neg_add {α: Type} (ag: AdditiveGroup α) [g: Group ag.add ag.zero ag.neg] (a: α): -a + a = 0 := g.inv_op a
lemma AdditiveGroup.cancel_left {α: Type} (ag: AdditiveGroup α) [g: Group ag.add ag.zero ag.neg] {a b c: α}: a + b = a + c ↔ b = c := g.cancel_left
lemma AdditiveGroup.cancel_right {α: Type} (ag: AdditiveGroup α) [g: Group ag.add ag.zero ag.neg] {a b c: α}: b + a = c + a ↔ b = c := g.cancel_right
class AdditiveAbelianGroup (α: Type) extends AdditiveGroup α := (comm: Commutative add)
def AdditiveAbelianGroup.to_AbelianGroup {α: Type} (aag: AdditiveAbelianGroup α): AbelianGroup aag.add aag.zero aag.neg := {assoc:=aag.assoc, left_id:=aag.left_id, left_inv:=aag.left_inv, comm:=aag.comm}
instance AdditiveAbelianGroupIsAbelianGroup {α: Type} [aag: AdditiveAbelianGroup α]: AbelianGroup aag.add aag.zero aag.neg := aag.to_AbelianGroup
def AdditiveAbelianGroup.to_Group {α: Type} (aag: AdditiveAbelianGroup α): Group aag.add aag.zero aag.neg := aag.to_AbelianGroup.to_Group
lemma AdditiveAbelianGroup.add_comm (aag: AdditiveAbelianGroup α) [ag: AbelianGroup aag.add aag.zero aag.neg] (a b : α): a + b = b + a := ag.op_comm a b

-- Some specialised versions for multiplicative work
class MultiplicativeSemigroup (α: Type) extends has_mul α := (mul_assoc_: (Associative mul))
instance MultiplicativeSemigroupIsSemigroup (α: Type) [msg: MultiplicativeSemigroup α]: Semigroup msg.mul := {assoc := msg.mul_assoc_}
lemma MultiplicativeSemigroup.mul_assoc {α : Type} (msg: MultiplicativeSemigroup α) [sg: Semigroup msg.mul] (a b c: α): (a*b)*c = a*(b*c) := sg.op_assoc a b c
class MultiplicativeMonoid (α: Type) extends has_one α, has_mul α := (assoc: Associative mul) (left_id: ∀ a : α, 1*a = a) (right_id: ∀ a : α, a*1 = a)
instance MultiplicativeMonoidIsMonoid (α: Type) [mm: MultiplicativeMonoid α]: Monoid mm.mul mm.one := {assoc := mm.assoc, left_id := mm.left_id, right_id := mm.right_id}
lemma MultiplicativeMonoid.mul_one {α: Type} (mm: MultiplicativeMonoid α) [m: Monoid mm.mul mm.one] (a: α): a*1 = a := m.op_id a
lemma MultiplicativeMonoid.one_mul {α: Type} (mm: MultiplicativeMonoid α) [m: Monoid mm.mul mm.one] (a: α): 1*a = a := m.id_op a
class MultiplicativeGroup (α: Type) extends MultiplicativeMonoid α, has_inv α := (left_inv: ∀ a : α, a⁻¹*a = 1) (right_inv: ∀ a : α, a*a⁻¹ = 1)
instance MultiplicativeGroupIsGroup (α: Type) [mg: MultiplicativeGroup α]: Group mg.mul mg.one mg.inv := {assoc:=mg.assoc, left_id:=mg.left_id, right_id:=mg.right_id, left_inv:=mg.left_inv, right_inv:=mg.right_inv}
lemma MultiplicativeGroup.mul_inv {α: Type} (mg: MultiplicativeGroup α) [g: Group mg.mul mg.one mg.inv] (a: α): a*a⁻¹ = 1 := g.op_inv a
lemma MultiplicativeGroup.inv_mul {α: Type} (mg: MultiplicativeGroup α) [g: Group mg.mul mg.one mg.inv] (a: α): a⁻¹*a = 1 := g.inv_op a
lemma MultiplicativeGroup.cancel_left {α: Type} (mg: MultiplicativeGroup α) [g: Group mg.mul mg.one mg.inv] {a b c: α}: a*b = a*c ↔ b = c := g.cancel_left
lemma MultiplicativeGroup.cancel_right {α: Type} (mg: MultiplicativeGroup α) [g: Group mg.mul mg.one mg.inv] {a b c: α}: b*a = c*a ↔ b = c := g.cancel_right
class MultiplicativeAbelianGroup (α: Type) extends MultiplicativeGroup α := (comm: Commutative mul)
instance MultiplicativeAbelianGroupIsAbelianGroup (α: Type) [mag: MultiplicativeAbelianGroup α]: AbelianGroup mag.mul mag.one mag.inv := {assoc:=mag.assoc, left_id:=mag.left_id, left_inv:=mag.left_inv, comm:=mag.comm}
lemma MultiplicativeAbelianGroup.mul_comm (mag: MultiplicativeAbelianGroup α) [ag: AbelianGroup mag.mul mag.one mag.inv] (a b : α): a * b = b * a := ag.op_comm a b


/-
Copyright (c) 2021 Manuel Candales. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Manuel Candales
-/
import data.real.basic
import data.real.sqrt
import data.nat.prime
import number_theory.primes_congruent_one
import number_theory.quadratic_reciprocity
import tactic.linear_combination

/-!
# IMO 2008 Q3
Prove that there exist infinitely many positive integers `n` such that `n^2 + 1` has a prime
divisor which is greater than `2n + √(2n)`.

# Solution
We first prove the following lemma: for every prime `p > 20`, satisfying `p ≡ 1 [MOD 4]`,
there exists `n ∈ ℕ` such that `p ∣ n^2 + 1` and `p > 2n + √(2n)`. Then the statement of the
problem follows from the fact that there exist infinitely many primes `p ≡ 1 [MOD 4]`.

To prove the lemma, notice that `p ≡ 1 [MOD 4]` implies `∃ n ∈ ℕ` such that `n^2 ≡ -1 [MOD p]`
and we can take this `n` such that `n ≤ p/2`. Let `k = p - 2n ≥ 0`. Then we have:
`k^2 + 4 = (p - 2n)^2 + 4 ≣ 4n^2 + 4 ≡ 0 [MOD p]`. Then `k^2 + 4 ≥ p` and so `k ≥ √(p - 4) > 4`.
Then `p = 2n + k ≥ 2n + √(p - 4) = 2n + √(2n + k - 4) > √(2n)` and we are done.
-/

open real

lemma p_lemma (p : ℕ) (hpp : nat.prime p) (hp_mod_4_eq_1 : p ≡ 1 [MOD 4]) (hp_gt_20 : p > 20) :
  ∃ n : ℕ, p ∣ n ^ 2 + 1 ∧ (p : ℝ) > 2 * n + sqrt(2 * n) :=
begin
  haveI := fact.mk hpp,
  have hp_mod_4_ne_3 : p % 4 ≠ 3, { linarith [(show p % 4 = 1, by exact hp_mod_4_eq_1)] },
  obtain ⟨y, hy⟩ := (zmod.exists_sq_eq_neg_one_iff_mod_four_ne_three p).mpr hp_mod_4_ne_3,

  let m := zmod.val_min_abs y,
  let n := int.nat_abs m,

  have hnat₁ : p ∣ n ^ 2 + 1,
  { refine int.coe_nat_dvd.mp _,
    simp only [int.nat_abs_sq, int.coe_nat_pow, int.coe_nat_succ, int.coe_nat_dvd.mp],
    refine (zmod.int_coe_zmod_eq_zero_iff_dvd (m ^ 2 + 1) p).mp _,
    simp only [int.cast_pow, int.cast_add, int.cast_one, zmod.coe_val_min_abs],
    rw hy, exact add_left_neg 1 },

  have hnat₂ : n ≤ p / 2 := zmod.nat_abs_val_min_abs_le y,
  have hnat₃ : p ≥ 2 * n, { linarith [nat.div_mul_le_self p 2] },

  set k : ℕ := p - 2 * n with hnat₄,

  have hnat₅ : p ∣ k ^ 2 + 4,
  { cases hnat₁ with x hx,
    have : (p:ℤ) ∣ k ^ 2 + 4,
    { use (p:ℤ) - 4 * n + 4 * x,
      have hcast₁ : (k:ℤ) = p - 2 * n, { assumption_mod_cast },
      have hcast₂ : (n:ℤ) ^ 2 + 1 = p * x, { assumption_mod_cast },
      linear_combination (hcast₁, (k:ℤ) + p - 2 * n) (hcast₂, 4) },
    assumption_mod_cast },

  have hnat₆ : k ^ 2 + 4 ≥ p := nat.le_of_dvd (k ^ 2 + 3).succ_pos hnat₅,

  have hreal₁ : (k:ℝ) = p - 2 * n, { assumption_mod_cast },
  have hreal₂ : (p:ℝ) > 20,        { assumption_mod_cast },
  have hreal₃ : (k:ℝ) ^ 2 + 4 ≥ p, { assumption_mod_cast },

  have hreal₅ : (k:ℝ) > 4,
  { apply lt_of_pow_lt_pow 2 k.cast_nonneg,
    linarith only [hreal₂, hreal₃] },

  have hreal₆ : (k:ℝ) > sqrt (2 * n),
  { apply lt_of_pow_lt_pow 2 k.cast_nonneg,
    rw sq_sqrt (mul_nonneg zero_le_two n.cast_nonneg),
    linarith only [hreal₁, hreal₃, hreal₅] },

  exact ⟨n, hnat₁, by linarith only [hreal₆, hreal₁]⟩,
end

theorem imo2008_q3 : ∀ N : ℕ, ∃ n : ℕ, n ≥ N ∧
  ∃ p : ℕ, nat.prime p ∧ p ∣ n ^ 2 + 1 ∧ (p : ℝ) > 2 * n + sqrt(2 * n) :=
begin
  intro N,
  obtain ⟨p, hpp, hineq₁, hpmod4⟩ := nat.exists_prime_ge_modeq_one 4 (N ^ 2 + 21) zero_lt_four,
  obtain ⟨n, hnat, hreal⟩ := p_lemma p hpp hpmod4 (by linarith [hineq₁, nat.zero_le (N ^ 2)]),

  have hineq₂  : n ^ 2 + 1 ≥ p := nat.le_of_dvd (n ^ 2).succ_pos hnat,
  have hineq₃  : n * n ≥ N * N, { linarith [hineq₁, hineq₂] },
  have hn_ge_N : n ≥ N := nat.mul_self_le_mul_self_iff.mpr hineq₃,

  exact ⟨n, hn_ge_N, p, hpp, hnat, hreal⟩,
end


import data.finset algebra.big_operators tactic.ring

namespace maths_challenges

-- the n'th odd number (we start counting at zero)
def odd (n : ℕ) := 2 * n + 1

-- finset.range n is the finite set {0,1,2,...,n-1}
theorem challenge05 (n : ℕ) : (finset.range n).sum (odd) = n ^ 2 :=
begin
  induction n with d hd,
    refl,
  rw finset.sum_range_succ,
  rw hd,
  change (2 * d + 1) + d ^ 2 = (d + 1) ^ 2,
  ring,
end

end maths_challenges


import .group_representation
import .morphism
import .sub_module
universe variables u v w w' w'' w'''

variables {G : Type u} [group G] {R : Type v}[ring R]
variables {M1 : Type w}  [add_comm_group M1] [module R M1]
          {M2 : Type w'} [add_comm_group M2] [module R M2]
          {ρ1 : group_representation G R M1}
          {ρ2  : group_representation G R M2}
          (f  : ρ1 ⟶  ρ2 )

open stability morphism
namespace Kernel
/--
   The kernel of `f : ρ1 ⟶ ρ2` is define to be the kernel of `↑f : M1 →ₗ[R] M2`
-/
def Ker := linear_map.ker (f.ℓ)   /-- bof -/
lemma Ker_ext_iff(x : M1) : x ∈ Ker f ↔ x ∈ linear_map.ker (f.ℓ)   := iff.rfl    --- brouh  not good !
lemma mem_ker (x : M1) : x ∈ Ker f ↔ f x = 0 := linear_map.mem_ker
lemma Ker_ext  (x : M1 )  : f  x = 0  →  x ∈  Ker f :=
begin
    intros, rw Ker_ext_iff,rw linear_map.mem_ker,assumption,
end
lemma Ker_f_mem (x : M1) : (x ∈  Ker f) →  f x  =0 := begin
    rw ← mem_ker,intros, assumption,
end
/--
   The kernel of `f : ρ1 ⟶ ρ2` is an stable sub-stape of M1.
-/
theorem ker_is_stable_submodule : stable_submodule ρ1 (Ker f) := begin
    intros g,intros x,apply Ker_ext, rw morphism.commute_apply,
    rw Ker_f_mem,rw (ρ2 g).map_zero,
    rcases x,unfold Ker at ⊢ x_property , assumption,
end
/--
    The Kernel of `f : ρ1 ⟶ ρ2` has representation.
-/
def ker : group_representation G R (Ker f) := Res (ker_is_stable_submodule f)

end Kernel
namespace range
open linear_map
/--
    Range is stable. Let `y ∈ Im f`, let `g ∈ G`, we want : `ρ g y ∈ Im f`.
    Take `x ∈ M1` s.t `f x = y`.  We have (from `f.commute`) :
            `(f ∘ ρ1 g) x = ρ2 g ∘ f x`
    i.e `ρ2 g y = f ( ρ1 g x)` and so  `ρ2 g y ∈ Im f`
-/

theorem range_is_stable_submodule : stable_submodule ρ2 (range (f.ℓ  : M1→ₗ[R] M2)) := begin
    intros g,intros y,rcases y with ⟨y, ⟨x,hyp⟩⟩,
    apply linear_map.mem_range.mpr,
        use ρ1 g x,
    erw commute_apply,
    exact congr_arg ⇑(ρ2 g) hyp.right,
end
/--
  For a morphism `f : ρ1 ⟶ ρ2` between `representation` we define a sub representation of `M2`
-/
def Range  : group_representation G R (range (f.ℓ  : M1→ₗ[R] M2)) := Res (range_is_stable_submodule f)

end range


/-
Copyright (c) 2014 Floris van Doorn. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.

Authors: Floris van Doorn, Jeremy Avigad
Distance function on the natural numbers.
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.data.nat.basic
import Mathlib.PostPort

namespace Mathlib

namespace nat

/- distance -/

/-- Distance (absolute value of difference) between natural numbers. -/
def dist (n : ℕ) (m : ℕ) : ℕ := n - m + (m - n)

theorem dist.def (n : ℕ) (m : ℕ) : dist n m = n - m + (m - n) := rfl

theorem dist_comm (n : ℕ) (m : ℕ) : dist n m = dist m n := sorry

@[simp] theorem dist_self (n : ℕ) : dist n n = 0 := sorry

theorem eq_of_dist_eq_zero {n : ℕ} {m : ℕ} (h : dist n m = 0) : n = m := sorry

theorem dist_eq_zero {n : ℕ} {m : ℕ} (h : n = m) : dist n m = 0 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (dist n m = 0)) h))
    (eq.mpr (id (Eq._oldrec (Eq.refl (dist m m = 0)) (dist_self m))) (Eq.refl 0))

theorem dist_eq_sub_of_le {n : ℕ} {m : ℕ} (h : n ≤ m) : dist n m = m - n :=
  eq.mpr (id (Eq._oldrec (Eq.refl (dist n m = m - n)) (dist.def n m)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (n - m + (m - n) = m - n)) (sub_eq_zero_of_le h)))
      (eq.mpr (id (Eq._oldrec (Eq.refl (0 + (m - n) = m - n)) (zero_add (m - n))))
        (Eq.refl (m - n))))

theorem dist_eq_sub_of_le_right {n : ℕ} {m : ℕ} (h : m ≤ n) : dist n m = n - m :=
  eq.mpr (id (Eq._oldrec (Eq.refl (dist n m = n - m)) (dist_comm n m))) (dist_eq_sub_of_le h)

theorem dist_tri_left (n : ℕ) (m : ℕ) : m ≤ dist n m + n :=
  le_trans (nat.le_sub_add m n) (add_le_add_right (le_add_left (m - n) (n - m)) n)

theorem dist_tri_right (n : ℕ) (m : ℕ) : m ≤ n + dist n m :=
  eq.mpr (id (Eq._oldrec (Eq.refl (m ≤ n + dist n m)) (add_comm n (dist n m)))) (dist_tri_left n m)

theorem dist_tri_left' (n : ℕ) (m : ℕ) : n ≤ dist n m + m :=
  eq.mpr (id (Eq._oldrec (Eq.refl (n ≤ dist n m + m)) (dist_comm n m))) (dist_tri_left m n)

theorem dist_tri_right' (n : ℕ) (m : ℕ) : n ≤ m + dist n m :=
  eq.mpr (id (Eq._oldrec (Eq.refl (n ≤ m + dist n m)) (dist_comm n m))) (dist_tri_right m n)

theorem dist_zero_right (n : ℕ) : dist n 0 = n :=
  Eq.trans (dist_eq_sub_of_le_right (zero_le n)) (nat.sub_zero n)

theorem dist_zero_left (n : ℕ) : dist 0 n = n :=
  Eq.trans (dist_eq_sub_of_le (zero_le n)) (nat.sub_zero n)

theorem dist_add_add_right (n : ℕ) (k : ℕ) (m : ℕ) : dist (n + k) (m + k) = dist n m := sorry

theorem dist_add_add_left (k : ℕ) (n : ℕ) (m : ℕ) : dist (k + n) (k + m) = dist n m :=
  eq.mpr (id (Eq._oldrec (Eq.refl (dist (k + n) (k + m) = dist n m)) (add_comm k n)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (dist (n + k) (k + m) = dist n m)) (add_comm k m)))
      (dist_add_add_right n k m))

theorem dist_eq_intro {n : ℕ} {m : ℕ} {k : ℕ} {l : ℕ} (h : n + m = k + l) : dist n k = dist l m :=
  sorry

protected theorem sub_lt_sub_add_sub (n : ℕ) (m : ℕ) (k : ℕ) : n - k ≤ n - m + (m - k) := sorry

theorem dist.triangle_inequality (n : ℕ) (m : ℕ) (k : ℕ) : dist n k ≤ dist n m + dist m k := sorry

theorem dist_mul_right (n : ℕ) (k : ℕ) (m : ℕ) : dist (n * k) (m * k) = dist n m * k := sorry

theorem dist_mul_left (k : ℕ) (n : ℕ) (m : ℕ) : dist (k * n) (k * m) = k * dist n m := sorry

-- TODO(Jeremy): do when we have max and minx

--theorem dist_eq_max_sub_min {i j : nat} : dist i j = (max i j) - min i j :=

--sorry

/-
or.elim (lt_or_ge i j)
  (assume : i < j,
    by rw [max_eq_right_of_lt this, min_eq_left_of_lt this, dist_eq_sub_of_lt this])
  (assume : i ≥ j,
    by rw [max_eq_left this , min_eq_right this, dist_eq_sub_of_le_right this])
-/

theorem dist_succ_succ {i : ℕ} {j : ℕ} : dist (Nat.succ i) (Nat.succ j) = dist i j := sorry

theorem dist_pos_of_ne {i : ℕ} {j : ℕ} : i ≠ j → 0 < dist i j := sorry

end Mathlib


/-
Copyright (c) 2017 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro
-/
import data.option.basic
import data.nat.basic
/-!
# Partial predecessor and partial subtraction on the natural numbers

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

The usual definition of natural number subtraction (`nat.sub`) returns 0 as a "garbage value" for
`a - b` when `a < b`. Similarly, `nat.pred 0` is defined to be `0`. The functions in this file
wrap the result in an `option` type instead:

## Main definitions

- `nat.ppred`: a partial predecessor operation
- `nat.psub`: a partial subtraction operation

-/
namespace nat

/-- Partial predecessor operation. Returns `ppred n = some m`
  if `n = m + 1`, otherwise `none`. -/
@[simp] def ppred : ℕ → option ℕ
| 0     := none
| (n+1) := some n

/-- Partial subtraction operation. Returns `psub m n = some k`
  if `m = n + k`, otherwise `none`. -/
@[simp] def psub (m : ℕ) : ℕ → option ℕ
| 0     := some m
| (n+1) := psub n >>= ppred

theorem pred_eq_ppred (n : ℕ) : pred n = (ppred n).get_or_else 0 :=
by cases n; refl

theorem sub_eq_psub (m : ℕ) : ∀ n, m - n = (psub m n).get_or_else 0
| 0     := rfl
| (n+1) := (pred_eq_ppred (m-n)).trans $
  by rw [sub_eq_psub, psub]; cases psub m n; refl

@[simp] theorem ppred_eq_some {m : ℕ} : ∀ {n}, ppred n = some m ↔ succ m = n
| 0     := by split; intro h; contradiction
| (n+1) := by dsimp; split; intro h; injection h; subst n

@[simp] theorem ppred_eq_none : ∀ {n : ℕ}, ppred n = none ↔ n = 0
| 0     := by simp
| (n+1) := by dsimp; split; contradiction

theorem psub_eq_some {m : ℕ} : ∀ {n k}, psub m n = some k ↔ k + n = m
| 0     k := by simp [eq_comm]
| (n+1) k :=
  begin
    dsimp,
    apply option.bind_eq_some.trans,
    simp [psub_eq_some, add_comm, add_left_comm, nat.succ_eq_add_one]
  end

theorem psub_eq_none {m n : ℕ} : psub m n = none ↔ m < n :=
begin
  cases s : psub m n; simp [eq_comm],
  { show m < n, refine lt_of_not_ge (λ h, _),
    cases le.dest h with k e,
    injection s.symm.trans (psub_eq_some.2 $ (add_comm _ _).trans e) },
  { show n ≤ m, rw ← psub_eq_some.1 s, apply nat.le_add_left }
end

theorem ppred_eq_pred {n} (h : 0 < n) : ppred n = some (pred n) :=
ppred_eq_some.2 $ succ_pred_eq_of_pos h

theorem psub_eq_sub {m n} (h : n ≤ m) : psub m n = some (m - n) :=
psub_eq_some.2 $ nat.sub_add_cancel h

theorem psub_add (m n k) : psub m (n + k) = do x ← psub m n, psub x k :=
by induction k; simp [*, add_succ, bind_assoc]

/-- Same as `psub`, but with a more efficient implementation. -/
@[inline] def psub' (m n : ℕ) : option ℕ := if n ≤ m then some (m - n) else none

theorem psub'_eq_psub (m n) : psub' m n = psub m n :=
by rw [psub']; split_ifs;
  [exact (psub_eq_sub h).symm, exact (psub_eq_none.2 (not_le.1 h)).symm]

end nat


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

! This file was ported from Lean 3 source module group_theory.specific_groups.dihedral
! leanprover-community/mathlib commit 70fd9563a21e7b963887c9360bd29b2393e6225a
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Data.Zmod.Basic
import Mathbin.GroupTheory.Exponent

/-!
# Dihedral Groups

We define the dihedral groups `dihedral_group n`, with elements `r i` and `sr i` for `i : zmod n`.

For `n ≠ 0`, `dihedral_group n` represents the symmetry group of the regular `n`-gon. `r i`
represents the rotations of the `n`-gon by `2πi/n`, and `sr i` represents the reflections of the
`n`-gon. `dihedral_group 0` corresponds to the infinite dihedral group.
-/

/-- For `n ≠ 0`, `dihedral_group n` represents the symmetry group of the regular `n`-gon.
`r i` represents the rotations of the `n`-gon by `2πi/n`, and `sr i` represents the reflections of
the `n`-gon. `dihedral_group 0` corresponds to the infinite dihedral group.
-/
inductive DihedralGroup (n : ℕ) : Type
  | r : ZMod n → DihedralGroup
  | sr : ZMod n → DihedralGroup
  deriving DecidableEq
#align dihedral_group DihedralGroup

namespace DihedralGroup

variable {n : ℕ}

/-- Multiplication of the dihedral group.
-/
private def mul : DihedralGroup n → DihedralGroup n → DihedralGroup n
  | r i, r j => r (i + j)
  | r i, sr j => sr (j - i)
  | sr i, r j => sr (i + j)
  | sr i, sr j => r (j - i)
#align dihedral_group.mul dihedral_group.mul

/-- The identity `1` is the rotation by `0`.
-/
private def one : DihedralGroup n :=
  r 0
#align dihedral_group.one dihedral_group.one

instance : Inhabited (DihedralGroup n) :=
  ⟨one⟩

/-- The inverse of a an element of the dihedral group.
-/
private def inv : DihedralGroup n → DihedralGroup n
  | r i => r (-i)
  | sr i => sr i
#align dihedral_group.inv dihedral_group.inv

/-- The group structure on `dihedral_group n`.
-/
instance : Group (DihedralGroup n) where
  mul := mul
  mul_assoc := by rintro (a | a) (b | b) (c | c) <;> simp only [mul] <;> ring
  one := one
  one_mul := by
    rintro (a | a)
    exact congr_arg r (zero_add a)
    exact congr_arg sr (sub_zero a)
  mul_one := by
    rintro (a | a)
    exact congr_arg r (add_zero a)
    exact congr_arg sr (add_zero a)
  inv := inv
  mul_left_inv := by
    rintro (a | a)
    exact congr_arg r (neg_add_self a)
    exact congr_arg r (sub_self a)

@[simp]
theorem r_mul_r (i j : ZMod n) : r i * r j = r (i + j) :=
  rfl
#align dihedral_group.r_mul_r DihedralGroup.r_mul_r

@[simp]
theorem r_mul_sr (i j : ZMod n) : r i * sr j = sr (j - i) :=
  rfl
#align dihedral_group.r_mul_sr DihedralGroup.r_mul_sr

@[simp]
theorem sr_mul_r (i j : ZMod n) : sr i * r j = sr (i + j) :=
  rfl
#align dihedral_group.sr_mul_r DihedralGroup.sr_mul_r

@[simp]
theorem sr_mul_sr (i j : ZMod n) : sr i * sr j = r (j - i) :=
  rfl
#align dihedral_group.sr_mul_sr DihedralGroup.sr_mul_sr

theorem one_def : (1 : DihedralGroup n) = r 0 :=
  rfl
#align dihedral_group.one_def DihedralGroup.one_def

private def fintype_helper : Sum (ZMod n) (ZMod n) ≃ DihedralGroup n
    where
  invFun i :=
    match i with
    | r j => Sum.inl j
    | sr j => Sum.inr j
  toFun i :=
    match i with
    | Sum.inl j => r j
    | Sum.inr j => sr j
  left_inv := by rintro (x | x) <;> rfl
  right_inv := by rintro (x | x) <;> rfl
#align dihedral_group.fintype_helper dihedral_group.fintype_helper

/-- If `0 < n`, then `dihedral_group n` is a finite group.
-/
instance [NeZero n] : Fintype (DihedralGroup n) :=
  Fintype.ofEquiv _ fintypeHelper

instance : Nontrivial (DihedralGroup n) :=
  ⟨⟨r 0, sr 0, by decide⟩⟩

/-- If `0 < n`, then `dihedral_group n` has `2n` elements.
-/
theorem card [NeZero n] : Fintype.card (DihedralGroup n) = 2 * n := by
  rw [← fintype.card_eq.mpr ⟨fintype_helper⟩, Fintype.card_sum, ZMod.card, two_mul]
#align dihedral_group.card DihedralGroup.card

@[simp]
theorem r_one_pow (k : ℕ) : (r 1 : DihedralGroup n) ^ k = r k :=
  by
  induction' k with k IH
  · rw [Nat.cast_zero]
    rfl
  · rw [pow_succ, IH, r_mul_r]
    congr 1
    norm_cast
    rw [Nat.one_add]
#align dihedral_group.r_one_pow DihedralGroup.r_one_pow

@[simp]
theorem r_one_pow_n : r (1 : ZMod n) ^ n = 1 :=
  by
  rw [r_one_pow, one_def]
  congr 1
  exact ZMod.nat_cast_self _
#align dihedral_group.r_one_pow_n DihedralGroup.r_one_pow_n

@[simp]
theorem sr_mul_self (i : ZMod n) : sr i * sr i = 1 := by rw [sr_mul_sr, sub_self, one_def]
#align dihedral_group.sr_mul_self DihedralGroup.sr_mul_self

/-- If `0 < n`, then `sr i` has order 2.
-/
@[simp]
theorem orderOf_sr (i : ZMod n) : orderOf (sr i) = 2 :=
  by
  rw [orderOf_eq_prime _ _]
  · exact ⟨Nat.prime_two⟩
  rw [sq, sr_mul_self]
  decide
#align dihedral_group.order_of_sr DihedralGroup.orderOf_sr

/-- If `0 < n`, then `r 1` has order `n`.
-/
@[simp]
theorem orderOf_r_one : orderOf (r 1 : DihedralGroup n) = n :=
  by
  rcases eq_zero_or_neZero n with (rfl | hn)
  · rw [orderOf_eq_zero_iff']
    intro n hn
    rw [r_one_pow, one_def]
    apply mt r.inj
    simpa using hn.ne'
  · skip
    apply
      (Nat.le_of_dvd (NeZero.pos n) <|
            orderOf_dvd_of_pow_eq_one <| @r_one_pow_n n).lt_or_eq.resolve_left
    intro h
    have h1 : (r 1 : DihedralGroup n) ^ orderOf (r 1) = 1 := pow_orderOf_eq_one _
    rw [r_one_pow] at h1
    injection h1 with h2
    rw [← ZMod.val_eq_zero, ZMod.val_nat_cast, Nat.mod_eq_of_lt h] at h2
    exact absurd h2.symm (orderOf_pos _).Ne
#align dihedral_group.order_of_r_one DihedralGroup.orderOf_r_one

/-- If `0 < n`, then `i : zmod n` has order `n / gcd n i`.
-/
theorem orderOf_r [NeZero n] (i : ZMod n) : orderOf (r i) = n / Nat.gcd n i.val :=
  by
  conv_lhs => rw [← ZMod.nat_cast_zmod_val i]
  rw [← r_one_pow, orderOf_pow, order_of_r_one]
#align dihedral_group.order_of_r DihedralGroup.orderOf_r

theorem exponent : Monoid.exponent (DihedralGroup n) = lcm n 2 :=
  by
  rcases eq_zero_or_neZero n with (rfl | hn)
  · exact Monoid.exponent_eq_zero_of_order_zero order_of_r_one
  skip
  apply Nat.dvd_antisymm
  · apply Monoid.exponent_dvd_of_forall_pow_eq_one
    rintro (m | m)
    · rw [← orderOf_dvd_iff_pow_eq_one, order_of_r]
      refine' Nat.dvd_trans ⟨gcd n m.val, _⟩ (dvd_lcm_left n 2)
      · exact (Nat.div_mul_cancel (Nat.gcd_dvd_left n m.val)).symm
    · rw [← orderOf_dvd_iff_pow_eq_one, order_of_sr]
      exact dvd_lcm_right n 2
  · apply lcm_dvd
    · convert Monoid.order_dvd_exponent (r 1)
      exact order_of_r_one.symm
    · convert Monoid.order_dvd_exponent (sr 0)
      exact (order_of_sr 0).symm
#align dihedral_group.exponent DihedralGroup.exponent

end DihedralGroup


/-
Copyright (c) 2017 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johannes Hölzl, Floris van Doorn, Sébastien Gouëzel, Alex J. Best

! This file was ported from Lean 3 source module data.list.big_operators.basic
! leanprover-community/mathlib commit 6c5f73fd6f6cc83122788a80a27cdd54663609f4
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Data.Int.Order.Basic
import Mathlib.Data.List.Forall2

/-!
# Sums and products from lists

This file provides basic results about `List.prod`, `List.sum`, which calculate the product and sum
of elements of a list and `List.alternating_prod`, `List.alternating_sum`, their alternating
counterparts. These are defined in [`Data.List.Defs`](./defs).
-/

variable {ι α M N P M₀ G R : Type _}

namespace List

section Monoid

variable [Monoid M] [Monoid N] [Monoid P] {l l₁ l₂ : List M} {a : M}

@[to_additive (attr := simp)]
theorem prod_nil : ([] : List M).prod = 1 :=
  rfl
#align list.prod_nil List.prod_nil
#align list.sum_nil List.sum_nil

@[to_additive]
theorem prod_singleton : [a].prod = a :=
  one_mul a
#align list.prod_singleton List.prod_singleton
#align list.sum_singleton List.sum_singleton

@[to_additive (attr := simp)]
theorem prod_cons : (a :: l).prod = a * l.prod :=
  calc
    (a :: l).prod = foldl (· * ·) (a * 1) l :=
      by simp only [List.prod, foldl_cons, one_mul, mul_one]
    _ = _ := foldl_assoc

#align list.prod_cons List.prod_cons
#align list.sum_cons List.sum_cons

@[to_additive (attr := simp)]
theorem prod_append : (l₁ ++ l₂).prod = l₁.prod * l₂.prod :=
  calc
    (l₁ ++ l₂).prod = foldl (· * ·) (foldl (· * ·) 1 l₁ * 1) l₂ := by simp [List.prod]
    _ = l₁.prod * l₂.prod := foldl_assoc

#align list.prod_append List.prod_append
#align list.sum_append List.sum_append

@[to_additive]
theorem prod_concat : (l.concat a).prod = l.prod * a := by
  rw [concat_eq_append, prod_append, prod_singleton]
#align list.prod_concat List.prod_concat
#align list.sum_concat List.sum_concat

@[to_additive (attr := simp)]
theorem prod_join {l : List (List M)} : l.join.prod = (l.map List.prod).prod := by
  induction l <;> [rfl, simp only [*, List.join, map, prod_append, prod_cons]]
#align list.prod_join List.prod_join
#align list.sum_join List.sum_join

@[to_additive]
theorem prod_eq_foldr : ∀ {l : List M}, l.prod = foldr (· * ·) 1 l
  | [] => rfl
  | cons a l => by rw [prod_cons, foldr_cons, prod_eq_foldr]
#align list.prod_eq_foldr List.prod_eq_foldr
#align list.sum_eq_foldr List.sum_eq_foldr

@[to_additive (attr := simp)]
theorem prod_replicate (n : ℕ) (a : M) : (replicate n a).prod = a ^ n := by
  induction' n with n ih
  · rw [pow_zero]
    rfl
  · rw [replicate_succ, prod_cons, ih, pow_succ]
#align list.prod_replicate List.prod_replicate
#align list.sum_replicate List.sum_replicate

@[to_additive sum_eq_card_nsmul]

@[to_additive]
theorem prod_hom_rel (l : List ι) {r : M → N → Prop} {f : ι → M} {g : ι → N} (h₁ : r 1 1)
    (h₂ : ∀ ⦃i a b⦄, r a b → r (f i * a) (g i * b)) : r (l.map f).prod (l.map g).prod :=
  List.recOn l h₁ fun a l hl => by simp only [map_cons, prod_cons, h₂ hl]
#align list.prod_hom_rel List.prod_hom_rel
#align list.sum_hom_rel List.sum_hom_rel

@[to_additive]
theorem prod_hom (l : List M) {F : Type _} [MonoidHomClass F M N] (f : F) :
    (l.map f).prod = f l.prod := by
  simp only [prod, foldl_map, ← map_one f]
  exact l.foldl_hom f (. * .) (. * f .) 1 (fun x y => (map_mul f x y).symm)
#align list.prod_hom List.prod_hom
#align list.sum_hom List.sum_hom

@[to_additive]
theorem prod_hom₂ (l : List ι) (f : M → N → P) (hf : ∀ a b c d, f (a * b) (c * d) = f a c * f b d)
    (hf' : f 1 1 = 1) (f₁ : ι → M) (f₂ : ι → N) :
    (l.map fun i => f (f₁ i) (f₂ i)).prod = f (l.map f₁).prod (l.map f₂).prod := by
  simp only [prod, foldl_map]
  -- Porting note: next 3 lines used to be
  -- convert l.foldl_hom₂ (fun a b => f a b) _ _ _ _ _ fun a b i => _
  -- · exact hf'.symm
  -- · exact hf _ _ _ _
  rw [← l.foldl_hom₂ (fun a b => f a b), hf']
  intros
  exact hf _ _ _ _
#align list.prod_hom₂ List.prod_hom₂
#align list.sum_hom₂ List.sum_hom₂

@[to_additive (attr := simp)]
theorem prod_map_mul {α : Type _} [CommMonoid α] {l : List ι} {f g : ι → α} :
    (l.map fun i => f i * g i).prod = (l.map f).prod * (l.map g).prod :=
  l.prod_hom₂ (· * ·) mul_mul_mul_comm (mul_one _) _ _
#align list.prod_map_mul List.prod_map_mul
#align list.sum_map_add List.sum_map_add

@[simp]
theorem prod_map_neg {α} [CommMonoid α] [HasDistribNeg α] (l : List α) :
    (l.map Neg.neg).prod = (-1) ^ l.length * l.prod := by
  simpa only [id_eq, neg_mul, one_mul, map_const', prod_replicate, map_id]
    using @prod_map_mul α α _ l (fun _ => -1) id
#align list.prod_map_neg List.prod_map_neg

@[to_additive]
theorem prod_map_hom (L : List ι) (f : ι → M) {G : Type _} [MonoidHomClass G M N] (g : G) :
    (L.map (g ∘ f)).prod = g (L.map f).prod := by rw [← prod_hom, map_map]
#align list.prod_map_hom List.prod_map_hom
#align list.sum_map_hom List.sum_map_hom

@[to_additive]
theorem prod_isUnit : ∀ {L : List M} (_ : ∀ m ∈ L, IsUnit m), IsUnit L.prod
  | [], _ => by simp
  | h :: t, u => by
    simp only [List.prod_cons]
    exact IsUnit.mul (u h (mem_cons_self h t)) (prod_isUnit fun m mt => u m (mem_cons_of_mem h mt))
#align list.prod_is_unit List.prod_isUnit
#align list.sum_is_add_unit List.sum_isAddUnit

@[to_additive]
theorem prod_isUnit_iff {α : Type _} [CommMonoid α] {L : List α} :
    IsUnit L.prod ↔ ∀ m ∈ L, IsUnit m := by
  refine' ⟨fun h => _, prod_isUnit⟩
  induction' L with m L ih
  · exact fun m' h' => False.elim (not_mem_nil m' h')
  rw [prod_cons, IsUnit.mul_iff] at h
  exact fun m' h' => Or.elim (eq_or_mem_of_mem_cons h') (fun H => H.substr h.1) fun H => ih h.2 _ H
#align list.prod_is_unit_iff List.prod_isUnit_iff
#align list.sum_is_add_unit_iff List.sum_isAddUnit_iff

@[to_additive (attr := simp)]
theorem prod_take_mul_prod_drop : ∀ (L : List M) (i : ℕ), (L.take i).prod * (L.drop i).prod = L.prod
  | [], i => by simp [Nat.zero_le]
  | L, 0 => by simp
  | h :: t, n + 1 => by
    dsimp
    rw [prod_cons, prod_cons, mul_assoc, prod_take_mul_prod_drop t]
#align list.prod_take_mul_prod_drop List.prod_take_mul_prod_drop
#align list.sum_take_add_sum_drop List.sum_take_add_sum_drop

@[to_additive (attr := simp)]
theorem prod_take_succ :
    ∀ (L : List M) (i : ℕ) (p), (L.take (i + 1)).prod = (L.take i).prod * L.nthLe i p
  | [], i, p => by cases p
  | h :: t, 0, _ => rfl
  | h :: t, n + 1, p => by
    dsimp
    rw [prod_cons, prod_cons, prod_take_succ t n (Nat.lt_of_succ_lt_succ p), mul_assoc,
      nthLe_cons, dif_neg (Nat.add_one_ne_zero _)]
    simp

#align list.prod_take_succ List.prod_take_succ
#align list.sum_take_succ List.sum_take_succ

/-- A list with product not one must have positive length. -/
@[to_additive "A list with sum not zero must have positive length."]
theorem length_pos_of_prod_ne_one (L : List M) (h : L.prod ≠ 1) : 0 < L.length := by
  cases L
  · contrapose h
    simp
  · simp
#align list.length_pos_of_prod_ne_one List.length_pos_of_prod_ne_one
#align list.length_pos_of_sum_ne_zero List.length_pos_of_sum_ne_zero

/-- A list with product greater than one must have positive length. -/
@[to_additive length_pos_of_sum_pos "A list with positive sum must have positive length."]
theorem length_pos_of_one_lt_prod [Preorder M] (L : List M) (h : 1 < L.prod) : 0 < L.length :=
  length_pos_of_prod_ne_one L h.ne'
#align list.length_pos_of_one_lt_prod List.length_pos_of_one_lt_prod
#align list.length_pos_of_sum_pos List.length_pos_of_sum_pos

/-- A list with product less than one must have positive length. -/
@[to_additive "A list with negative sum must have positive length."]
theorem length_pos_of_prod_lt_one [Preorder M] (L : List M) (h : L.prod < 1) : 0 < L.length :=
  length_pos_of_prod_ne_one L h.ne
#align list.length_pos_of_prod_lt_one List.length_pos_of_prod_lt_one
#align list.length_pos_of_sum_neg List.length_pos_of_sum_neg

@[to_additive]
theorem prod_set :
    ∀ (L : List M) (n : ℕ) (a : M),
      (L.set n a).prod =
        ((L.take n).prod * if n < L.length then a else 1) * (L.drop (n + 1)).prod
  | x :: xs, 0, a => by simp [set]
  | x :: xs, i + 1, a => by simp [set, prod_set xs i a, mul_assoc, Nat.succ_eq_add_one]
  | [], _, _ => by simp [set, (Nat.zero_le _).not_lt, Nat.zero_le]
#align list.prod_update_nth List.prod_set
#align list.sum_update_nth List.sum_set

open MulOpposite

/-- We'd like to state this as `L.headI * L.tail.prod = L.prod`, but because `L.headI` relies on an
inhabited instance to return a garbage value on the empty list, this is not possible.
Instead, we write the statement in terms of `(L.get? 0).getD 1`.
-/
@[to_additive "We'd like to state this as `L.headI + L.tail.sum = L.sum`, but because `L.headI`
  relies on an inhabited instance to return a garbage value on the empty list, this is not possible.
  Instead, we write the statement in terms of `(L.get? 0).getD 0`."]
theorem get?_zero_mul_tail_prod (l : List M) : (l.get? 0).getD 1 * l.tail.prod = l.prod := by
  cases l <;> simp
#align list.nth_zero_mul_tail_prod List.get?_zero_mul_tail_prod
#align list.nth_zero_add_tail_sum List.get?_zero_add_tail_sum

/-- Same as `get?_zero_mul_tail_prod`, but avoiding the `List.headI` garbage complication by
  requiring the list to be nonempty. -/
@[to_additive "Same as `get?_zero_add_tail_sum`, but avoiding the `List.headI` garbage complication
  by requiring the list to be nonempty."]
theorem headI_mul_tail_prod_of_ne_nil [Inhabited M] (l : List M) (h : l ≠ []) :
    l.headI * l.tail.prod = l.prod := by cases l <;> [contradiction, simp]
#align list.head_mul_tail_prod_of_ne_nil List.headI_mul_tail_prod_of_ne_nil
#align list.head_add_tail_sum_of_ne_nil List.headI_add_tail_sum_of_ne_nil

@[to_additive]
theorem _root_.Commute.list_prod_right (l : List M) (y : M) (h : ∀ x ∈ l, Commute y x) :
    Commute y l.prod := by
  induction' l with z l IH
  · simp
  · rw [List.forall_mem_cons] at h
    rw [List.prod_cons]
    exact Commute.mul_right h.1 (IH h.2)
#align commute.list_prod_right Commute.list_prod_right
#align add_commute.list_sum_right AddCommute.list_sum_right

@[to_additive]
theorem _root_.Commute.list_prod_left (l : List M) (y : M) (h : ∀ x ∈ l, Commute x y) :
    Commute l.prod y :=
  ((Commute.list_prod_right _ _) fun _ hx => (h _ hx).symm).symm
#align commute.list_prod_left Commute.list_prod_left
#align add_commute.list_sum_left AddCommute.list_sum_left

@[to_additive sum_le_sum]
theorem Forall₂.prod_le_prod' [Preorder M] [CovariantClass M M (Function.swap (· * ·)) (· ≤ ·)]
    [CovariantClass M M (· * ·) (· ≤ ·)] {l₁ l₂ : List M} (h : Forall₂ (· ≤ ·) l₁ l₂) :
    l₁.prod ≤ l₂.prod := by
  induction' h with a b la lb hab ih ih'
  · rfl
  · simpa only [prod_cons] using mul_le_mul' hab ih'
#align list.forall₂.prod_le_prod' List.Forall₂.prod_le_prod'
#align list.forall₂.sum_le_sum List.Forall₂.sum_le_sum

/-- If `l₁` is a sublist of `l₂` and all elements of `l₂` are greater than or equal to one, then
`l₁.prod ≤ l₂.prod`. One can prove a stronger version assuming `∀ a ∈ l₂.diff l₁, 1 ≤ a` instead
of `∀ a ∈ l₂, 1 ≤ a` but this lemma is not yet in `mathlib`. -/
@[to_additive sum_le_sum "If `l₁` is a sublist of `l₂` and all elements of `l₂` are nonnegative,
  then `l₁.sum ≤ l₂.sum`.
  One can prove a stronger version assuming `∀ a ∈ l₂.diff l₁, 0 ≤ a` instead of `∀ a ∈ l₂, 0 ≤ a`
  but this lemma is not yet in `mathlib`."]
theorem Sublist.prod_le_prod' [Preorder M] [CovariantClass M M (Function.swap (· * ·)) (· ≤ ·)]
    [CovariantClass M M (· * ·) (· ≤ ·)] {l₁ l₂ : List M} (h : l₁ <+ l₂)
    (h₁ : ∀ a ∈ l₂, (1 : M) ≤ a) : l₁.prod ≤ l₂.prod := by
  induction h
  case slnil => rfl
  case cons l₁ l₂ a _ ih' =>
    simp only [prod_cons, forall_mem_cons] at h₁⊢
    exact (ih' h₁.2).trans (le_mul_of_one_le_left' h₁.1)
  case cons₂ l₁ l₂ a _ ih' =>
    simp only [prod_cons, forall_mem_cons] at h₁⊢
    exact mul_le_mul_left' (ih' h₁.2) _
#align list.sublist.prod_le_prod' List.Sublist.prod_le_prod'
#align list.sublist.sum_le_sum List.Sublist.sum_le_sum

@[to_additive sum_le_sum]
theorem SublistForall₂.prod_le_prod' [Preorder M]
    [CovariantClass M M (Function.swap (· * ·)) (· ≤ ·)] [CovariantClass M M (· * ·) (· ≤ ·)]
    {l₁ l₂ : List M} (h : SublistForall₂ (· ≤ ·) l₁ l₂) (h₁ : ∀ a ∈ l₂, (1 : M) ≤ a) :
    l₁.prod ≤ l₂.prod :=
  let ⟨_, hall, hsub⟩ := sublistForall₂_iff.1 h
  hall.prod_le_prod'.trans <| hsub.prod_le_prod' h₁
#align list.sublist_forall₂.prod_le_prod' List.SublistForall₂.prod_le_prod'
#align list.sublist_forall₂.sum_le_sum List.SublistForall₂.sum_le_sum

@[to_additive sum_le_sum]
theorem prod_le_prod' [Preorder M] [CovariantClass M M (Function.swap (· * ·)) (· ≤ ·)]
    [CovariantClass M M (· * ·) (· ≤ ·)] {l : List ι} {f g : ι → M} (h : ∀ i ∈ l, f i ≤ g i) :
    (l.map f).prod ≤ (l.map g).prod :=
  Forall₂.prod_le_prod' <| by simpa
#align list.prod_le_prod' List.prod_le_prod'
#align list.sum_le_sum List.sum_le_sum

@[to_additive sum_lt_sum]
theorem prod_lt_prod' [Preorder M] [CovariantClass M M (· * ·) (· < ·)]
    [CovariantClass M M (· * ·) (· ≤ ·)] [CovariantClass M M (Function.swap (· * ·)) (· < ·)]
    [CovariantClass M M (Function.swap (· * ·)) (· ≤ ·)] {l : List ι} (f g : ι → M)
    (h₁ : ∀ i ∈ l, f i ≤ g i) (h₂ : ∃ i ∈ l, f i < g i) : (l.map f).prod < (l.map g).prod := by
  induction' l with i l ihl
  · rcases h₂ with ⟨_, ⟨⟩, _⟩
  simp only [forall_mem_cons, exists_mem_cons, map_cons, prod_cons] at h₁ h₂⊢
  cases h₂
  · exact mul_lt_mul_of_lt_of_le ‹_› (prod_le_prod' h₁.2)
  · exact mul_lt_mul_of_le_of_lt h₁.1 <| ihl h₁.2 ‹_›
#align list.prod_lt_prod' List.prod_lt_prod'
#align list.sum_lt_sum List.sum_lt_sum

@[to_additive]
theorem prod_lt_prod_of_ne_nil [Preorder M] [CovariantClass M M (· * ·) (· < ·)]
    [CovariantClass M M (· * ·) (· ≤ ·)] [CovariantClass M M (Function.swap (· * ·)) (· < ·)]
    [CovariantClass M M (Function.swap (· * ·)) (· ≤ ·)] {l : List ι} (hl : l ≠ []) (f g : ι → M)
    (hlt : ∀ i ∈ l, f i < g i) : (l.map f).prod < (l.map g).prod :=
  (prod_lt_prod' f g fun i hi => (hlt i hi).le) <|
    (exists_mem_of_ne_nil l hl).imp fun i hi => ⟨hi, hlt i hi⟩
#align list.prod_lt_prod_of_ne_nil List.prod_lt_prod_of_ne_nil
#align list.sum_lt_sum_of_ne_nil List.sum_lt_sum_of_ne_nil

@[to_additive sum_le_card_nsmul]
theorem prod_le_pow_card [Preorder M] [CovariantClass M M (Function.swap (· * ·)) (· ≤ ·)]
    [CovariantClass M M (· * ·) (· ≤ ·)] (l : List M) (n : M) (h : ∀ x ∈ l, x ≤ n) :
    l.prod ≤ n ^ l.length := by
      simpa only [map_id'', map_const', prod_replicate] using prod_le_prod' h
#align list.prod_le_pow_card List.prod_le_pow_card
#align list.sum_le_card_nsmul List.sum_le_card_nsmul

@[to_additive exists_lt_of_sum_lt]
theorem exists_lt_of_prod_lt' [LinearOrder M] [CovariantClass M M (Function.swap (· * ·)) (· ≤ ·)]
    [CovariantClass M M (· * ·) (· ≤ ·)] {l : List ι} (f g : ι → M)
    (h : (l.map f).prod < (l.map g).prod) : ∃ i ∈ l, f i < g i := by
  contrapose! h
  exact prod_le_prod' h
#align list.exists_lt_of_prod_lt' List.exists_lt_of_prod_lt'
#align list.exists_lt_of_sum_lt List.exists_lt_of_sum_lt

@[to_additive exists_le_of_sum_le]
theorem exists_le_of_prod_le' [LinearOrder M] [CovariantClass M M (· * ·) (· < ·)]
    [CovariantClass M M (· * ·) (· ≤ ·)] [CovariantClass M M (Function.swap (· * ·)) (· < ·)]
    [CovariantClass M M (Function.swap (· * ·)) (· ≤ ·)] {l : List ι} (hl : l ≠ []) (f g : ι → M)
    (h : (l.map f).prod ≤ (l.map g).prod) : ∃ x ∈ l, f x ≤ g x := by
  contrapose! h
  exact prod_lt_prod_of_ne_nil hl _ _ h
#align list.exists_le_of_prod_le' List.exists_le_of_prod_le'
#align list.exists_le_of_sum_le List.exists_le_of_sum_le

@[to_additive sum_nonneg]
theorem one_le_prod_of_one_le [Preorder M] [CovariantClass M M (· * ·) (· ≤ ·)] {l : List M}
    (hl₁ : ∀ x ∈ l, (1 : M) ≤ x) : 1 ≤ l.prod := by
  -- We don't use `pow_card_le_prod` to avoid assumption
  -- [covariant_class M M (function.swap (*)) (≤)]
  induction' l with hd tl ih
  · rfl
  rw [prod_cons]
  exact one_le_mul (hl₁ hd (mem_cons_self hd tl)) (ih fun x h => hl₁ x (mem_cons_of_mem hd h))
#align list.one_le_prod_of_one_le List.one_le_prod_of_one_le
#align list.sum_nonneg List.sum_nonneg

end Monoid

section MonoidWithZero

variable [MonoidWithZero M₀]

/-- If zero is an element of a list `L`, then `List.prod L = 0`. If the domain is a nontrivial
monoid with zero with no divisors, then this implication becomes an `iff`, see
`List.prod_eq_zero_iff`. -/
theorem prod_eq_zero {L : List M₀} (h : (0 : M₀) ∈ L) : L.prod = 0 := by
  induction' L with a L ihL
  · exact absurd h (not_mem_nil _)
  · rw [prod_cons]
    cases' mem_cons.1 h with ha hL
    exacts[mul_eq_zero_of_left ha.symm _, mul_eq_zero_of_right _ (ihL hL)]
#align list.prod_eq_zero List.prod_eq_zero

/-- Product of elements of a list `L` equals zero if and only if `0 ∈ L`. See also
`List.prod_eq_zero` for an implication that needs weaker typeclass assumptions. -/
@[simp]
theorem prod_eq_zero_iff [Nontrivial M₀] [NoZeroDivisors M₀] {L : List M₀} :
    L.prod = 0 ↔ (0 : M₀) ∈ L := by
  induction' L with a L ihL
  · simp
  · rw [prod_cons, mul_eq_zero, ihL, mem_cons, eq_comm]
#align list.prod_eq_zero_iff List.prod_eq_zero_iff

theorem prod_ne_zero [Nontrivial M₀] [NoZeroDivisors M₀] {L : List M₀} (hL : (0 : M₀) ∉ L) :
    L.prod ≠ 0 :=
  mt prod_eq_zero_iff.1 hL
#align list.prod_ne_zero List.prod_ne_zero

end MonoidWithZero

section Group

variable [Group G]

/-- This is the `List.prod` version of `mul_inv_rev` -/
@[to_additive "This is the `List.sum` version of `add_neg_rev`"]
theorem prod_inv_reverse : ∀ L : List G, L.prod⁻¹ = (L.map fun x => x⁻¹).reverse.prod
  | [] => by simp
  | x :: xs => by simp [prod_inv_reverse xs]
#align list.prod_inv_reverse List.prod_inv_reverse
#align list.sum_neg_reverse List.sum_neg_reverse

/-- A non-commutative variant of `List.prod_reverse` -/
@[to_additive "A non-commutative variant of `List.sum_reverse`"]
theorem prod_reverse_noncomm : ∀ L : List G, L.reverse.prod = (L.map fun x => x⁻¹).prod⁻¹ := by
  simp [prod_inv_reverse]
#align list.prod_reverse_noncomm List.prod_reverse_noncomm
#align list.sum_reverse_noncomm List.sum_reverse_noncomm

set_option linter.deprecated false in
/-- Counterpart to `List.prod_take_succ` when we have an inverse operation -/
@[to_additive (attr := simp)
  "Counterpart to `List.sum_take_succ` when we have an negation operation"]
theorem prod_drop_succ :
    ∀ (L : List G) (i : ℕ) (p), (L.drop (i + 1)).prod = (L.nthLe i p)⁻¹ * (L.drop i).prod
  | [], i, p => False.elim (Nat.not_lt_zero _ p)
  | x :: xs, 0, _ => by simp [nthLe]
  | x :: xs, i + 1, p => prod_drop_succ xs i _
#align list.prod_drop_succ List.prod_drop_succ
#align list.sum_drop_succ List.sum_drop_succ

end Group

section CommGroup

variable [CommGroup G]

/-- This is the `List.prod` version of `mul_inv` -/
@[to_additive "This is the `List.sum` version of `add_neg`"]
theorem prod_inv : ∀ L : List G, L.prod⁻¹ = (L.map fun x => x⁻¹).prod
  | [] => by simp
  | x :: xs => by simp [mul_comm, prod_inv xs]
#align list.prod_inv List.prod_inv
#align list.sum_neg List.sum_neg

/-- Alternative version of `List.prod_set` when the list is over a group -/
@[to_additive "Alternative version of `List.sum_set` when the list is over a group"]
theorem prod_set' (L : List G) (n : ℕ) (a : G) :
    (L.set n a).prod = L.prod * if hn : n < L.length then (L.nthLe n hn)⁻¹ * a else 1 := by
  refine (prod_set L n a).trans ?_
  split_ifs with hn
  · rw [mul_comm _ a, mul_assoc a, prod_drop_succ L n hn, mul_comm _ (drop n L).prod, ←
      mul_assoc (take n L).prod, prod_take_mul_prod_drop, mul_comm a, mul_assoc]
  · simp only [take_all_of_le (le_of_not_lt hn), prod_nil, mul_one,
      drop_eq_nil_of_le ((le_of_not_lt hn).trans n.le_succ)]
#align list.prod_update_nth' List.prod_set'
#align list.sum_update_nth' List.sum_set'

end CommGroup

@[to_additive]
theorem eq_of_prod_take_eq [LeftCancelMonoid M] {L L' : List M} (h : L.length = L'.length)
    (h' : ∀ i ≤ L.length, (L.take i).prod = (L'.take i).prod) : L = L' := by
  refine ext_get h fun i h₁ h₂ => ?_
  have : (L.take (i + 1)).prod = (L'.take (i + 1)).prod := h' _ (Nat.succ_le_of_lt h₁)
  rw [prod_take_succ L i h₁, prod_take_succ L' i h₂, h' i (le_of_lt h₁)] at this
  convert mul_left_cancel this
#align list.eq_of_prod_take_eq List.eq_of_prod_take_eq
#align list.eq_of_sum_take_eq List.eq_of_sum_take_eq

@[to_additive]
theorem monotone_prod_take [CanonicallyOrderedMonoid M] (L : List M) :
    Monotone fun i => (L.take i).prod := by
  refine' monotone_nat_of_le_succ fun n => _
  cases' lt_or_le n L.length with h h
  · rw [prod_take_succ _ _ h]
    exact le_self_mul
  · simp [take_all_of_le h, take_all_of_le (le_trans h (Nat.le_succ _))]
#align list.monotone_prod_take List.monotone_prod_take
#align list.monotone_sum_take List.monotone_sum_take

@[to_additive sum_pos]
theorem one_lt_prod_of_one_lt [OrderedCommMonoid M] :
    ∀ (l : List M) (_ : ∀ x ∈ l, (1 : M) < x) (_ : l ≠ []), 1 < l.prod
  | [], _, h => (h rfl).elim
  | [b], h, _ => by simpa using h
  | a :: b :: l, hl₁, _ =>
    by
    simp only [forall_eq_or_imp, List.mem_cons] at hl₁
    rw [List.prod_cons]
    apply one_lt_mul_of_lt_of_le' hl₁.1
    apply le_of_lt ((b :: l).one_lt_prod_of_one_lt _ (l.cons_ne_nil b))
    intro x hx; cases hx
    · exact hl₁.2.1
    · exact hl₁.2.2 _ ‹_›
#align list.one_lt_prod_of_one_lt List.one_lt_prod_of_one_lt
#align list.sum_pos List.sum_pos

@[to_additive]
theorem single_le_prod [OrderedCommMonoid M] {l : List M} (hl₁ : ∀ x ∈ l, (1 : M) ≤ x) :
    ∀ x ∈ l, x ≤ l.prod := by
  induction l
  · simp
  simp_rw [prod_cons, forall_mem_cons] at hl₁⊢
  constructor
  case cons.left => exact le_mul_of_one_le_right' (one_le_prod_of_one_le hl₁.2)
  case cons.right hd tl ih => exact fun x H => le_mul_of_one_le_of_le hl₁.1 (ih hl₁.right x H)
#align list.single_le_prod List.single_le_prod
#align list.single_le_sum List.single_le_sum

@[to_additive all_zero_of_le_zero_le_of_sum_eq_zero]
theorem all_one_of_le_one_le_of_prod_eq_one [OrderedCommMonoid M] {l : List M}
    (hl₁ : ∀ x ∈ l, (1 : M) ≤ x) (hl₂ : l.prod = 1) {x : M} (hx : x ∈ l) : x = 1 :=
  _root_.le_antisymm (hl₂ ▸ single_le_prod hl₁ _ hx) (hl₁ x hx)
#align list.all_one_of_le_one_le_of_prod_eq_one List.all_one_of_le_one_le_of_prod_eq_one
#align list.all_zero_of_le_zero_le_of_sum_eq_zero List.all_zero_of_le_zero_le_of_sum_eq_zero

/-- Slightly more general version of `List.prod_eq_one_iff` for a non-ordered `Monoid` -/
@[to_additive
      "Slightly more general version of `List.sum_eq_zero_iff` for a non-ordered `AddMonoid`"]
theorem prod_eq_one [Monoid M] {l : List M} (hl : ∀ x ∈ l, x = (1 : M)) : l.prod = 1 := by
  induction' l with i l hil
  · rfl
  rw [List.prod_cons, hil fun x hx => hl _ (mem_cons_of_mem i hx), hl _ (mem_cons_self i l),
    one_mul]
#align list.prod_eq_one List.prod_eq_one
#align list.sum_eq_zero List.sum_eq_zero

@[to_additive]
theorem exists_mem_ne_one_of_prod_ne_one [Monoid M] {l : List M} (h : l.prod ≠ 1) :
    ∃ x ∈ l, x ≠ (1 : M) := by simpa only [not_forall, exists_prop] using mt prod_eq_one h
#align list.exists_mem_ne_one_of_prod_ne_one List.exists_mem_ne_one_of_prod_ne_one
#align list.exists_mem_ne_zero_of_sum_ne_zero List.exists_mem_ne_zero_of_sum_ne_zero

-- TODO: develop theory of tropical rings
theorem sum_le_foldr_max [AddMonoid M] [AddMonoid N] [LinearOrder N] (f : M → N) (h0 : f 0 ≤ 0)
    (hadd : ∀ x y, f (x + y) ≤ max (f x) (f y)) (l : List M) : f l.sum ≤ (l.map f).foldr max 0 := by
  induction' l with hd tl IH
  · simpa using h0
  simp only [List.sum_cons, List.foldr_map, List.foldr] at IH⊢
  exact (hadd _ _).trans (max_le_max le_rfl IH)
#align list.sum_le_foldr_max List.sum_le_foldr_max

@[to_additive (attr := simp)]
theorem prod_erase [DecidableEq M] [CommMonoid M] {a} :
    ∀ {l : List M}, a ∈ l → a * (l.erase a).prod = l.prod
  | b :: l, h => by
    obtain rfl | ⟨ne, h⟩ := Decidable.List.eq_or_ne_mem_of_mem h
    · simp only [List.erase, if_pos, prod_cons, beq_self_eq_true]
    · simp only [List.erase, beq_false_of_ne ne.symm, prod_cons, prod_erase h, mul_left_comm a b]
#align list.prod_erase List.prod_erase
#align list.sum_erase List.sum_erase

@[to_additive (attr := simp)]
theorem prod_map_erase [DecidableEq ι] [CommMonoid M] (f : ι → M) {a} :
    ∀ {l : List ι}, a ∈ l → f a * ((l.erase a).map f).prod = (l.map f).prod
  | b :: l, h => by
    obtain rfl | ⟨ne, h⟩ := Decidable.List.eq_or_ne_mem_of_mem h
    · simp only [map, erase_cons_head, prod_cons]
    · simp only [map, erase_cons_tail _ ne.symm, prod_cons, prod_map_erase _ h,
        mul_left_comm (f a) (f b)]
#align list.prod_map_erase List.prod_map_erase
#align list.sum_map_erase List.sum_map_erase

theorem sum_const_nat (m n : ℕ) : sum (replicate m n) = m * n :=
  sum_replicate m n
#align list.sum_const_nat List.sum_const_nat

/-- The product of a list of positive natural numbers is positive,
and likewise for any nontrivial ordered semiring. -/
theorem prod_pos [StrictOrderedSemiring R] (l : List R) (h : ∀ a ∈ l, (0 : R) < a) :
    0 < l.prod := by
  induction' l with a l ih
  · simp
  · rw [prod_cons]
    exact mul_pos (h _ <| mem_cons_self _ _) (ih fun a ha => h a <| mem_cons_of_mem _ ha)
#align list.prod_pos List.prod_pos

/-- A variant of `List.prod_pos` for `CanonicallyOrderedCommSemiring`. -/
@[simp] lemma _root_.CanonicallyOrderedCommSemiring.list_prod_pos
    {α : Type _} [CanonicallyOrderedCommSemiring α] [Nontrivial α] :
    ∀ {l : List α}, 0 < l.prod ↔ (∀ x ∈ l, (0 : α) < x)
  | [] => by simp
  | (x :: xs) => by simp_rw [prod_cons, forall_mem_cons, CanonicallyOrderedCommSemiring.mul_pos,
    list_prod_pos]
#align canonically_ordered_comm_semiring.list_prod_pos CanonicallyOrderedCommSemiring.list_prod_pos

/-!
Several lemmas about sum/head/tail for `List ℕ`.
These are hard to generalize well, as they rely on the fact that `default ℕ = 0`.
If desired, we could add a class stating that `default = 0`.
-/

/-- This relies on `default ℕ = 0`. -/
theorem headI_add_tail_sum (L : List ℕ) : L.headI + L.tail.sum = L.sum := by
  cases L <;> simp
#align list.head_add_tail_sum List.headI_add_tail_sum

/-- This relies on `default ℕ = 0`. -/
theorem headI_le_sum (L : List ℕ) : L.headI ≤ L.sum :=
  Nat.le.intro (headI_add_tail_sum L)
#align list.head_le_sum List.headI_le_sum

/-- This relies on `default ℕ = 0`. -/
theorem tail_sum (L : List ℕ) : L.tail.sum = L.sum - L.headI := by
  rw [← headI_add_tail_sum L, add_comm, @add_tsub_cancel_right]
#align list.tail_sum List.tail_sum

section Alternating

section

variable [One α] [Mul α] [Inv α]

@[to_additive (attr := simp)]
theorem alternatingProd_nil : alternatingProd ([] : List α) = 1 :=
  rfl
#align list.alternating_prod_nil List.alternatingProd_nil
#align list.alternating_sum_nil List.alternatingSum_nil

@[to_additive (attr := simp)]
theorem alternatingProd_singleton (a : α) : alternatingProd [a] = a :=
  rfl
#align list.alternating_prod_singleton List.alternatingProd_singleton
#align list.alternating_sum_singleton List.alternatingSum_singleton

@[to_additive]
theorem alternatingProd_cons_cons' (a b : α) (l : List α) :
    alternatingProd (a :: b :: l) = a * b⁻¹ * alternatingProd l :=
  rfl
#align list.alternating_prod_cons_cons' List.alternatingProd_cons_cons'
#align list.alternating_sum_cons_cons' List.alternatingSum_cons_cons'

end

@[to_additive]
theorem alternatingProd_cons_cons [DivInvMonoid α] (a b : α) (l : List α) :
    alternatingProd (a :: b :: l) = a / b * alternatingProd l := by
  rw [div_eq_mul_inv, alternatingProd_cons_cons']
#align list.alternating_prod_cons_cons List.alternatingProd_cons_cons
#align list.alternating_sum_cons_cons List.alternatingSum_cons_cons

variable [CommGroup α]

@[to_additive]
theorem alternatingProd_cons' :
    ∀ (a : α) (l : List α), alternatingProd (a :: l) = a * (alternatingProd l)⁻¹
  | a, [] => by rw [alternatingProd_nil, inv_one, mul_one, alternatingProd_singleton]
  | a, b :: l => by
    rw [alternatingProd_cons_cons', alternatingProd_cons' b l, mul_inv, inv_inv, mul_assoc]
#align list.alternating_prod_cons' List.alternatingProd_cons'
#align list.alternating_sum_cons' List.alternatingSum_cons'

@[to_additive (attr := simp)]
theorem alternatingProd_cons (a : α) (l : List α) :
    alternatingProd (a :: l) = a / alternatingProd l := by
  rw [div_eq_mul_inv, alternatingProd_cons']
#align list.alternating_prod_cons List.alternatingProd_cons
#align list.alternating_sum_cons List.alternatingSum_cons

end Alternating

lemma sum_nat_mod (l : List ℕ) (n : ℕ) : l.sum % n = (l.map (· % n)).sum % n := by
  induction l <;> simp [Nat.add_mod, *]
#align list.sum_nat_mod List.sum_nat_mod

lemma prod_nat_mod (l : List ℕ) (n : ℕ) : l.prod % n = (l.map (· % n)).prod % n := by
  induction l <;> simp [Nat.mul_mod, *]
#align list.prod_nat_mod List.prod_nat_mod

lemma sum_int_mod (l : List ℤ) (n : ℤ) : l.sum % n = (l.map (· % n)).sum % n := by
  induction l <;> simp [Int.add_emod, *]
#align list.sum_int_mod List.sum_int_mod

lemma prod_int_mod (l : List ℤ) (n : ℤ) : l.prod % n = (l.map (· % n)).prod % n := by
  induction l <;> simp [Int.mul_emod, *]
#align list.prod_int_mod List.prod_int_mod

end List

section MonoidHom

variable [Monoid M] [Monoid N]

@[to_additive]
theorem map_list_prod {F : Type _} [MonoidHomClass F M N] (f : F) (l : List M) :
    f l.prod = (l.map f).prod :=
  (l.prod_hom f).symm
#align map_list_prod map_list_prod
#align map_list_sum map_list_sum

namespace MonoidHom

/-- Deprecated, use `_root_.map_list_prod` instead. -/
@[to_additive "Deprecated, use `_root_.map_list_sum` instead."]
protected theorem map_list_prod (f : M →* N) (l : List M) : f l.prod = (l.map f).prod :=
  map_list_prod f l
#align monoid_hom.map_list_prod MonoidHom.map_list_prod
#align add_monoid_hom.map_list_sum AddMonoidHom.map_list_sum

end MonoidHom

end MonoidHom


section chap3ex1
    variables p q r : Prop

    example : p ∧ q ↔ q ∧ p :=
    begin
        apply iff.intro,
        repeat {
          intro h,
            exact and.intro h.right h.left,
        }
    end

    example : p ∨ q ↔ q ∨ p :=
    begin
        apply iff.intro,
          intro h,
          apply or.elim h,
            intro hp,
            right, exact hp,
            intro hq,
            left, exact hq,
        intro h,
        apply or.elim h,
          intro hp,
          right, exact hp,
        intro hq,
        left, exact hq
    end

    example : (p ∧ q) ∧ r ↔ p ∧ (q ∧ r) :=
    begin
        apply iff.intro,
        intro h,
        exact and.intro h.left.left ⟨h.left.right, h.right⟩,
        intro h,
        exact and.intro ⟨h.left, h.right.left⟩ h.right.right
    end

    example : (p ∨ q) ∨ r ↔ p ∨ (q ∨ r) :=
    begin
        apply iff.intro,
        { intro h,
          cases h with hpq hr,
          { cases hpq with hp hq,
            { left, assumption },
            { right, left, assumption }},
          { right, right, exact hr }},
        intro h,
        cases h with hp hqr,
        { left, left, exact hp },
        { cases hqr with hq hr,
          { left, right, exact hq },
          { right, exact hr }}
    end

    example : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) :=
    begin
        apply iff.intro,
        { intro h,
          have hp : p, from h.left,
          have hqr : q ∨ r, from h.right,
          cases hqr with hq hr,
          { left, constructor, assumption, assumption },
          right, constructor, assumption, assumption },
        intro h,
        cases h with hpq hpr,
          { constructor, exact hpq.left, left, exact hpq.right },
        constructor, exact hpr.left, right, exact hpr.right
    end

    example : p ∨ (q ∧ r) ↔ (p ∨ q) ∧ (p ∨ r) :=
    begin
      apply iff.intro,
      { intro h,
        cases h with hp hqr,
        { constructor, repeat { left, exact hp } },
        { constructor; right, exact hqr.left, exact hqr.right } },
      intro h,
      have pq : p ∨ q, from h.left,
      have pr : p ∨ r, from h.right,
      cases pq with hp hq,
      { left, exact hp },
      have hq : q, from hq,
      cases pr with hp hr,
      { left, exact hp },
      right, constructor, exact hq, exact hr
    end

    example : (p → (q → r)) ↔ (p ∧ q → r) :=
    begin
      apply iff.intro,
      { intros h1 h2, exact h1 h2.left h2.right  },
      intros h1 hp hq,
      have pq : p ∧ q, { constructor, repeat { assumption }},
      exact h1 pq
    end

    example : ((p ∨ q) → r) ↔ (p → r) ∧ (q → r) :=
    begin
      apply iff.intro,
      { intro h,
        constructor ;
        { intro h1,
          have pq : p ∨ q, { { left, exact h1 } <|> { right, exact h1 } },
          exact h pq }},
      intros h1 h2,
      cases h2 with hp hq,
        exact h1.left hp,
      exact h1.right hq
    end

    example : ¬(p ∨ q) ↔ ¬p ∧ ¬q :=
    begin
      apply iff.intro,
      { intro hnpq,
        constructor ;
        { intro h1,
          have hpq : p ∨ q, { { left <|> right, exact h1 } <|> { right, exact h1 } },
          exact absurd hpq hnpq }},
      intros h hc,
      have hnp : ¬ p, from h.left,
      have hnq : ¬ q, from h.right,
      cases hc with hp hq;
      { apply absurd, exact hp <|> exact hq, assumption}
    end

    example : ¬p ∨ ¬q → ¬(p ∧ q) :=
    begin
      intros hdis hconj,
      cases hdis with hnp hnq,
        { exact absurd hconj.left hnp },
      exact absurd hconj.right hnq
    end

    example : ¬(p ∧ ¬p) :=
    begin
      intro h,
      exact absurd h.left h.right
    end

    example : p ∧ ¬q → ¬(p → q) :=
    begin
      intros hconj hn,
      have hq : q, { exact hn hconj.left },
      exact absurd hq hconj.right
    end

    example : ¬p → (p → q) :=
    begin
      intros hnp hp,
      exact (false.elim $ hnp hp)
    end

    example : (¬p ∨ q) → (p → q) :=
    begin
      intros hdis hp,
      cases hdis with hnp hq,
      { exact absurd hp hnp },
      assumption
    end

    example : p ∨ false ↔ p :=
    begin
      apply iff.intro,
      { intro h, cases h with hp hfalse, assumption, exact false.elim hfalse },
      intro hp, left, exact hp
    end

    example : p ∧ false ↔ false :=
    begin
      apply iff.intro,
      { intro h, exact h.right },
      intro hfalse, exact false.elim hfalse
    end

    example : ¬(p ↔ ¬p) :=
    begin
      intro h,
      have l : p → ¬p, { exact iff.elim_left h },
      have r : ¬p → p, { exact iff.elim_right h },
      have hnp : ¬p, { intro hp, exact absurd hp (l hp) },
      exact absurd (r hnp) hnp,
    end

    example : (p → q) → (¬q → ¬p) :=
    begin
      intros hptoq hnq hp,
      exact absurd (hptoq hp) hnq
    end

end chap3ex1

section chap3ex2
    open classical

    variables p q r s : Prop

    example : (p → r ∨ s) → ((p → r) ∨ (p → s)) :=
    begin
      intro h,
      cases (em p) with hp hnp,
      { cases (h hp) with hr hs,
        { left, exact (λ hp, hr) },
        right, exact (λ hp, hs)},
      left,
      intro hp,
      contradiction
    end

    example : ¬(p ∧ q) → ¬p ∨ ¬q :=
    begin
      intro h,
      cases (em p) with hp hnp,
      { cases (em q) with hq hnq,
        { exact (false.elim $ h ⟨hp, hq⟩) },
        right, exact hnq },
      left, exact hnp
    end

    example : ¬(p → q) → p ∧ ¬q :=
    begin
      intro h,
      cases (em p) with hp hnp,
      { cases (em q) with hq hnq,
        { have  ptoq : (p → q), from λ hp, hq,
          contradiction },
        constructor, assumption, assumption },
      cases (em q) with hq hnq,
      { have ptoq : p → q, from λ hp, absurd hp hnp,
        contradiction },
      have ptoq : p → q, { intro hp, contradiction },
      contradiction
    end

    example : (p → q) → (¬p ∨ q) :=
    begin
      intro h,
      cases (em p) with hp hnp,
      { right, exact h hp },
      left, exact hnp
    end

    example : (¬q → ¬p) → (p → q) :=
    begin
      intro h,
      cases (em q) with hq hnq,
        { exact (λ hp, hq) },
      have hnp : ¬p, from h hnq,
      exact (λ hp, absurd hp hnp)
    end

    example : p ∨ ¬p :=
    begin
      cases (em p) with hp hnp,
      { left, assumption },
      right, assumption
    end

    example : (((p → q) → p) → p) :=
    begin
      intro h,
      cases (em p) with hp hnp,
      { assumption },
      have ptoq : p → q, { intro hp, contradiction },
      exact h ptoq
    end

end chap3ex2

section chap3ex3

  example { p : Prop } : ¬(p ↔ ¬p) :=
  begin
    intro h,
    have nptop : ¬p → p, from iff.elim_right h,
    have ptonp : p → ¬p, from iff.elim_left h,
    have np : ¬p, { intro hp, exact absurd hp (ptonp hp) },
    exact absurd (nptop np) np
  end

end chap3ex3

section chap4ex1

  variables (α : Type) (p q : α → Prop)

  example : (∀ x, p x ∧ q x) ↔ (∀ x, p x) ∧ (∀ x, q x) :=
  begin
    apply iff.intro,
    { intro h,
      constructor,
      repeat {
        intro ha,
        let h1 := h ha,
        exact h1.left <|> exact h1.right
      }},
    intros h ha,
    constructor,
    { exact h.left ha },
    exact h.right ha
  end

  example : (∀ x, p x → q x) → (∀ x, p x) → (∀ x, q x) :=
  begin
    intros h1 h2 hx,
    exact (h1 hx $ h2 hx)
  end

  example : (∀ x, p x) ∨ (∀ x, q x) → ∀ x, p x ∨ q x :=
  begin
    intros h1 hx,
    cases h1 with hpx hqx,
    { left, exact hpx hx },
    right, exact hqx hx
  end

end chap4ex1

section chap4ex2

  variables (α : Type) (p q : α → Prop)
  variable r : Prop

  example : α → ((∀ x : α, r) ↔ r) :=
  begin
    intro ha,
    apply iff.intro,
    { intro hr, exact hr ha },
    intros hr hx,
    assumption
  end

  -- one branch requires classical logic
  example : (∀ x, p x ∨ r) ↔ (∀ x, p x) ∨ r :=
  begin
    apply iff.intro,
    { intros h,
      cases (classical.em r) with hr hnr,
      { right, assumption },
      left,
      intro hx,
      cases (h hx), assumption, contradiction },
    intros h hx,
    cases h with hpx hr,
    { left, exact hpx hx },
    right, exact hr
  end

  example : (∀ x, r → p x) ↔ (r → ∀ x, p x) :=
  begin
    apply iff.intro,
    { intros h hr hx, exact h hx hr },
    intros h hx hr, exact (h hr) hx
  end

end chap4ex2

section chap4ex3

  variables (men : Type) (barber : men)
  variable  (shaves : men → men → Prop)

  example (h : ∀ x : men, shaves barber x ↔ ¬ shaves x x) : false :=
  begin
    have barber_case : shaves barber barber ↔ ¬ shaves barber barber, from h barber,
    have barber_doesnt_shave_himself: ¬(shaves barber barber),
      begin
        intro h1,
        have : ¬ shaves barber barber, from (iff.elim_left barber_case) h1,
        contradiction
      end,
    have does_he_though : shaves barber barber, from (iff.elim_right barber_case) barber_doesnt_shave_himself,
    contradiction
  end

end chap4ex3

-- chap4ex4 doesn't involve any proof

section chap4ex5

  open classical

  variables (α : Type) (p q : α → Prop)
  variable a : α
  variable r : Prop

  include a

  example : (∃ x : α, r) → r :=
  begin
    intro h,
    cases h,
    assumption
  end

  example : r → (∃ x : α, r) :=
  begin
    intro h,
    constructor, exact a, exact h
  end

  example : (∃ x, p x ∧ r) ↔ (∃ x, p x) ∧ r :=
  begin
    apply iff.intro,
    { intro h,
      cases h with ha hconj,
      constructor,
      { existsi ha, exact hconj.left },
      exact hconj.right },
    intro h,
    have ex : ∃ x, p x, from h.left,
    cases ex with hx hpx,
    existsi hx,
    constructor,
    { exact hpx },
    exact h.right,
  end

  example : (∃ x, p x ∨ q x) ↔ (∃ x, p x) ∨ (∃ x, q x) :=
  begin
    apply iff.intro,
    { intro h,
      cases h with hx hdisj,
      cases hdisj with hpx hqx,
      { left, existsi hx, exact hpx },
      right, existsi hx, exact hqx },
    intro h,
    cases h with hpx hqx,
    { cases hpx with hx hpx', existsi hx, left, exact hpx' },
    cases hqx with hx hqx', existsi hx, right, exact hqx'
  end

  def forall_px_not_exists_not_px : (∀ x, p x) ↔ ¬ (∃ x, ¬ p x) :=
  begin
    apply iff.intro,
    { intros h hcon,
      cases hcon with hx hnpx,
      have hpx : p hx, from h hx,
      contradiction },
    intros h hx,
    cases (classical.em (p hx)) with _ hnpx,
    { show p hx, by assumption },
    show p hx,
    have : ∃ x, ¬p x, { existsi hx, exact hnpx },
    contradiction
  end

  example : (∀ x, p x) ↔ ¬ (∃ x, ¬ p x) := @forall_px_not_exists_not_px α p a

  def exists_p_x_not_forall_not_px : (∃ x, p x) ↔ ¬ (∀ x, ¬ p x) :=
  begin
    apply iff.intro,
    { intros h hcon,
      cases h with hx hpx,
      have hnpx : ¬ p hx, { exact hcon hx },
      contradiction },
    show ¬(∀ x, ¬p x) → (∃ x, p x),
    intro h,
    cases (classical.em (∃ x, p x)) with _ hnex,
    { assumption },
    have hcont : ∀x, ¬p x, {
        intros hx hpx,
        have hex : ∃ x, p x, { existsi hx, exact hpx },
        contradiction
      },
    contradiction
  end

  example : (∃ x, p x) ↔ ¬ (∀ x, ¬ p x) := @exists_p_x_not_forall_not_px α p a

  example : (¬ ∃ x, p x) ↔ (∀ x, ¬ p x) :=
  begin
    apply iff.intro,
    { intros h hx,
      cases (em (p hx)) with hpx hnpx,
      { have : ∃ x, p x, from ⟨hx, hpx⟩,
        contradiction },
      assumption },
    intros h hcont,
    cases hcont with hx hpx,
    have nphx : ¬ p hx, from h hx,
    contradiction
  end

  def not_forall_exists_not_equivalence : (¬ ∀ x, p x) ↔ (∃ x, ¬ p x) :=
  begin
    apply iff.intro,
    { intro h,
      cases (em (p a)) with hpa hnpa,
      { cases (em ∃ x, ¬ p x) with hexists hnexists,
        { assumption },
        have : ∀ x, p x, from iff.elim_right (@forall_px_not_exists_not_px α p a) hnexists,
        contradiction },
      existsi a, exact hnpa },
    intros h hcont,
    cases h with hx hnpx,
    have hpx : p hx, from hcont hx,
    contradiction
  end

  example : (¬ ∀ x, p x) ↔ (∃ x, ¬ p x) := @not_forall_exists_not_equivalence α p a

  example : (∀ x, p x → r) ↔ (∃ x, p x) → r :=
  begin
    apply iff.intro,
    { intros h hex,
      cases hex with hx hpx,
      exact h hx hpx},
    intros h hx hphx,
    have : ∃ x, p x, from ⟨hx, hphx⟩,
    exact h this
  end

  example : (∃ x, p x → r) ↔ (∀ x, p x) → r :=
  begin
    apply iff.intro,
    { intros hex hfa,
      cases hex with hx hphx,
      have hhx : p hx, from hfa hx,
      exact hphx hhx },
    intros h,
    cases (em (∀ (x : α), p x)) with hyes hno,
    { existsi a, intro hpa, exact h hyes },
    have : (∃ x, ¬ p x), from iff.elim_left (@not_forall_exists_not_equivalence α p a) hno,
    cases this with hx hnpx,
    existsi hx, intro hpx, contradiction
  end

  example : (∃ x, r → p x) ↔ (r → ∃ x, p x) :=
  begin
    apply iff.intro,
    { intros hex hr,
      cases hex with hx hrtopx,
      existsi hx, exact hrtopx hr },
    intro h,
    cases (em r) with hr hnr,
    { have : (∃ (x : α), p x), from h hr,
      cases this with hx hpx,
      existsi hx, intro hr, exact hpx },
    existsi a, intro hr, contradiction
  end

end chap4ex5

section chap4ex6
  variables (real : Type) [ordered_ring real]
  variables (log exp : real → real)
  variable  log_exp_eq : ∀ x, log (exp x) = x
  variable  exp_log_eq : ∀ {x}, x > 0 → exp (log x) = x
  variable  exp_pos    : ∀ x, exp x > 0
  variable  exp_add    : ∀ x y, exp (x + y) = exp x * exp y

  -- this ensures the assumptions are available in tactic proofs
  include log_exp_eq exp_log_eq exp_pos exp_add

  example (x y z : real) :
    exp (x + y + z) = exp x * exp y * exp z :=
  by rw [exp_add, exp_add]

  example (y : real) (h : y > 0)  : exp (log y) = y :=
  exp_log_eq h

  theorem log_mul {x y : real} (hx : x > 0) (hy : y > 0) :
    log (x * y) = log x + log y :=
  let x := x, y := y in
  begin
    have s1 : log (x * y) = log (x * y), by reflexivity,
    have s2 : log (x * y) = log (exp (log x) * (exp (log y))), by { rw (exp_log_eq hx), rw (exp_log_eq hy) },
    have s3 : log (exp (log x) * (exp (log y))) = log (exp (log x + log y)), by { rw exp_add },
    have s4 : log (exp (log x + log y)) = log x + log y, by rw log_exp_eq,
    show log (x * y) = log x + log y, by rw [s1, s2, s3, s4],
  end

end chap4ex6

section chap4ex7

  #check sub_self

  example (x : ℤ) : x * 0 = 0 :=
  calc
    x * 0 = 0 * x : by rw [mul_comm]
      ... = (x - x) * x : by rw sub_self
      ... = (x * x) - (x * x) : by rw sub_mul
      ... = 0 : by rw sub_self

end chap4ex7

section chap5ex2

  example (p q r : Prop) (hp : p) :
  (p ∨ q ∨ r) ∧ (q ∨ p ∨ r) ∧ (q ∨ r ∨ p) :=
  by repeat { constructor, repeat { { left, exact hp } <|> { right, left, exact hp } <|> { right, right, exact hp } } }

end chap5ex2


import MyNat.Definition
import MyNat.Inequality -- le_iff_exists_add
import Mathlib.Tactic.Use -- use tactic
import AdditionWorld.Level6 -- add_right_comm
namespace MyNat
open MyNat
/-!

# Inequality world.

## Level 11: `add_le_add_right`

If you're faced with a goal of the form `forall t, ...`, then the next
line is "so let `t` be arbitrary". The way to do this in Lean is `intro t`.

## Lemma : add_le_add_right
For all naturals `a` and `b`, `a ≤ b` implies that for all naturals `t`,
`a+t ≤ b+t`.
-/
theorem add_le_add_right {a b : MyNat} : a ≤ b → ∀ t, (a + t) ≤ (b + t) := by
  intro h
  cases h with
  | _ c hc =>
    intro t
    use c
    rw [hc]
    rw [add_right_comm]

/-!
Next up [Level 12](./Level12.lean.md)
-/


import tactic.ring data.nat.basic data.nat.modeq tactic.linarith

/-- digit b n d is the d'th digit of n in base b
    (where the 0th digit is the units digit of n) -/
definition digit (b : ℕ) : ℕ → ℕ → ℕ
| n 0 := n % b
| n (e + 1) := digit (n / b) e

def dec_digit (n : ℕ) (d : ℕ) := digit 10 n d

-- n congruent mod b to 0th digit
lemma digit_zero (b n) :
digit b n 0 = n % b := rfl

-- d+1'st digit of n = d'th digit of n / b
lemma digit_succ (b n d : ℕ) :
digit b n (d + 1) = digit b (n / b) d := rfl

-- digits are all less than b.
lemma digit_lt_base (b : ℕ) (hb : b ≥ 1) (d : ℕ) :
∀ n, digit b n d < b :=
begin
  induction d with e He,
    -- base case
    intro n, exact nat.mod_lt n hb,
  -- inductive step
  intro n, exact He _
end

lemma irritating (M : ℕ) : 10 ^ (M + 1) - 1 = 10 * (10 ^ M - 1) + 9 :=
begin
  rw [nat.pow_succ,nat.mul_sub_left_distrib,mul_comm,mul_one],
  apply nat.sub_eq_of_eq_add,
  rw [add_comm,add_assoc],
  refine (nat.sub_add_cancel _).symm,
  suffices : 10 * 10 ^ M ≥ 10 * 1,
    rwa mul_one at this,
  apply nat.mul_le_mul_left,
  show 10 ^ M > 0,
  apply nat.pow_pos,
  exact dec_trivial
end

lemma all_nines_ends_in_nine (L : ℕ) (HL : L ≥ 1) : (10 ^ L - 1) % 10 = 9 :=
begin
  cases L with M, cases HL,
  rw [irritating,add_comm,nat.add_mul_mod_self_left],
  refl,
end

theorem zero_digit_sum (M m n : ℕ) (hmn : m + n = 10 ^ (M + 1) - 1) :
m % 10 + n % 10 = 9 :=
begin
  have m9 : dec_digit m 0 ≤ 9 := by unfold dec_digit; exact nat.le_of_lt_succ (digit_lt_base 10 (dec_trivial) 0 m),
  have n9 : dec_digit n 0 ≤ 9 := by unfold dec_digit; exact nat.le_of_lt_succ (digit_lt_base 10 (dec_trivial) 0 n),
  have mn : (dec_digit m 0 + dec_digit n 0) % 10 = 9,
    unfold dec_digit,rw digit_zero,rw digit_zero,
    rw [←all_nines_ends_in_nine (nat.succ M) (dec_trivial),←hmn],
    apply nat.modeq.modeq_add,
      exact nat.modeq.mod_modeq m 10,
      exact nat.modeq.mod_modeq n 10,
  have : dec_digit m 0 + dec_digit n 0 ≤ 18 := by linarith,
  generalize h2 : dec_digit m 0 + dec_digit n 0 = e,
  rw h2 at this mn,
  rw ←nat.mod_add_div e 10 at this,
  rw mn at this,
  suffices h3 : e / 10 = 0,
    rw ←nat.mod_add_div e 10,
    rw h3,
    rw mn,
    refl,
  replace this := nat.le_sub_left_of_add_le this,
  change _ ≤ 9 at this,
  generalize h4 : e / 10 = f,
  rw h4 at this,
  cases f with g,refl,exfalso,
  revert this,
  apply not_le_of_gt,
  change 10 * (g + 1) > 9,
  rw mul_add,
  exact nat.lt_add_left _ _ _ (dec_trivial),
end

theorem digit_sum (L m n : ℕ) (hmn : m + n = 10 ^ L - 1) :
∀ d, d < L → dec_digit m d + dec_digit n d = 9 :=
begin
  -- induction on length
  revert m n,
  induction L with M HM,
    -- base case empty
    intros m n hmn d Hd,cases Hd, -- no cases
  intros m n hmn d,
  induction d with e He,
    -- done base case already
    intro zzz, exact zero_digit_sum M m n hmn,
  -- succ
  intro Hem,
  unfold dec_digit, rw digit_succ,rw digit_succ,
  apply HM,
  { rw ←nat.mul_left_inj (show 10 > 0, from dec_trivial),
    rw [mul_add,nat.mul_sub_left_distrib,mul_one],
    rw [mul_comm _ (10 ^ M),←nat.pow_succ],
    apply @nat.add_right_cancel _ (m % 10 + n % 10),
    rw [←add_assoc,add_assoc _ _ (m % 10),add_comm _ (m % 10),←add_assoc (10 * (m / 10))],
    rw [add_comm _ (m % 10),nat.mod_add_div],
    rw [add_assoc,add_comm _ (n % 10),nat.mod_add_div],
    rw zero_digit_sum _ _ _ hmn,
    rw hmn,
    rw [irritating M,nat.mul_sub_left_distrib,mul_one,nat.pow_succ,mul_comm] },
  exact nat.lt_of_succ_lt_succ Hem
end


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

Some proofs and docs came from `algebra/commute` (c) Neil Strickland
-/
import algebra.group.units

/-!
# Semiconjugate elements of a semigroup

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

## Main definitions

We say that `x` is semiconjugate to `y` by `a` (`semiconj_by a x y`), if `a * x = y * a`.
In this file we  provide operations on `semiconj_by _ _ _`.

In the names of these operations, we treat `a` as the “left” argument, and both `x` and `y` as
“right” arguments. This way most names in this file agree with the names of the corresponding lemmas
for `commute a b = semiconj_by a b b`. As a side effect, some lemmas have only `_right` version.

Lean does not immediately recognise these terms as equations, so for rewriting we need syntax like
`rw [(h.pow_right 5).eq]` rather than just `rw [h.pow_right 5]`.

This file provides only basic operations (`mul_left`, `mul_right`, `inv_right` etc). Other
operations (`pow_right`, field inverse etc) are in the files that define corresponding notions.
-/

universes u v
variables {G : Type*}

/-- `x` is semiconjugate to `y` by `a`, if `a * x = y * a`. -/
@[to_additive add_semiconj_by "`x` is additive semiconjugate to `y` by `a` if `a + x = y + a`"]
def semiconj_by {M : Type u} [has_mul M] (a x y : M) : Prop := a * x = y * a

namespace semiconj_by

/-- Equality behind `semiconj_by a x y`; useful for rewriting. -/
@[to_additive "Equality behind `add_semiconj_by a x y`; useful for rewriting."]
protected lemma eq {S : Type u} [has_mul S] {a x y : S} (h : semiconj_by a x y) :
  a * x = y * a := h

section semigroup

variables {S : Type u} [semigroup S] {a b x y z x' y' : S}

/-- If `a` semiconjugates `x` to `y` and `x'` to `y'`,
then it semiconjugates `x * x'` to `y * y'`. -/
@[simp, to_additive "If `a` semiconjugates `x` to `y` and `x'` to `y'`, then it semiconjugates
`x + x'` to `y + y'`."]
lemma mul_right (h : semiconj_by a x y) (h' : semiconj_by a x' y') :
  semiconj_by a (x * x') (y * y') :=
by unfold semiconj_by; assoc_rw [h.eq, h'.eq]

/-- If both `a` and `b` semiconjugate `x` to `y`, then so does `a * b`. -/
@[to_additive "If both `a` and `b` semiconjugate `x` to `y`, then so does `a + b`."]
lemma mul_left (ha : semiconj_by a y z) (hb : semiconj_by b x y) : semiconj_by (a * b) x z :=
by unfold semiconj_by; assoc_rw [hb.eq, ha.eq, mul_assoc]

/-- The relation “there exists an element that semiconjugates `a` to `b`” on a semigroup
is transitive. -/
@[to_additive "The relation “there exists an element that semiconjugates `a` to `b`” on an additive
semigroup is transitive."]
protected lemma transitive : transitive (λ a b : S, ∃ c, semiconj_by c a b) :=
λ a b c ⟨x, hx⟩ ⟨y, hy⟩, ⟨y * x, hy.mul_left hx⟩

end semigroup

section mul_one_class

variables {M : Type u} [mul_one_class M]

/-- Any element semiconjugates `1` to `1`. -/
@[simp, to_additive "Any element additively semiconjugates `0` to `0`."]

/-- One semiconjugates any element to itself. -/
@[simp, to_additive "Zero additively semiconjugates any element to itself."]
lemma one_left (x : M) : semiconj_by 1 x x := eq.symm $ one_right x

/-- The relation “there exists an element that semiconjugates `a` to `b`” on a monoid (or, more
generally, on ` mul_one_class` type) is reflexive. -/
@[to_additive "The relation “there exists an element that semiconjugates `a` to `b`” on an additive
monoid (or, more generally, on a `add_zero_class` type) is reflexive."]
protected lemma reflexive : reflexive (λ a b : M, ∃ c, semiconj_by c a b) :=
λ a, ⟨1, one_left a⟩

end mul_one_class

section monoid

variables {M : Type u} [monoid M]

/-- If `a` semiconjugates a unit `x` to a unit `y`, then it semiconjugates `x⁻¹` to `y⁻¹`. -/
@[to_additive "If `a` semiconjugates an additive unit `x` to an additive unit `y`, then it
semiconjugates `-x` to `-y`."]
lemma units_inv_right {a : M} {x y : Mˣ} (h : semiconj_by a x y) : semiconj_by a ↑x⁻¹ ↑y⁻¹ :=
calc a * ↑x⁻¹ = ↑y⁻¹ * (y * a) * ↑x⁻¹ : by rw [units.inv_mul_cancel_left]
          ... = ↑y⁻¹ * a              : by rw [← h.eq, mul_assoc, units.mul_inv_cancel_right]

@[simp, to_additive] lemma units_inv_right_iff {a : M} {x y : Mˣ} :
  semiconj_by a ↑x⁻¹ ↑y⁻¹ ↔ semiconj_by a x y :=
⟨units_inv_right, units_inv_right⟩

/-- If a unit `a` semiconjugates `x` to `y`, then `a⁻¹` semiconjugates `y` to `x`. -/
@[to_additive "If an additive unit `a` semiconjugates `x` to `y`, then `-a` semiconjugates `y` to
`x`."]
lemma units_inv_symm_left {a : Mˣ} {x y : M} (h : semiconj_by ↑a x y) :
  semiconj_by ↑a⁻¹ y x :=
calc ↑a⁻¹ * y = ↑a⁻¹ * (y * a * ↑a⁻¹) : by rw [units.mul_inv_cancel_right]
          ... = x * ↑a⁻¹              : by rw [← h.eq, ← mul_assoc, units.inv_mul_cancel_left]

@[simp, to_additive] lemma units_inv_symm_left_iff {a : Mˣ} {x y : M} :
  semiconj_by ↑a⁻¹ y x ↔ semiconj_by ↑a x y :=
⟨units_inv_symm_left, units_inv_symm_left⟩

@[to_additive] theorem units_coe {a x y : Mˣ} (h : semiconj_by a x y) :
  semiconj_by (a : M) x y :=
congr_arg units.val h

@[to_additive] theorem units_of_coe {a x y : Mˣ} (h : semiconj_by (a : M) x y) :
  semiconj_by a x y :=
units.ext h

@[simp, to_additive] theorem units_coe_iff {a x y : Mˣ} :
  semiconj_by (a : M) x y ↔ semiconj_by a x y :=
⟨units_of_coe, units_coe⟩

@[simp, to_additive]
lemma pow_right {a x y : M} (h : semiconj_by a x y) (n : ℕ) : semiconj_by a (x^n) (y^n) :=
begin
  induction n with n ih,
  { rw [pow_zero, pow_zero], exact semiconj_by.one_right _ },
  { rw [pow_succ, pow_succ],
    exact h.mul_right ih }
end

end monoid

section division_monoid
variables [division_monoid G] {a x y : G}

@[simp, to_additive] lemma inv_inv_symm_iff : semiconj_by a⁻¹ x⁻¹ y⁻¹ ↔ semiconj_by a y x :=
inv_involutive.injective.eq_iff.symm.trans $ by simp_rw [mul_inv_rev, inv_inv, eq_comm, semiconj_by]

@[to_additive] lemma inv_inv_symm : semiconj_by a x y → semiconj_by a⁻¹ y⁻¹ x⁻¹ :=
inv_inv_symm_iff.2

end division_monoid

section group

variables [group G] {a x y : G}

@[simp, to_additive] lemma inv_right_iff : semiconj_by a x⁻¹ y⁻¹ ↔ semiconj_by a x y :=
@units_inv_right_iff G _ a ⟨x, x⁻¹, mul_inv_self x, inv_mul_self x⟩
  ⟨y, y⁻¹, mul_inv_self y, inv_mul_self y⟩

@[to_additive] lemma inv_right : semiconj_by a x y → semiconj_by a x⁻¹ y⁻¹ :=
inv_right_iff.2

@[simp, to_additive] lemma inv_symm_left_iff : semiconj_by a⁻¹ y x ↔ semiconj_by a x y :=
@units_inv_symm_left_iff G _ ⟨a, a⁻¹, mul_inv_self a, inv_mul_self a⟩ _ _

@[to_additive] lemma inv_symm_left : semiconj_by a x y → semiconj_by a⁻¹ y x :=
inv_symm_left_iff.2

/-- `a` semiconjugates `x` to `a * x * a⁻¹`. -/
@[to_additive "`a` semiconjugates `x` to `a + x + -a`."]
lemma conj_mk (a x : G) : semiconj_by a x (a * x * a⁻¹) :=
by unfold semiconj_by; rw [mul_assoc, inv_mul_self, mul_one]

end group

end semiconj_by

@[simp, to_additive add_semiconj_by_iff_eq]
lemma semiconj_by_iff_eq {M : Type u} [cancel_comm_monoid M] {a x y : M} :
  semiconj_by a x y ↔ x = y :=
⟨λ h, mul_left_cancel (h.trans (mul_comm _ _)), λ h, by rw [h, semiconj_by, mul_comm] ⟩

/-- `a` semiconjugates `x` to `a * x * a⁻¹`. -/
@[to_additive "`a` semiconjugates `x` to `a + x + -a`."]
lemma units.mk_semiconj_by {M : Type u} [monoid M] (u : Mˣ) (x : M) :
  semiconj_by ↑u x (u * x * ↑u⁻¹) :=
by unfold semiconj_by; rw [units.inv_mul_cancel_right]


import lib.m154

/-
Ce fichier concerne la définition de limite d'une suite (de nombres réels).
Une suite u est une fonction de ℕ dans ℝ, Lean écrit donc u : ℕ → ℝ
-/

-- Définition de « u tend vers l »
def limite_suite (u : ℕ → ℝ) (l : ℝ) : Prop :=
∀ ε > 0, ∃ N, ∀ n ≥ N, |u n - l| ≤ ε

/-
On notera dans la définition ci-dessus l'utilisation de « ∀ ε > 0, ... »
qui est une abbréviation de « ∀ ε, ε > 0 → ... ».

En particulier un énoncé de la forme « h : ∀ ε > 0, ... » se spécialise à
un ε₀ fixé par la commande « Par h on obtient ε₀ tel que hε₀ » où hε₀ est
une démonstration de ε₀ > 0.

Le lemme demi_pos ci-dessous sera utile pour transformer une démonstration
de ε > 0 en une démonstration de ε/2 > 0 lorsque l'on spécialise un énoncé
avec ε/2 au lieu de ε.

La démonstration n'est pas très éclairante car Lean fait le travail
automatiquement, mais c'est l'occasion de rappeler que la commande
`On conclut` accepte ce type de travail d'ajustement très direct.
-/

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

-- Dans toute la suite, u, v et w sont des suites tandis que l et l' sont des
-- nombres réels
variables (u v w : ℕ → ℝ) (l l' : ℝ)

-- Si u est constante de valeur l, alors u tend vers l
example : (∀ n, u n = l) → limite_suite u l :=
begin
  sorry
end

/- Concernant les valeurs absolues, on pourra utiliser les lemmes

`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|`

Il est conseillé de noter ces lemmes sur une feuille car ils
peuvent être utiles dans chaque exercice.
-/

-- Si u tend vers l strictement positif, alors u n ≥ l/2 pour n assez grand.
example (hl : l > 0) : limite_suite u l → ∃ N, ∀ n ≥ N, u n ≥ l/2 :=
begin
  sorry
end

/- Concernant le maximum de deux nombres, on pourra utiliser les lemmes

`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`

Il est conseillé de noter ces lemmes sur une feuille car ils
peuvent être utiles dans chaque exercice.

Dans l'exemple suivant, notez particulièrement la façon dont
`demi_pos` est utilisé : sachant que `ε` est fixé et qu'on a une
hypothèse `ε_pos : ε > 0`, on peut former l'expression
`demi_pos ε_pos : ε/2 > 0`.

Notez aussi l'utilisation de `superieur_max_ssi` qui reviendra
très souvent, et la façon d'annoncer à l'avance des inégalités
intermédiaire avant de la combiner par `On combine`.
-/

-- Si u tend vers l et v tend vers l' alors u+v tend vers l+l'
example (hu : limite_suite u l) (hv : limite_suite v l') :
limite_suite (u + v) (l + l') :=
begin
  Soit ε > 0,
  Par hu appliqué à [ε/2, demi_pos ε_pos] on obtient N₁
      tel que hN₁ : ∀ n ≥ N₁, |u n - l| ≤ ε / 2,
  Par hv appliqué à [ε/2, demi_pos ε_pos] on obtient N₂
      tel que hN₂ : ∀ n ≥ N₂, |v n - l'| ≤ ε / 2,
  Montrons que max N₁ N₂ convient : ∀ n ≥ max N₁ N₂, |(u + v) n - (l + l')| ≤ ε,
  Soit n ≥ max N₁ N₂,
  On réécrit via superieur_max_ssi dans n_ge,
  Par n_ge on obtient (hn₁ : n ≥ N₁) (hn₂ : n ≥ N₂),
  Fait fait₁ : |u n - l| ≤ ε/2,
    On applique hN₁,
  Fait fait₂ : |v n - l'| ≤ ε/2,
    On conclut par hN₂ appliqué à [n, hn₂],  -- Notez la variante Lean par rapport à fait₁
  calc
  |(u + v) n - (l + l')| = |(u n - l) + (v n - l')| : by On calcule
                     ... ≤ |u n - l| + |v n - l'| : by On applique ineg_triangle
                     ... ≤  ε/2 + ε/2             : by On combine [fait₁, fait₂]
                     ... =  ε                     : by On calcule,
end

example (hu : limite_suite u l) (hw : limite_suite w l)
(h : ∀ n, u n ≤ v n)
(h' : ∀ n, v n ≤ w n) : limite_suite v l :=
begin
  sorry

end

-- La dernière inégalité dans la définition de limite peut être remplacée par
-- une inégalité stricte.
example (u l) : limite_suite u l ↔
 ∀ ε > 0, ∃ N, ∀ n ≥ N, |u n - l| < ε :=
begin
  sorry
end

/- Dans l'exercice suivant, on pourra utiliser le lemme

`egal_si_abs_eps (x y : ℝ) : (∀ ε > 0, |x - y| ≤ ε) → x = y`
-/

-- Une suite u admet au plus une limite
example : limite_suite u l → limite_suite u l' → l = l' :=
begin
  sorry
end

-- Définition de « la suite u est croissante »
def croissante (u : ℕ → ℝ) := ∀ n m, n ≤ m → u n ≤ u m

-- Définition de « M est borne supérieure des termes de la suite u  »
def est_borne_sup (M : ℝ) (u : ℕ → ℝ) :=
(∀ n, u n ≤ M) ∧ ∀ ε > 0, ∃ n₀, u n₀ ≥ M - ε

-- Toute suite croissante ayant une borne supérieure tend vers cette borne
example (M : ℝ) (h : est_borne_sup M u) (h' : croissante u) :
limite_suite u M :=
begin
  sorry
end


/-
Copyright (c) 2019 Michael Howes. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Michael Howes

! This file was ported from Lean 3 source module group_theory.presented_group
! leanprover-community/mathlib commit d90e4e186f1d18e375dcd4e5b5f6364b01cb3e46
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.GroupTheory.FreeGroup
import Mathlib.GroupTheory.QuotientGroup

/-!
# Defining a group given by generators and relations

Given a subset `rels` of relations of the free group on a type `α`, this file constructs the group
given by generators `x : α` and relations `r ∈ rels`.

## Main definitions

* `PresentedGroup rels`: the quotient group of the free group on a type `α` by a subset `rels` of
  relations of the free group on `α`.
* `of`: The canonical map from `α` to a presented group with generators `α`.
* `toGroup f`: the canonical group homomorphism `PresentedGroup rels → G`, given a function
  `f : α → G` from a type `α` to a group `G` which satisfies the relations `rels`.

## Tags

generators, relations, group presentations
-/

variable {α : Type _}

/-- Given a set of relations, `rels`, over a type `α`, `PresentedGroup` constructs the group with
generators `x : α` and relations `rels` as a quotient of `FreeGroup α`. -/
def PresentedGroup (rels : Set (FreeGroup α)) :=
  FreeGroup α ⧸ Subgroup.normalClosure rels
#align presented_group PresentedGroup

namespace PresentedGroup

instance (rels : Set (FreeGroup α)) : Group (PresentedGroup rels) :=
  QuotientGroup.Quotient.group _

/-- `of` is the canonical map from `α` to a presented group with generators `x : α`. The term `x` is
mapped to the equivalence class of the image of `x` in `FreeGroup α`. -/
def of {rels : Set (FreeGroup α)} (x : α) : PresentedGroup rels :=
  QuotientGroup.mk (FreeGroup.of x)
#align presented_group.of PresentedGroup.of

section ToGroup

/-
Presented groups satisfy a universal property. If `G` is a group and `f : α → G` is a map such that
the images of `f` satisfy all the given relations, then `f` extends uniquely to a group homomorphism
from `PresentedGroup rels` to `G`.
-/
variable {G : Type _} [Group G] {f : α → G} {rels : Set (FreeGroup α)}

-- mathport name: exprF
local notation "F" => FreeGroup.lift f

-- Porting note: `F` has been expanded, because `F r = 1` produces a sorry.
variable (h : ∀ r ∈ rels, FreeGroup.lift f r = 1)

theorem closure_rels_subset_ker : Subgroup.normalClosure rels ≤ MonoidHom.ker F :=
  Subgroup.normalClosure_le_normal fun x w ↦ (MonoidHom.mem_ker _).2 (h x w)
#align presented_group.closure_rels_subset_ker PresentedGroup.closure_rels_subset_ker

theorem to_group_eq_one_of_mem_closure : ∀ x ∈ Subgroup.normalClosure rels, F x = 1 :=
  fun _ w ↦ (MonoidHom.mem_ker _).1 <| closure_rels_subset_ker h w
#align presented_group.to_group_eq_one_of_mem_closure PresentedGroup.to_group_eq_one_of_mem_closure

/-- The extension of a map `f : α → G` that satisfies the given relations to a group homomorphism
from `PresentedGroup rels → G`. -/
def toGroup : PresentedGroup rels →* G :=
  QuotientGroup.lift (Subgroup.normalClosure rels) F (to_group_eq_one_of_mem_closure h)
#align presented_group.to_group PresentedGroup.toGroup

@[simp]
theorem toGroup.of {x : α} : toGroup h (of x) = f x :=
  FreeGroup.lift.of
#align presented_group.to_group.of PresentedGroup.toGroup.of

theorem toGroup.unique (g : PresentedGroup rels →* G)
    (hg : ∀ x : α, g (PresentedGroup.of x) = f x) : ∀ {x}, g x = toGroup h x := by
  intro x
  refine' QuotientGroup.induction_on x _
  exact fun _ ↦ FreeGroup.lift.unique (g.comp (QuotientGroup.mk' _)) hg
#align presented_group.to_group.unique PresentedGroup.toGroup.unique

end ToGroup

instance (rels : Set (FreeGroup α)) : Inhabited (PresentedGroup rels) :=
  ⟨1⟩

end PresentedGroup


/-
Copyright (c) 2018 Chris Hughes. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Chris Hughes, Patrick Stevens

! This file was ported from Lean 3 source module data.nat.choose.dvd
! leanprover-community/mathlib commit 966e0cf0685c9cedf8a3283ac69eef4d5f2eaca2
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Data.Nat.Choose.Basic
import Mathlib.Data.Nat.Prime

/-!
# Divisibility properties of binomial coefficients
-/

namespace Nat

open Nat

namespace Prime

variable {p a b k : ℕ}

theorem dvd_choose_add (hp : Prime p) (hap : a < p) (hbp : b < p) (h : p ≤ a + b) :
    p ∣ choose (a + b) a := by
  have h₁ : p ∣ (a + b)! := hp.dvd_factorial.2 h
  rw [← add_choose_mul_factorial_mul_factorial, ← choose_symm_add, hp.dvd_mul, hp.dvd_mul,
    hp.dvd_factorial, hp.dvd_factorial] at h₁
  exact (h₁.resolve_right hbp.not_le).resolve_right hap.not_le
#align nat.prime.dvd_choose_add Nat.Prime.dvd_choose_add

lemma dvd_choose (hp : Prime p) (ha : a < p) (hab : b - a < p) (h : p ≤ b) : p ∣ choose b a :=
  have : a + (b - a) = b := Nat.add_sub_of_le (ha.le.trans h)
  this ▸ hp.dvd_choose_add ha hab (this.symm ▸ h)
#align nat.prime.dvd_choose Nat.Prime.dvd_choose

lemma dvd_choose_self (hp : Prime p) (hk : k ≠ 0) (hkp : k < p) : p ∣ choose p k :=
  hp.dvd_choose hkp (sub_lt ((zero_le _).trans_lt hkp) hk.bot_lt) le_rfl
#align nat.prime.dvd_choose_self Nat.Prime.dvd_choose_self

end Prime

end Nat


import algebra.big_operators
import analysis.specific_limits

-- Sum of a Geometric Series

open finset

theorem t066_finite {α} [division_ring α] {x : α} : Π (h : x ≠ 1) (n : ℕ),
  (range n).sum (λ i, x^i) = (x^n-1)/(x-1)
:= geom_sum

lemma t066_infinite {r : ℝ} : Π (h₁ : 0 ≤ r) (h₂ : r < 1),
  has_sum (λn:ℕ, r ^ n) (1 - r)⁻¹
:= has_sum_geometric