/-
Copyright (c) 2018 Guy Leroy. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sangwoo Jo (aka Jason), Guy Leroy, Johannes Hölzl, Mario Carneiro
-/
import data.nat.prime
/-!
# Extended GCD and divisibility over ℤ

## Main definitions

* Given `x y : ℕ`, `xgcd x y` computes the pair of integers `(a, b)` such that
  `gcd x y = x * a + y * b`. `gcd_a x y` and `gcd_b x y` are defined to be `a` and `b`,
  respectively.

## Main statements

* `gcd_eq_gcd_ab`: Bézout's lemma, given `x y : ℕ`, `gcd x y = x * gcd_a x y + y * gcd_b x y`.

## Tags

Bézout's lemma, Bezout's lemma
-/

/-! ### Extended Euclidean algorithm -/
namespace nat

/-- Helper function for the extended GCD algorithm (`nat.xgcd`). -/
def xgcd_aux : ℕ → ℤ → ℤ → ℕ → ℤ → ℤ → ℕ × ℤ × ℤ
| 0          s t r' s' t' := (r', s', t')
| r@(succ _) s t r' s' t' :=
  have r' % r < r, from mod_lt _ $ succ_pos _,
  let q := r' / r in xgcd_aux (r' % r) (s' - q * s) (t' - q * t) r s t

@[simp] theorem xgcd_zero_left {s t r' s' t'} : xgcd_aux 0 s t r' s' t' = (r', s', t') :=
by simp [xgcd_aux]

theorem xgcd_aux_rec {r s t r' s' t'} (h : 0 < r) :
  xgcd_aux r s t r' s' t' = xgcd_aux (r' % r) (s' - (r' / r) * s) (t' - (r' / r) * t) r s t :=
by cases r; [exact absurd h (lt_irrefl _), {simp only [xgcd_aux], refl}]

/-- Use the extended GCD algorithm to generate the `a` and `b` values
  satisfying `gcd x y = x * a + y * b`. -/
def xgcd (x y : ℕ) : ℤ × ℤ := (xgcd_aux x 1 0 y 0 1).2

/-- The extended GCD `a` value in the equation `gcd x y = x * a + y * b`. -/
def gcd_a (x y : ℕ) : ℤ := (xgcd x y).1

/-- The extended GCD `b` value in the equation `gcd x y = x * a + y * b`. -/
def gcd_b (x y : ℕ) : ℤ := (xgcd x y).2

@[simp] theorem gcd_a_zero_left {s : ℕ} : gcd_a 0 s = 0 :=
by { unfold gcd_a, rw [xgcd, xgcd_zero_left] }

@[simp] theorem gcd_b_zero_left {s : ℕ} : gcd_b 0 s = 1 :=
by { unfold gcd_b, rw [xgcd, xgcd_zero_left] }

@[simp] theorem gcd_a_zero_right {s : ℕ} (h : s ≠ 0) : gcd_a s 0 = 1 :=
begin
  unfold gcd_a xgcd,
  induction s,
  { exact absurd rfl h, },
  { simp [xgcd_aux], }
end

@[simp] theorem gcd_b_zero_right {s : ℕ} (h : s ≠ 0) : gcd_b s 0 = 0 :=
begin
  unfold gcd_b xgcd,
  induction s,
  { exact absurd rfl h, },
  { simp [xgcd_aux], }
end

@[simp] theorem xgcd_aux_fst (x y) : ∀ s t s' t',
  (xgcd_aux x s t y s' t').1 = gcd x y :=
gcd.induction x y (by simp) (λ x y h IH s t s' t', by simp [xgcd_aux_rec, h, IH]; rw ← gcd_rec)

theorem xgcd_aux_val (x y) : xgcd_aux x 1 0 y 0 1 = (gcd x y, xgcd x y) :=
by rw [xgcd, ← xgcd_aux_fst x y 1 0 0 1]; cases xgcd_aux x 1 0 y 0 1; refl

theorem xgcd_val (x y) : xgcd x y = (gcd_a x y, gcd_b x y) :=
by unfold gcd_a gcd_b; cases xgcd x y; refl

section
parameters (x y : ℕ)

private def P : ℕ × ℤ × ℤ → Prop
| (r, s, t) := (r : ℤ) = x * s + y * t

theorem xgcd_aux_P {r r'} : ∀ {s t s' t'}, P (r, s, t) → P (r', s', t') →
  P (xgcd_aux r s t r' s' t') :=
gcd.induction r r' (by simp) $ λ a b h IH s t s' t' p p', begin
  rw [xgcd_aux_rec h], refine IH _ p, dsimp [P] at *,
  rw [int.mod_def], generalize : (b / a : ℤ) = k,
  rw [p, p'],
  simp [mul_add, mul_comm, mul_left_comm, add_comm, add_left_comm, sub_eq_neg_add, mul_assoc]
end

/-- **Bézout's lemma**: given `x y : ℕ`, `gcd x y = x * a + y * b`, where `a = gcd_a x y` and
`b = gcd_b x y` are computed by the extended Euclidean algorithm.
-/
theorem gcd_eq_gcd_ab : (gcd x y : ℤ) = x * gcd_a x y + y * gcd_b x y :=
by have := @xgcd_aux_P x y x y 1 0 0 1 (by simp [P]) (by simp [P]);
   rwa [xgcd_aux_val, xgcd_val] at this
end

lemma exists_mul_mod_eq_gcd {k n : ℕ} (hk : gcd n k < k) :
  ∃ m, n * m % k = gcd n k :=
begin
  have hk' := int.coe_nat_ne_zero.mpr (ne_of_gt (lt_of_le_of_lt (zero_le (gcd n k)) hk)),
  have key := congr_arg (λ m, int.nat_mod m k) (gcd_eq_gcd_ab n k),
  simp_rw int.nat_mod at key,
  rw [int.add_mul_mod_self_left, ←int.coe_nat_mod, int.to_nat_coe_nat, mod_eq_of_lt hk] at key,
  refine ⟨(n.gcd_a k % k).to_nat, eq.trans (int.coe_nat_inj _) key.symm⟩,
  rw [int.coe_nat_mod, int.coe_nat_mul, int.to_nat_of_nonneg (int.mod_nonneg _ hk'),
      int.to_nat_of_nonneg (int.mod_nonneg _ hk'), int.mul_mod, int.mod_mod, ←int.mul_mod],
end

lemma exists_mul_mod_eq_one_of_coprime {k n : ℕ} (hkn : coprime n k) (hk : 1 < k) :
  ∃ m, n * m % k = 1 :=
Exists.cases_on (exists_mul_mod_eq_gcd (lt_of_le_of_lt (le_of_eq hkn) hk))
  (λ m hm, ⟨m, hm.trans hkn⟩)

end nat

/-! ### Divisibility over ℤ -/
namespace int

protected lemma coe_nat_gcd (m n : ℕ) : int.gcd ↑m ↑n = nat.gcd m n := rfl

/-- The extended GCD `a` value in the equation `gcd x y = x * a + y * b`. -/
def gcd_a : ℤ → ℤ → ℤ
| (of_nat m) n := m.gcd_a n.nat_abs
| -[1+ m]    n := -m.succ.gcd_a n.nat_abs

/-- The extended GCD `b` value in the equation `gcd x y = x * a + y * b`. -/
def gcd_b : ℤ → ℤ → ℤ
| m (of_nat n) := m.nat_abs.gcd_b n
| m -[1+ n]    := -m.nat_abs.gcd_b n.succ

/-- **Bézout's lemma** -/
theorem gcd_eq_gcd_ab : ∀ x y : ℤ, (gcd x y : ℤ) = x * gcd_a x y + y * gcd_b x y
| (m : ℕ) (n : ℕ) := nat.gcd_eq_gcd_ab _ _
| (m : ℕ) -[1+ n] := show (_ : ℤ) = _ + -(n+1) * -_, by rw neg_mul_neg; apply nat.gcd_eq_gcd_ab
| -[1+ m] (n : ℕ) := show (_ : ℤ) = -(m+1) * -_ + _ , by rw neg_mul_neg; apply nat.gcd_eq_gcd_ab
| -[1+ m] -[1+ n] := show (_ : ℤ) = -(m+1) * -_ + -(n+1) * -_,
  by { rw [neg_mul_neg, neg_mul_neg], apply nat.gcd_eq_gcd_ab }

theorem nat_abs_div (a b : ℤ) (H : b ∣ a) : nat_abs (a / b) = (nat_abs a) / (nat_abs b) :=
begin
  cases (nat.eq_zero_or_pos (nat_abs b)),
  {rw eq_zero_of_nat_abs_eq_zero h, simp [int.div_zero]},
  calc
  nat_abs (a / b) = nat_abs (a / b) * 1 : by rw mul_one
    ... = nat_abs (a / b) * (nat_abs b / nat_abs b) : by rw nat.div_self h
    ... = nat_abs (a / b) * nat_abs b / nat_abs b : by rw (nat.mul_div_assoc _ dvd_rfl)
    ... = nat_abs (a / b * b) / nat_abs b : by rw (nat_abs_mul (a / b) b)
    ... = nat_abs a / nat_abs b : by rw int.div_mul_cancel H,
end

lemma succ_dvd_or_succ_dvd_of_succ_sum_dvd_mul {p : ℕ} (p_prime : nat.prime p) {m n : ℤ} {k l : ℕ}
      (hpm : ↑(p ^ k) ∣ m)
      (hpn : ↑(p ^ l) ∣ n) (hpmn : ↑(p ^ (k+l+1)) ∣ m*n) : ↑(p ^ (k+1)) ∣ m ∨ ↑(p ^ (l+1)) ∣ n :=
have hpm' : p ^ k ∣ m.nat_abs, from int.coe_nat_dvd.1 $ int.dvd_nat_abs.2 hpm,
have hpn' : p ^ l ∣ n.nat_abs, from int.coe_nat_dvd.1 $ int.dvd_nat_abs.2 hpn,
have hpmn' : (p ^ (k+l+1)) ∣ m.nat_abs*n.nat_abs,
  by rw ←int.nat_abs_mul; apply (int.coe_nat_dvd.1 $ int.dvd_nat_abs.2 hpmn),
let hsd := nat.succ_dvd_or_succ_dvd_of_succ_sum_dvd_mul p_prime hpm' hpn' hpmn' in
hsd.elim
  (λ hsd1, or.inl begin apply int.dvd_nat_abs.1, apply int.coe_nat_dvd.2 hsd1 end)
  (λ hsd2, or.inr begin apply int.dvd_nat_abs.1, apply int.coe_nat_dvd.2 hsd2 end)

theorem dvd_of_mul_dvd_mul_left {i j k : ℤ} (k_non_zero : k ≠ 0) (H : k * i ∣ k * j) : i ∣ j :=
dvd.elim H (λl H1, by rw mul_assoc at H1; exact ⟨_, mul_left_cancel₀ k_non_zero H1⟩)

theorem dvd_of_mul_dvd_mul_right {i j k : ℤ} (k_non_zero : k ≠ 0) (H : i * k ∣ j * k) : i ∣ j :=
by rw [mul_comm i k, mul_comm j k] at H; exact dvd_of_mul_dvd_mul_left k_non_zero H

lemma prime.dvd_nat_abs_of_coe_dvd_sq {p : ℕ} (hp : p.prime) (k : ℤ) (h : ↑p ∣ k ^ 2) :
  p ∣ k.nat_abs :=
begin
  apply @nat.prime.dvd_of_dvd_pow _ _ 2 hp,
  rwa [sq, ← nat_abs_mul, ← coe_nat_dvd_left, ← sq]
end

/-- ℤ specific version of least common multiple. -/
def lcm (i j : ℤ) : ℕ := nat.lcm (nat_abs i) (nat_abs j)

theorem lcm_def (i j : ℤ) : lcm i j = nat.lcm (nat_abs i) (nat_abs j) := rfl

protected lemma coe_nat_lcm (m n : ℕ) : int.lcm ↑m ↑n = nat.lcm m n := rfl

theorem gcd_dvd_left (i j : ℤ) : (gcd i j : ℤ) ∣ i :=
dvd_nat_abs.mp $ coe_nat_dvd.mpr $ nat.gcd_dvd_left _ _

theorem gcd_dvd_right (i j : ℤ) : (gcd i j : ℤ) ∣ j :=
dvd_nat_abs.mp $ coe_nat_dvd.mpr $ nat.gcd_dvd_right _ _

theorem dvd_gcd {i j k : ℤ} (h1 : k ∣ i) (h2 : k ∣ j) : k ∣ gcd i j :=
nat_abs_dvd.1 $ coe_nat_dvd.2 $ nat.dvd_gcd (nat_abs_dvd_iff_dvd.2 h1) (nat_abs_dvd_iff_dvd.2 h2)

theorem gcd_mul_lcm (i j : ℤ) : gcd i j * lcm i j = nat_abs (i * j) :=
by rw [int.gcd, int.lcm, nat.gcd_mul_lcm, nat_abs_mul]

theorem gcd_comm (i j : ℤ) : gcd i j = gcd j i := nat.gcd_comm _ _

theorem gcd_assoc (i j k : ℤ) : gcd (gcd i j) k = gcd i (gcd j k) := nat.gcd_assoc _ _ _

@[simp] theorem gcd_self (i : ℤ) : gcd i i = nat_abs i := by simp [gcd]

@[simp] theorem gcd_zero_left (i : ℤ) : gcd 0 i = nat_abs i := by simp [gcd]

@[simp] theorem gcd_zero_right (i : ℤ) : gcd i 0 = nat_abs i := by simp [gcd]

@[simp] theorem gcd_one_left (i : ℤ) : gcd 1 i = 1 := nat.gcd_one_left _

@[simp] theorem gcd_one_right (i : ℤ) : gcd i 1 = 1 := nat.gcd_one_right _

theorem gcd_mul_left (i j k : ℤ) : gcd (i * j) (i * k) = nat_abs i * gcd j k :=
by { rw [int.gcd, int.gcd, nat_abs_mul, nat_abs_mul], apply nat.gcd_mul_left }

theorem gcd_mul_right (i j k : ℤ) : gcd (i * j) (k * j) = gcd i k * nat_abs j :=
by { rw [int.gcd, int.gcd, nat_abs_mul, nat_abs_mul], apply nat.gcd_mul_right }

theorem gcd_pos_of_non_zero_left {i : ℤ} (j : ℤ) (i_non_zero : i ≠ 0) : 0 < gcd i j :=
nat.gcd_pos_of_pos_left (nat_abs j) (nat_abs_pos_of_ne_zero i_non_zero)

theorem gcd_pos_of_non_zero_right (i : ℤ) {j : ℤ} (j_non_zero : j ≠ 0) : 0 < gcd i j :=
nat.gcd_pos_of_pos_right (nat_abs i) (nat_abs_pos_of_ne_zero j_non_zero)

theorem gcd_eq_zero_iff {i j : ℤ} : gcd i j = 0 ↔ i = 0 ∧ j = 0 :=
begin
  rw int.gcd,
  split,
  { intro h,
    exact ⟨nat_abs_eq_zero.mp (nat.eq_zero_of_gcd_eq_zero_left h),
      nat_abs_eq_zero.mp (nat.eq_zero_of_gcd_eq_zero_right h)⟩ },
  { intro h, rw [nat_abs_eq_zero.mpr h.left, nat_abs_eq_zero.mpr h.right],
    apply nat.gcd_zero_left }
end

theorem gcd_div {i j k : ℤ} (H1 : k ∣ i) (H2 : k ∣ j) :
  gcd (i / k) (j / k) = gcd i j / nat_abs k :=
by rw [gcd, nat_abs_div i k H1, nat_abs_div j k H2];
exact nat.gcd_div (nat_abs_dvd_iff_dvd.mpr H1) (nat_abs_dvd_iff_dvd.mpr H2)

theorem gcd_div_gcd_div_gcd {i j : ℤ} (H : 0 < gcd i j) :
  gcd (i / gcd i j) (j / gcd i j) = 1 :=
begin
  rw [gcd_div (gcd_dvd_left i j) (gcd_dvd_right i j)],
  rw [nat_abs_of_nat, nat.div_self H]
end

theorem gcd_dvd_gcd_of_dvd_left {i k : ℤ} (j : ℤ) (H : i ∣ k) : gcd i j ∣ gcd k j :=
int.coe_nat_dvd.1 $ dvd_gcd ((gcd_dvd_left i j).trans H) (gcd_dvd_right i j)

theorem gcd_dvd_gcd_of_dvd_right {i k : ℤ} (j : ℤ) (H : i ∣ k) : gcd j i ∣ gcd j k :=
int.coe_nat_dvd.1 $ dvd_gcd (gcd_dvd_left j i) ((gcd_dvd_right j i).trans H)

theorem gcd_dvd_gcd_mul_left (i j k : ℤ) : gcd i j ∣ gcd (k * i) j :=
gcd_dvd_gcd_of_dvd_left _ (dvd_mul_left _ _)

theorem gcd_dvd_gcd_mul_right (i j k : ℤ) : gcd i j ∣ gcd (i * k) j :=
gcd_dvd_gcd_of_dvd_left _ (dvd_mul_right _ _)

theorem gcd_dvd_gcd_mul_left_right (i j k : ℤ) : gcd i j ∣ gcd i (k * j) :=
gcd_dvd_gcd_of_dvd_right _ (dvd_mul_left _ _)

theorem gcd_dvd_gcd_mul_right_right (i j k : ℤ) : gcd i j ∣ gcd i (j * k) :=
gcd_dvd_gcd_of_dvd_right _ (dvd_mul_right _ _)

theorem gcd_eq_left {i j : ℤ} (H : i ∣ j) : gcd i j = nat_abs i :=
nat.dvd_antisymm (by unfold gcd; exact nat.gcd_dvd_left _ _)
                 (by unfold gcd; exact nat.dvd_gcd dvd_rfl (nat_abs_dvd_iff_dvd.mpr H))

theorem gcd_eq_right {i j : ℤ} (H : j ∣ i) : gcd i j = nat_abs j :=
by rw [gcd_comm, gcd_eq_left H]

theorem ne_zero_of_gcd {x y : ℤ}
  (hc : gcd x y ≠ 0) : x ≠ 0 ∨ y ≠ 0 :=
begin
  contrapose! hc,
  rw [hc.left, hc.right, gcd_zero_right, nat_abs_zero]
end

theorem exists_gcd_one {m n : ℤ} (H : 0 < gcd m n) :
  ∃ (m' n' : ℤ), gcd m' n' = 1 ∧ m = m' * gcd m n ∧ n = n' * gcd m n :=
⟨_, _, gcd_div_gcd_div_gcd H,
  (int.div_mul_cancel (gcd_dvd_left m n)).symm,
  (int.div_mul_cancel (gcd_dvd_right m n)).symm⟩

theorem exists_gcd_one' {m n : ℤ} (H : 0 < gcd m n) :
  ∃ (g : ℕ) (m' n' : ℤ), 0 < g ∧ gcd m' n' = 1 ∧ m = m' * g ∧ n = n' * g :=
let ⟨m', n', h⟩ := exists_gcd_one H in ⟨_, m', n', H, h⟩

theorem pow_dvd_pow_iff {m n : ℤ} {k : ℕ} (k0 : 0 < k) : m ^ k ∣ n ^ k ↔ m ∣ n :=
begin
  refine ⟨λ h, _, λ h, pow_dvd_pow_of_dvd h _⟩,
  apply int.nat_abs_dvd_iff_dvd.mp,
  apply (nat.pow_dvd_pow_iff k0).mp,
  rw [← int.nat_abs_pow, ← int.nat_abs_pow],
  exact int.nat_abs_dvd_iff_dvd.mpr h
end

/-! ### lcm -/

theorem lcm_comm (i j : ℤ) : lcm i j = lcm j i :=
by { rw [int.lcm, int.lcm], exact nat.lcm_comm _ _ }

theorem lcm_assoc (i j k : ℤ) : lcm (lcm i j) k = lcm i (lcm j k) :=
by { rw [int.lcm, int.lcm, int.lcm, int.lcm, nat_abs_of_nat, nat_abs_of_nat], apply nat.lcm_assoc }

@[simp] theorem lcm_zero_left (i : ℤ) : lcm 0 i = 0 :=
by { rw [int.lcm], apply nat.lcm_zero_left }

@[simp] theorem lcm_zero_right (i : ℤ) : lcm i 0 = 0 :=
by { rw [int.lcm], apply nat.lcm_zero_right }

@[simp] theorem lcm_one_left (i : ℤ) : lcm 1 i = nat_abs i :=
by { rw int.lcm, apply nat.lcm_one_left }

@[simp] theorem lcm_one_right (i : ℤ) : lcm i 1 = nat_abs i :=
by { rw int.lcm, apply nat.lcm_one_right }

@[simp] theorem lcm_self (i : ℤ) : lcm i i = nat_abs i :=
by { rw int.lcm, apply nat.lcm_self }

theorem dvd_lcm_left (i j : ℤ) : i ∣ lcm i j :=
by { rw int.lcm, apply coe_nat_dvd_right.mpr, apply nat.dvd_lcm_left }

theorem dvd_lcm_right (i j : ℤ) : j ∣ lcm i j :=
by { rw int.lcm, apply coe_nat_dvd_right.mpr, apply nat.dvd_lcm_right }

theorem lcm_dvd {i j k : ℤ}  : i ∣ k → j ∣ k → (lcm i j : ℤ) ∣ k :=
begin
  rw int.lcm,
  intros hi hj,
  exact coe_nat_dvd_left.mpr
    (nat.lcm_dvd (nat_abs_dvd_iff_dvd.mpr hi) (nat_abs_dvd_iff_dvd.mpr hj))
end

end int

lemma pow_gcd_eq_one {M : Type*} [monoid M] (x : M) {m n : ℕ} (hm : x ^ m = 1) (hn : x ^ n = 1) :
  x ^ m.gcd n = 1 :=
begin
  cases m, { simp only [hn, nat.gcd_zero_left] },
  obtain ⟨x, rfl⟩ : is_unit x,
  { apply is_unit_of_pow_eq_one _ _ hm m.succ_pos },
  simp only [← units.coe_pow] at *,
  rw [← units.coe_one, ← zpow_coe_nat, ← units.ext_iff] at *,
  simp only [nat.gcd_eq_gcd_ab, zpow_add, zpow_mul, hm, hn, one_zpow, one_mul]
end

lemma gcd_nsmul_eq_zero {M : Type*} [add_monoid M] (x : M) {m n : ℕ} (hm : m • x = 0)
  (hn : n • x = 0) : (m.gcd n) • x = 0 :=
begin
  apply multiplicative.of_add.injective,
  rw [of_add_nsmul, of_add_zero, pow_gcd_eq_one];
  rwa [←of_add_nsmul, ←of_add_zero, equiv.apply_eq_iff_eq]
end

attribute [to_additive gcd_nsmul_eq_zero] pow_gcd_eq_one

/-! ### GCD prover -/
open norm_num

namespace tactic
namespace norm_num

lemma int_gcd_helper' {d : ℕ} {x y a b : ℤ} (h₁ : (d:ℤ) ∣ x) (h₂ : (d:ℤ) ∣ y)
  (h₃ : x * a + y * b = d) : int.gcd x y = d :=
begin
  refine nat.dvd_antisymm _ (int.coe_nat_dvd.1 (int.dvd_gcd h₁ h₂)),
  rw [← int.coe_nat_dvd, ← h₃],
  apply dvd_add,
  { exact (int.gcd_dvd_left _ _).mul_right _ },
  { exact (int.gcd_dvd_right _ _).mul_right _ }
end

lemma nat_gcd_helper_dvd_left (x y a : ℕ) (h : x * a = y) : nat.gcd x y = x :=
nat.gcd_eq_left ⟨a, h.symm⟩

lemma nat_gcd_helper_dvd_right (x y a : ℕ) (h : y * a = x) : nat.gcd x y = y :=
nat.gcd_eq_right ⟨a, h.symm⟩

lemma nat_gcd_helper_2 (d x y a b u v tx ty : ℕ) (hu : d * u = x) (hv : d * v = y)
  (hx : x * a = tx) (hy : y * b = ty) (h : ty + d = tx) : nat.gcd x y = d :=
begin
  rw ← int.coe_nat_gcd, apply @int_gcd_helper' _ _ _ a (-b)
    (int.coe_nat_dvd.2 ⟨_, hu.symm⟩) (int.coe_nat_dvd.2 ⟨_, hv.symm⟩),
  rw [mul_neg_eq_neg_mul_symm, ← sub_eq_add_neg, sub_eq_iff_eq_add'],
  norm_cast, rw [hx, hy, h]
end

lemma nat_gcd_helper_1 (d x y a b u v tx ty : ℕ) (hu : d * u = x) (hv : d * v = y)
  (hx : x * a = tx) (hy : y * b = ty) (h : tx + d = ty) : nat.gcd x y = d :=
(nat.gcd_comm _ _).trans $ nat_gcd_helper_2 _ _ _ _ _ _ _ _ _ hv hu hy hx h

lemma nat_lcm_helper (x y d m n : ℕ) (hd : nat.gcd x y = d) (d0 : 0 < d)
  (xy : x * y = n) (dm : d * m = n) : nat.lcm x y = m :=
(nat.mul_right_inj d0).1 $ by rw [dm, ← xy, ← hd, nat.gcd_mul_lcm]

lemma nat_coprime_helper_zero_left (x : ℕ) (h : 1 < x) : ¬ nat.coprime 0 x :=
mt (nat.coprime_zero_left _).1 $ ne_of_gt h

lemma nat_coprime_helper_zero_right (x : ℕ) (h : 1 < x) : ¬ nat.coprime x 0 :=
mt (nat.coprime_zero_right _).1 $ ne_of_gt h

lemma nat_coprime_helper_2 (x y a b tx ty : ℕ)
  (hx : x * a = tx) (hy : y * b = ty) (h : ty + 1 = tx) : nat.coprime x y :=
nat_gcd_helper_2 _ _ _ _ _ _ _ _ _ (one_mul _) (one_mul _) hx hy h

lemma nat_not_coprime_helper (d x y u v : ℕ) (hu : d * u = x) (hv : d * v = y)
  (h : 1 < d) : ¬ nat.coprime x y :=
nat.not_coprime_of_dvd_of_dvd h ⟨_, hu.symm⟩ ⟨_, hv.symm⟩

lemma int_gcd_helper (x y : ℤ) (nx ny d : ℕ) (hx : (nx:ℤ) = x) (hy : (ny:ℤ) = y)
  (h : nat.gcd nx ny = d) : int.gcd x y = d :=
by rwa [← hx, ← hy, int.coe_nat_gcd]

lemma int_gcd_helper_neg_left (x y : ℤ) (d : ℕ) (h : int.gcd x y = d) : int.gcd (-x) y = d :=
by rw int.gcd at h ⊢; rwa int.nat_abs_neg

lemma int_gcd_helper_neg_right (x y : ℤ) (d : ℕ) (h : int.gcd x y = d) : int.gcd x (-y) = d :=
by rw int.gcd at h ⊢; rwa int.nat_abs_neg

lemma int_lcm_helper (x y : ℤ) (nx ny d : ℕ) (hx : (nx:ℤ) = x) (hy : (ny:ℤ) = y)
  (h : nat.lcm nx ny = d) : int.lcm x y = d :=
by rwa [← hx, ← hy, int.coe_nat_lcm]

lemma int_lcm_helper_neg_left (x y : ℤ) (d : ℕ) (h : int.lcm x y = d) : int.lcm (-x) y = d :=
by rw int.lcm at h ⊢; rwa int.nat_abs_neg

lemma int_lcm_helper_neg_right (x y : ℤ) (d : ℕ) (h : int.lcm x y = d) : int.lcm x (-y) = d :=
by rw int.lcm at h ⊢; rwa int.nat_abs_neg

/-- Evaluates the `nat.gcd` function. -/
meta def prove_gcd_nat (c : instance_cache) (ex ey : expr) :
  tactic (instance_cache × expr × expr) := do
  x ← ex.to_nat,
  y ← ey.to_nat,
  match x, y with
  | 0, _ := pure (c, ey, `(nat.gcd_zero_left).mk_app [ey])
  | _, 0 := pure (c, ex, `(nat.gcd_zero_right).mk_app [ex])
  | 1, _ := pure (c, `(1:ℕ), `(nat.gcd_one_left).mk_app [ey])
  | _, 1 := pure (c, `(1:ℕ), `(nat.gcd_one_right).mk_app [ex])
  | _, _ := do
    let (d, a, b) := nat.xgcd_aux x 1 0 y 0 1,
    if d = x then do
      (c, ea) ← c.of_nat (y / x),
      (c, _, p) ← prove_mul_nat c ex ea,
      pure (c, ex, `(nat_gcd_helper_dvd_left).mk_app [ex, ey, ea, p])
    else if d = y then do
      (c, ea) ← c.of_nat (x / y),
      (c, _, p) ← prove_mul_nat c ey ea,
      pure (c, ey, `(nat_gcd_helper_dvd_right).mk_app [ex, ey, ea, p])
    else do
      (c, ed) ← c.of_nat d,
      (c, ea) ← c.of_nat a.nat_abs,
      (c, eb) ← c.of_nat b.nat_abs,
      (c, eu) ← c.of_nat (x / d),
      (c, ev) ← c.of_nat (y / d),
      (c, _, pu) ← prove_mul_nat c ed eu,
      (c, _, pv) ← prove_mul_nat c ed ev,
      (c, etx, px) ← prove_mul_nat c ex ea,
      (c, ety, py) ← prove_mul_nat c ey eb,
      (c, p) ← if a ≥ 0 then prove_add_nat c ety ed etx else prove_add_nat c etx ed ety,
      let pf : expr := if a ≥ 0 then `(nat_gcd_helper_2) else `(nat_gcd_helper_1),
      pure (c, ed, pf.mk_app [ed, ex, ey, ea, eb, eu, ev, etx, ety, pu, pv, px, py, p])
  end

/-- Evaluates the `nat.lcm` function. -/
meta def prove_lcm_nat (c : instance_cache) (ex ey : expr) :
  tactic (instance_cache × expr × expr) := do
  x ← ex.to_nat,
  y ← ey.to_nat,
  match x, y with
  | 0, _ := pure (c, `(0:ℕ), `(nat.lcm_zero_left).mk_app [ey])
  | _, 0 := pure (c, `(0:ℕ), `(nat.lcm_zero_right).mk_app [ex])
  | 1, _ := pure (c, ey, `(nat.lcm_one_left).mk_app [ey])
  | _, 1 := pure (c, ex, `(nat.lcm_one_right).mk_app [ex])
  | _, _ := do
    (c, ed, pd) ← prove_gcd_nat c ex ey,
    (c, p0) ← prove_pos c ed,
    (c, en, xy) ← prove_mul_nat c ex ey,
    d ← ed.to_nat,
    (c, em) ← c.of_nat ((x * y) / d),
    (c, _, dm) ← prove_mul_nat c ed em,
    pure (c, em, `(nat_lcm_helper).mk_app [ex, ey, ed, em, en, pd, p0, xy, dm])
  end

/-- Evaluates the `int.gcd` function. -/
meta def prove_gcd_int (zc nc : instance_cache) : expr → expr →
  tactic (instance_cache × instance_cache × expr × expr)
| x y := match match_neg x with
  | some x := do
    (zc, nc, d, p) ← prove_gcd_int x y,
    pure (zc, nc, d, `(int_gcd_helper_neg_left).mk_app [x, y, d, p])
  | none := match match_neg y with
    | some y := do
      (zc, nc, d, p) ← prove_gcd_int x y,
      pure (zc, nc, d, `(int_gcd_helper_neg_right).mk_app [x, y, d, p])
    | none := do
      (zc, nc, nx, px) ← prove_nat_uncast zc nc x,
      (zc, nc, ny, py) ← prove_nat_uncast zc nc y,
      (nc, d, p) ← prove_gcd_nat nc nx ny,
      pure (zc, nc, d, `(int_gcd_helper).mk_app [x, y, nx, ny, d, px, py, p])
    end
  end

/-- Evaluates the `int.lcm` function. -/
meta def prove_lcm_int (zc nc : instance_cache) : expr → expr →
  tactic (instance_cache × instance_cache × expr × expr)
| x y := match match_neg x with
  | some x := do
    (zc, nc, d, p) ← prove_lcm_int x y,
    pure (zc, nc, d, `(int_lcm_helper_neg_left).mk_app [x, y, d, p])
  | none := match match_neg y with
    | some y := do
      (zc, nc, d, p) ← prove_lcm_int x y,
      pure (zc, nc, d, `(int_lcm_helper_neg_right).mk_app [x, y, d, p])
    | none := do
      (zc, nc, nx, px) ← prove_nat_uncast zc nc x,
      (zc, nc, ny, py) ← prove_nat_uncast zc nc y,
      (nc, d, p) ← prove_lcm_nat nc nx ny,
      pure (zc, nc, d, `(int_lcm_helper).mk_app [x, y, nx, ny, d, px, py, p])
    end
  end

/-- Evaluates the `nat.coprime` function. -/
meta def prove_coprime_nat (c : instance_cache) (ex ey : expr) :
  tactic (instance_cache × (expr ⊕ expr)) := do
  x ← ex.to_nat,
  y ← ey.to_nat,
  match x, y with
  | 1, _ := pure (c, sum.inl $ `(nat.coprime_one_left).mk_app [ey])
  | _, 1 := pure (c, sum.inl $ `(nat.coprime_one_right).mk_app [ex])
  | 0, 0 := pure (c, sum.inr `(nat.not_coprime_zero_zero))
  | 0, _ := do
    c ← mk_instance_cache `(ℕ),
    (c, p) ← prove_lt_nat c `(1) ey,
    pure (c, sum.inr $ `(nat_coprime_helper_zero_left).mk_app [ey, p])
  | _, 0 := do
    c ← mk_instance_cache `(ℕ),
    (c, p) ← prove_lt_nat c `(1) ex,
    pure (c, sum.inr $ `(nat_coprime_helper_zero_right).mk_app [ex, p])
  | _, _ := do
    c ← mk_instance_cache `(ℕ),
    let (d, a, b) := nat.xgcd_aux x 1 0 y 0 1,
    if d = 1 then do
      (c, ea) ← c.of_nat a.nat_abs,
      (c, eb) ← c.of_nat b.nat_abs,
      (c, etx, px) ← prove_mul_nat c ex ea,
      (c, ety, py) ← prove_mul_nat c ey eb,
      (c, p) ← if a ≥ 0 then prove_add_nat c ety `(1) etx else prove_add_nat c etx `(1) ety,
      let pf : expr := if a ≥ 0 then `(nat_coprime_helper_2) else `(nat_coprime_helper_1),
      pure (c, sum.inl $ pf.mk_app [ex, ey, ea, eb, etx, ety, px, py, p])
    else do
      (c, ed) ← c.of_nat d,
      (c, eu) ← c.of_nat (x / d),
      (c, ev) ← c.of_nat (y / d),
      (c, _, pu) ← prove_mul_nat c ed eu,
      (c, _, pv) ← prove_mul_nat c ed ev,
      (c, p) ← prove_lt_nat c `(1) ed,
      pure (c, sum.inr $ `(nat_not_coprime_helper).mk_app [ed, ex, ey, eu, ev, pu, pv, p])
  end

/-- Evaluates the `gcd`, `lcm`, and `coprime` functions. -/
@[norm_num] meta def eval_gcd : expr → tactic (expr × expr)
| `(nat.gcd %%ex %%ey) := do
    c ← mk_instance_cache `(ℕ),
    prod.snd <$> prove_gcd_nat c ex ey
| `(nat.lcm %%ex %%ey) := do
    c ← mk_instance_cache `(ℕ),
    prod.snd <$> prove_lcm_nat c ex ey
| `(nat.coprime %%ex %%ey) := do
    c ← mk_instance_cache `(ℕ),
    prove_coprime_nat c ex ey >>= sum.elim true_intro false_intro ∘ prod.snd
| `(int.gcd %%ex %%ey) := do
    zc ← mk_instance_cache `(ℤ),
    nc ← mk_instance_cache `(ℕ),
    (prod.snd ∘ prod.snd) <$> prove_gcd_int zc nc ex ey
| `(int.lcm %%ex %%ey) := do
    zc ← mk_instance_cache `(ℤ),
    nc ← mk_instance_cache `(ℕ),
    (prod.snd ∘ prod.snd) <$> prove_lcm_int zc nc ex ey
| _ := failed

end norm_num
end tactic


/-
Copyright (c) 2020 Frédéric Dupuis. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Frédéric Dupuis
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.analysis.calculus.local_extr
import Mathlib.topology.algebra.affine
import Mathlib.PostPort

universes u_2 u_1

namespace Mathlib

/-!
# Minima and maxima of convex functions

We show that if a function `f : E → β` is convex, then a local minimum is also
a global minimum, and likewise for concave functions.
-/

/--
Helper lemma for the more general case: `is_min_on.of_is_local_min_on_of_convex_on`.
-/
theorem is_min_on.of_is_local_min_on_of_convex_on_Icc {β : Type u_2} [linear_ordered_add_comm_group β] [semimodule ℝ β] [ordered_semimodule ℝ β] {f : ℝ → β} {a : ℝ} {b : ℝ} (a_lt_b : a < b) (h_local_min : is_local_min_on f (set.Icc a b) a) (h_conv : convex_on (set.Icc a b) f) (x : ℝ) (H : x ∈ set.Icc a b) : f a ≤ f x := sorry

/--
A local minimum of a convex function is a global minimum, restricted to a set `s`.
-/
theorem is_min_on.of_is_local_min_on_of_convex_on {E : Type u_1} {β : Type u_2} [add_comm_group E] [topological_space E] [module ℝ E] [topological_add_group E] [topological_vector_space ℝ E] [linear_ordered_add_comm_group β] [semimodule ℝ β] [ordered_semimodule ℝ β] {s : set E} {f : E → β} {a : E} (a_in_s : a ∈ s) (h_localmin : is_local_min_on f s a) (h_conv : convex_on s f) (x : E) (H : x ∈ s) : f a ≤ f x := sorry

/-- A local maximum of a concave function is a global maximum, restricted to a set `s`. -/
theorem is_max_on.of_is_local_max_on_of_concave_on {E : Type u_1} {β : Type u_2} [add_comm_group E] [topological_space E] [module ℝ E] [topological_add_group E] [topological_vector_space ℝ E] [linear_ordered_add_comm_group β] [semimodule ℝ β] [ordered_semimodule ℝ β] {s : set E} {f : E → β} {a : E} (a_in_s : a ∈ s) (h_localmax : is_local_max_on f s a) (h_conc : concave_on s f) (x : E) (H : x ∈ s) : f x ≤ f a :=
  is_min_on.of_is_local_min_on_of_convex_on a_in_s h_localmax h_conc

/-- A local minimum of a convex function is a global minimum. -/
theorem is_min_on.of_is_local_min_of_convex_univ {E : Type u_1} {β : Type u_2} [add_comm_group E] [topological_space E] [module ℝ E] [topological_add_group E] [topological_vector_space ℝ E] [linear_ordered_add_comm_group β] [semimodule ℝ β] [ordered_semimodule ℝ β] {f : E → β} {a : E} (h_local_min : is_local_min f a) (h_conv : convex_on set.univ f) (x : E) : f a ≤ f x :=
  is_min_on.of_is_local_min_on_of_convex_on (set.mem_univ a) (is_local_min.on h_local_min set.univ) h_conv x
    (set.mem_univ x)

/-- A local maximum of a concave function is a global maximum. -/
theorem is_max_on.of_is_local_max_of_convex_univ {E : Type u_1} {β : Type u_2} [add_comm_group E] [topological_space E] [module ℝ E] [topological_add_group E] [topological_vector_space ℝ E] [linear_ordered_add_comm_group β] [semimodule ℝ β] [ordered_semimodule ℝ β] {f : E → β} {a : E} (h_local_max : is_local_max f a) (h_conc : concave_on set.univ f) (x : E) : f x ≤ f a :=
  is_min_on.of_is_local_min_of_convex_univ h_local_max h_conc


/-
# Tutorial World

## Level 3: the rewrite (`rw`) tactic (II).

In the previous level, we learned that `rw h` changes A's into B's when the goal contains one or more A's
and we have the hypothesis `h : A = B` in the local context. You may be wondering if the opposite case is
also possible. That is to say: could we change B's into A's when the goal contains one or more B's and we have
the hypothesis `h : A = B` in the local context?

So the answer is... Yes! The hypotheses in this level are a bit different than before,
so you should use **`rw ←`** instead. To do so, you can type the little left-arrow by typing **\l**
and then a space, so the system will change it automatically.

## Did you know?

On the top right corner of the screen, there is a box named "View source" for each level. If you
click on it, you will see one possible solution to this level. Again, try to use this tool wisely!

-/

/- Hint : Click here for a hint, in case you get stuck.
You may want to use *`rw ←`* first. Use it only with one of the hypotheses. Then, think if it's necessary to use it again
or you just can finish the proof by using `rw` without `←`.
-/
variables {Ω : Type} -- hide

/- Lemma : no-side-bar
If A, B and C are points with B = A and B = C, then A = C.
-/
lemma example_exact (A B C: Ω) (h1 : B = A) (h2 : B = C) : A = C :=
begin
  rw ← h1,
  rw h2,

end


/- Integers mod 37

  A demonstration of how to use equivalence relations and equivalence classes in Lean.

  We define the "congruent mod 37" relation on integers, prove it is an equivalence
  relation, define Zmod37 to be the equivalence classes, and put a ring structure on
  the quotient.

-/
-- this import is helpful for some intermediate calculation
import tactic.ring

-- Definition of the equivalence relation
definition cong_mod37 (a b : ℤ) : Prop := ∃ (k : ℤ), k * 37 = b - a

-- Now check it's an equivalence reln!

theorem cong_mod_refl : reflexive (cong_mod37) :=
begin
  intro x,
  -- to prove cong_mod37 x x we just observe that k = 0 will do.
  use (0:ℤ), -- this is k
  simp,
end

theorem cong_mod_symm : symmetric (cong_mod37) :=
begin
  intros a b H,
  -- H : cond_mod37 a b
  cases H with k Hk,
  -- Hk : k * 37 = (b - a)
  -- Goal is to find an integer k' with k' * 37 = a - b
  use -k,
  simp [Hk],
end

theorem cong_mod_trans : transitive (cong_mod37) :=
begin
  intros a b c Hab Hbc,
  cases Hab with k Hk,
  cases Hbc with l Hl,
  -- Hk : k*37 = b - a, and Hl : l*37 = c - b
  -- Goal : m * 37 = c - a
  use (k+l),
  simp [add_mul,Hk,Hl],
end

-- so we've now seen a general technique for proving a ≈ b -- use (the k that works)

theorem cong_mod_equiv : equivalence (cong_mod37) :=
⟨cong_mod_refl,cong_mod_symm,cong_mod_trans⟩

instance Z_setoid : setoid ℤ := { r := cong_mod37, iseqv := cong_mod_equiv }

definition Zmod37 := quotient (Z_setoid)

namespace Zmod37

definition reduce_mod37 : ℤ → Zmod37 := quot.mk (cong_mod37)

-- now a little bit of basic interface

-- Natural map from ℤ to ℤmod37
instance coe_int_Zmod37 : has_coe ℤ (Zmod37) := ⟨reduce_mod37⟩

-- Notation for 0 and 1
instance : has_zero (Zmod37) := ⟨reduce_mod37 0⟩
instance : has_one (Zmod37) := ⟨reduce_mod37 1⟩

-- Add basic facts about 0 and 1 to the set of simp facts
@[simp] theorem of_int_zero : (0 : (Zmod37))  = reduce_mod37 0 := rfl
@[simp] theorem of_int_one : (1 : (Zmod37))  = reduce_mod37 1 := rfl

-- now back to the maths

-- here's a useful lemma -- it's needed to prove addition is well-defined on the quotient.
-- Note the use of quotient.sound to get from Zmod37 back to Z

lemma congr_add (a₁ a₂ b₁ b₂ : ℤ) : a₁ ≈ b₁ → a₂ ≈ b₂ → ⟦a₁ + a₂⟧ = ⟦b₁ + b₂⟧ :=
begin
  intros H1 H2,
  cases H1 with m Hm, -- Hm : m * 37 = b₁ - a₁
  cases H2 with n Hn, -- Hn : n * 37 = b₂ - a₂
  -- goal is ⟦a₁ + a₂⟧ = ⟦b₁ + b₂⟧
  apply quotient.sound,
  -- goal now a₁ + a₂ ≈ b₁ + b₂, and we know how to do these.
  use (m+n),
  simp [add_mul,Hm,Hn]
end

-- That lemma above is *exactly* what we need to make sure addition is
-- well-defined on Zmod37, so let's do this now, using quotient.lift

-- note: stuff like "add" is used everywhere so it's best to protect.
protected definition add : Zmod37 → Zmod37 → Zmod37 :=
quotient.lift₂ (λ a b : ℤ, ⟦a + b⟧) (begin
  show ∀ (a₁ a₂ b₁ b₂ : ℤ), a₁ ≈ b₁ → a₂ ≈ b₂ → ⟦a₁ + a₂⟧ = ⟦b₁ + b₂⟧,
  -- that's what quotient.lift₂ reduces us to doing. But we did it already!
  exact congr_add,
end)

-- Now here's the lemma we need for the definition of neg

-- I spelt out the proof for add, here's a quick term proof for neg.

lemma congr_neg (a b : ℤ) : a ≈ b → ⟦-a⟧ = ⟦-b⟧ :=
λ ⟨m,Hm⟩,quotient.sound ⟨-m,by simp [Hm]⟩

protected def neg : Zmod37 → Zmod37 := quotient.lift (λ a : ℤ, ⟦-a⟧) congr_neg

-- For multiplication I won't even bother proving the lemma, I'll just let ring do it

protected def mul : Zmod37 → Zmod37 → Zmod37 :=
quotient.lift₂ (λ a b : ℤ, ⟦a*b⟧) (λ a₁ a₂ b₁ b₂ ⟨m₁,H₁⟩ ⟨m₂,H₂⟩,quotient.sound ⟨b₁ * m₂ + a₂ * m₁,
  by rw [add_mul,mul_assoc,mul_assoc,H₁,H₂];ring⟩)

-- this adds notation to the quotient

instance : has_add (Zmod37) := ⟨Zmod37.add⟩
instance : has_neg (Zmod37) := ⟨Zmod37.neg⟩
instance : has_mul (Zmod37) := ⟨Zmod37.mul⟩

-- these are now very cool proofs:
@[simp] lemma coe_add {a b : ℤ} : (↑(a + b) : Zmod37) = ↑a + ↑b := rfl
@[simp] lemma coe_neg {a : ℤ} : (↑(-a) : Zmod37) = -↑a := rfl
@[simp] lemma coe_mul {a b : ℤ} : (↑(a * b) : Zmod37) = ↑a * ↑b := rfl

-- The proof of coe_add would not be rfl at all if you defined addition on the quotient
-- by choosing representatives and then adding them. Note that choosing reps
-- and adding them is exactly what mathematicians do; they shoot first and
-- ask questions later.

-- Now here's how to use quotient.induction_on and quotient.sound

instance : add_comm_group (Zmod37)  :=
{ add_comm_group .
  zero         := 0, -- because we already defined has_zero
  add          := (+), -- could also have written has_add.add or Zmod37.add
  neg          := has_neg.neg,
  zero_add     :=
    λ abar, quotient.induction_on abar (begin
      -- goal is ∀ (a : ℤ), 0 + ⟦a⟧ = ⟦a⟧ -- that's what quotient.induction_on does for us
      intro a,
      apply quotient.sound, -- works because 0 + ⟦a⟧ is by definition ⟦0⟧ + ⟦a⟧ which is by definition ⟦0 + a⟧
      -- goal is now 0 + a ≈ a
      -- here's the way we used to do it.
      use (0 : ℤ),
      simp,
      -- but there are tricks now, which I'll show you with add_zero and add_assoc.
    end),
  add_assoc    := λ abar bbar cbar,quotient.induction_on₃ abar bbar cbar (λ a b c,
    begin
      -- goal now ⟦a⟧ + ⟦b⟧ + ⟦c⟧ = ⟦a⟧ + (⟦b⟧ + ⟦c⟧)
      apply quotient.sound,
      -- goal now a + b + c ≈ a + (b + c)
      rw add_assoc, -- done :-) because after a rw a goal is closed if it's of the form x ≈ x, as ≈ is
                    -- known to be reflexive.
    end),
  add_zero     := -- I will intrroduce some more sneaky stuff now now
                  -- add_zero for Zmod37 follows from add_zero on Z.
                  -- Note use of $ instead of the brackets
    λ abar, quotient.induction_on abar $ λ a, quotient.sound $ by rw add_zero,
                  -- that's it! Term mode proof.
  add_left_neg := -- super-slow method not even using quotient.induction_on
    begin
      intro abar,
      cases (quot.exists_rep abar) with a Ha,
      rw [←Ha],
      apply quot.sound,
      use (0:ℤ),
      simp,
    end,
  -- but really all proofs should just look something like this
  add_comm     := λ abar bbar, quotient.induction_on₂ abar bbar $ λ _ _,quotient.sound $ by rw add_comm,
  -- the noise at the beginning is just the machine; all the work is done by the rewrite
}

-- Now let's just nail this using all the tricks in the book. All ring axioms on the quotient
-- follow from the corresponding axioms for Z.
instance : comm_ring (Zmod37) :=
{
  mul := Zmod37.mul, -- could have written (*)
  -- Now look how the proof of mul_assoc is just the same structure as add_comm above
  -- but with three variables not two
  mul_assoc := λ a b c, quotient.induction_on₃ a b c $ λ _ _ _, quotient.sound $ by rw mul_assoc,
  one := 1,
  one_mul := λ a, quotient.induction_on a $ λ _, quotient.sound $ by rw one_mul,
  mul_one := λ a, quotient.induction_on a $ λ _, quotient.sound $ by rw mul_one,
  left_distrib := λ a b c, quotient.induction_on₃ a b c $ λ _ _ _, quotient.sound $ by rw left_distrib,
  right_distrib := λ a b c, quotient.induction_on₃ a b c $ λ _ _ _, quotient.sound $ by rw right_distrib,
  mul_comm := λ a b, quotient.induction_on₂ a b $ λ _ _, quotient.sound $ by rw mul_comm,
  ..Zmod37.add_comm_group
}

end Zmod37


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

! This file was ported from Lean 3 source module data.fin.tuple.monotone
! leanprover-community/mathlib commit e3d9ab8faa9dea8f78155c6c27d62a621f4c152d
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Data.Fin.VecNotation

/-!
# Monotone finite sequences

In this file we prove `simp` lemmas that allow to simplify propositions like `Monotone ![a, b, c]`.
-/

open Set Fin Matrix Function

variable {α : Type _}

theorem lift_fun_vecCons {n : ℕ} (r : α → α → Prop) [IsTrans α r] {f : Fin (n + 1) → α} {a : α} :
    ((· < ·) ⇒ r) (vecCons a f) (vecCons a f) ↔ r a (f 0) ∧ ((· < ·) ⇒ r) f f := by
  simp only [lift_fun_iff_succ r, forall_fin_succ, cons_val_succ, cons_val_zero, ← succ_castSucc,
    castSucc_zero]
#align lift_fun_vec_cons lift_fun_vecCons

variable [Preorder α] {n : ℕ} {f : Fin (n + 1) → α} {a : α}

@[simp]
theorem strictMono_vecCons : StrictMono (vecCons a f) ↔ a < f 0 ∧ StrictMono f :=
  lift_fun_vecCons (· < ·)
#align strict_mono_vec_cons strictMono_vecCons

@[simp]
theorem monotone_vecCons : Monotone (vecCons a f) ↔ a ≤ f 0 ∧ Monotone f := by
  simpa only [monotone_iff_forall_lt] using @lift_fun_vecCons α n (· ≤ ·) _ f a
#align monotone_vec_cons monotone_vecCons

--Porting note: new lemma, in Lean3 would be proven by `Subsingleton.monotone`
@[simp]
theorem monotone_vecEmpty : Monotone (vecCons a vecEmpty)
  | ⟨0, _⟩, ⟨0, _⟩, _ => le_refl _

--Porting note: new lemma, in Lean3 would be proven by `Subsingleton.strictMono`
@[simp]
theorem strictMono_vecEmpty : StrictMono (vecCons a vecEmpty)
  | ⟨0, _⟩, ⟨0, _⟩, h => (irrefl _ h).elim

@[simp]
theorem strictAnti_vecCons : StrictAnti (vecCons a f) ↔ f 0 < a ∧ StrictAnti f :=
  lift_fun_vecCons (· > ·)
#align strict_anti_vec_cons strictAnti_vecCons

@[simp]
theorem antitone_vecCons : Antitone (vecCons a f) ↔ f 0 ≤ a ∧ Antitone f :=
  @monotone_vecCons αᵒᵈ _ _ _ _
#align antitone_vec_cons antitone_vecCons

--Porting note: new lemma, in Lean3 would be proven by `Subsingleton.antitone`
@[simp]
theorem antitone_vecEmpty : Antitone (vecCons a vecEmpty)
  | ⟨0, _⟩, ⟨0, _⟩, _ => le_refl _

--Porting note: new lemma, in Lean3 would be proven by `Subsingleton.strictAnti`
@[simp]
theorem strictAnti_vecEmpty : StrictAnti (vecCons a vecEmpty)
  | ⟨0, _⟩, ⟨0, _⟩, h => (irrefl _ h).elim

theorem StrictMono.vecCons (hf : StrictMono f) (ha : a < f 0) : StrictMono (vecCons a f) :=
  strictMono_vecCons.2 ⟨ha, hf⟩
#align strict_mono.vec_cons StrictMono.vecCons

theorem StrictAnti.vecCons (hf : StrictAnti f) (ha : f 0 < a) : StrictAnti (vecCons a f) :=
  strictAnti_vecCons.2 ⟨ha, hf⟩
#align strict_anti.vec_cons StrictAnti.vecCons

theorem Monotone.vecCons (hf : Monotone f) (ha : a ≤ f 0) : Monotone (vecCons a f) :=
  monotone_vecCons.2 ⟨ha, hf⟩
#align monotone.vec_cons Monotone.vecCons

theorem Antitone.vecCons (hf : Antitone f) (ha : f 0 ≤ a) : Antitone (vecCons a f) :=
  antitone_vecCons.2 ⟨ha, hf⟩
#align antitone.vec_cons Antitone.vecCons

example : Monotone ![1, 2, 2, 3] := by simp


import data.finset
import data.real.basic
import algebra.group.basic
import algebra.order.floor

#check finset.sum_range_induction

section commutators

variables {G H : Type*} [group G] [group H]

def γ (a b : G) : G := a * b * a⁻¹ * b⁻¹

lemma map_γ (f : monoid_hom G H) (a b : G) : f (γ a b) = γ (f a) (f b) := by {
  dsimp[γ], simp[f.map_mul, f.map_inv]
}

lemma cube_γ (a b : G) : (γ a b) ^ 3 =
   (γ (a * b * a⁻¹) (b⁻¹ * a * b * (a⁻¹ ^ 2))) * (γ (b⁻¹ * a * b) (b ^ 2)) :=
begin
  dsimp[γ], repeat { rw[pow_succ] }, repeat { rw[pow_zero] },
  repeat { rw[mul_inv_rev] }, repeat { rw[inv_inv] }, repeat { rw[one_inv] },
  repeat { rw[mul_one] }, repeat { rw[one_mul] },
  repeat { rw[mul_assoc] },
  simp,
end

end commutators

lemma sandwich {c x : ℝ} (h : ∀ n : ℕ+, (abs x) ≤ c / n) : x = 0 :=
begin
  by_cases hc : c > 0,
  { by_contra hx,
    replace hx : abs x > 0 := abs_pos.mpr hx,
    rcases (archimedean.arch (c + 1) hx) with ⟨n,hn⟩,
    cases n with n,
    { rw[zero_smul] at hn,
      exact lt_asymm (lt_of_lt_of_le (lt_add_one c) hn) hc },
    rw[nsmul_eq_mul, mul_comm] at hn,
    have np : (n.succ : ℝ) > 0 := nat.cast_pos.mpr n.succ_pos,
    have h' : abs x ≤ c / n.succ := h ⟨n.succ, n.succ_pos⟩,
    exact not_lt_of_ge (le_trans hn ((le_div_iff np).mp h')) (lt_add_one c)
  },
  { have h' := (h 1),
    have : (1 : ℝ) = (1 : ℕ+) := by norm_num, rw[← this, div_one] at h',
    exact abs_nonpos_iff.mp (le_trans h' (le_of_not_gt hc))
  }
end


/-
Copyright (c) 2020 Anatole Dedecker. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Anatole Dedecker
-/
import analysis.asymptotics.asymptotics
import analysis.normed.order.basic

/-!
# Asymptotic equivalence

In this file, we define the relation `is_equivalent l u v`, which means that `u-v` is little o of
`v` along the filter `l`.

Unlike `is_[oO]` relations, this one requires `u` and `v` to have the same codomain `β`. While the
definition only requires `β` to be a `normed_add_comm_group`, most interesting properties require it
to be a `normed_field`.

## Notations

We introduce the notation `u ~[l] v := is_equivalent l u v`, which you can use by opening the
`asymptotics` locale.

## Main results

If `β` is a `normed_add_comm_group` :

- `_ ~[l] _` is an equivalence relation
- Equivalent statements for `u ~[l] const _ c` :
  - If `c ≠ 0`, this is true iff `tendsto u l (𝓝 c)` (see `is_equivalent_const_iff_tendsto`)
  - For `c = 0`, this is true iff `u =ᶠ[l] 0` (see `is_equivalent_zero_iff_eventually_zero`)

If `β` is a `normed_field` :

- Alternative characterization of the relation (see `is_equivalent_iff_exists_eq_mul`) :

  `u ~[l] v ↔ ∃ (φ : α → β) (hφ : tendsto φ l (𝓝 1)), u =ᶠ[l] φ * v`

- Provided some non-vanishing hypothesis, this can be seen as `u ~[l] v ↔ tendsto (u/v) l (𝓝 1)`
  (see `is_equivalent_iff_tendsto_one`)
- For any constant `c`, `u ~[l] v` implies `tendsto u l (𝓝 c) ↔ tendsto v l (𝓝 c)`
  (see `is_equivalent.tendsto_nhds_iff`)
- `*` and `/` are compatible with `_ ~[l] _` (see `is_equivalent.mul` and `is_equivalent.div`)

If `β` is a `normed_linear_ordered_field` :

- If `u ~[l] v`, we have `tendsto u l at_top ↔ tendsto v l at_top`
  (see `is_equivalent.tendsto_at_top_iff`)

## Implementation Notes

Note that `is_equivalent` takes the parameters `(l : filter α) (u v : α → β)` in that order.
This is to enable `calc` support, as `calc` requires that the last two explicit arguments are `u v`.

-/

namespace asymptotics

open filter function
open_locale topology

section normed_add_comm_group

variables {α β : Type*} [normed_add_comm_group β]

/-- Two functions `u` and `v` are said to be asymptotically equivalent along a filter `l` when
    `u x - v x = o(v x)` as x converges along `l`. -/
def is_equivalent (l : filter α) (u v : α → β) := (u - v) =o[l] v

localized "notation (name := asymptotics.is_equivalent)
  u ` ~[`:50 l:50 `] `:0 v:50 := asymptotics.is_equivalent l u v" in asymptotics

variables {u v w : α → β} {l : filter α}

lemma is_equivalent.is_o (h : u ~[l] v) : (u - v) =o[l] v := h

lemma is_equivalent.is_O (h : u ~[l] v) : u =O[l] v :=
(is_O.congr_of_sub h.is_O.symm).mp (is_O_refl _ _)

lemma is_equivalent.is_O_symm (h : u ~[l] v) : v =O[l] u :=
begin
  convert h.is_o.right_is_O_add,
  ext,
  simp
end

@[refl] lemma is_equivalent.refl : u ~[l] u :=
begin
  rw [is_equivalent, sub_self],
  exact is_o_zero _ _
end

@[symm] lemma is_equivalent.symm (h : u ~[l] v) : v ~[l] u :=
(h.is_o.trans_is_O h.is_O_symm).symm

@[trans] lemma is_equivalent.trans {l : filter α} {u v w : α → β}
  (huv : u ~[l] v) (hvw : v ~[l] w) : u ~[l] w :=
(huv.is_o.trans_is_O hvw.is_O).triangle hvw.is_o

lemma is_equivalent.congr_left {u v w : α → β} {l : filter α} (huv : u ~[l] v)
  (huw : u =ᶠ[l] w) : w ~[l] v :=
huv.congr' (huw.sub (eventually_eq.refl _ _)) (eventually_eq.refl _ _)

lemma is_equivalent.congr_right {u v w : α → β} {l : filter α} (huv : u ~[l] v)
  (hvw : v =ᶠ[l] w) : u ~[l] w :=
(huv.symm.congr_left hvw).symm

lemma is_equivalent_zero_iff_eventually_zero : u ~[l] 0 ↔ u =ᶠ[l] 0 :=
begin
  rw [is_equivalent, sub_zero],
  exact is_o_zero_right_iff
end

lemma is_equivalent_zero_iff_is_O_zero : u ~[l] 0 ↔ u =O[l] (0 : α → β) :=
begin
  refine ⟨is_equivalent.is_O, λ h, _⟩,
  rw [is_equivalent_zero_iff_eventually_zero, eventually_eq_iff_exists_mem],
  exact ⟨{x : α | u x = 0}, is_O_zero_right_iff.mp h, λ x hx, hx⟩,
end

lemma is_equivalent_const_iff_tendsto {c : β} (h : c ≠ 0) : u ~[l] const _ c ↔ tendsto u l (𝓝 c) :=
begin
  rw [is_equivalent, is_o_const_iff h],
  split; intro h;
  [ { have := h.sub tendsto_const_nhds, rw zero_sub (-c) at this },
    { have := h.sub tendsto_const_nhds, rw ← sub_self c} ];
  convert this; try { ext }; simp
end

lemma is_equivalent.tendsto_const {c : β} (hu : u ~[l] const _ c) : tendsto u l (𝓝 c) :=
begin
  rcases (em $ c = 0) with ⟨rfl, h⟩,
  { exact (tendsto_congr' $ is_equivalent_zero_iff_eventually_zero.mp hu).mpr tendsto_const_nhds },
  { exact (is_equivalent_const_iff_tendsto h).mp hu }
end

lemma is_equivalent.tendsto_nhds_iff {c : β} (huv : u ~[l] v) :
  tendsto u l (𝓝 c) ↔ tendsto v l (𝓝 c) := ⟨huv.tendsto_nhds, huv.symm.tendsto_nhds⟩

lemma is_equivalent.add_is_o (huv : u ~[l] v) (hwv : w =o[l] v) : (u + w) ~[l] v :=
by simpa only [is_equivalent, add_sub_right_comm] using huv.add hwv

lemma is_equivalent.sub_is_o (huv : u ~[l] v) (hwv : w =o[l] v) : (u - w) ~[l] v :=
by simpa only [sub_eq_add_neg] using huv.add_is_o hwv.neg_left

lemma is_o.add_is_equivalent (hu : u =o[l] w) (hv : v ~[l] w) : (u + v) ~[l] w :=
add_comm v u ▸ hv.add_is_o hu

lemma is_o.is_equivalent (huv : (u - v) =o[l] v) : u ~[l] v := huv

lemma is_equivalent.neg (huv : u ~[l] v) : (λ x, - u x) ~[l] (λ x, - v x) :=
begin
  rw is_equivalent,
  convert huv.is_o.neg_left.neg_right,
  ext,
  simp,
end

end normed_add_comm_group

open_locale asymptotics

section normed_field

variables {α β : Type*} [normed_field β] {t u v w : α → β} {l : filter α}

lemma is_equivalent_iff_exists_eq_mul : u ~[l] v ↔
  ∃ (φ : α → β) (hφ : tendsto φ l (𝓝 1)), u =ᶠ[l] φ * v :=
begin
  rw [is_equivalent, is_o_iff_exists_eq_mul],
  split; rintros ⟨φ, hφ, h⟩; [use (φ + 1), use (φ - 1)]; split,
  { conv in (𝓝 _) { rw ← zero_add (1 : β) },
    exact hφ.add (tendsto_const_nhds) },
  { convert h.add (eventually_eq.refl l v); ext; simp [add_mul] },
  { conv in (𝓝 _) { rw ← sub_self (1 : β) },
    exact hφ.sub (tendsto_const_nhds) },
  { convert h.sub (eventually_eq.refl l v); ext; simp [sub_mul] }
end

lemma is_equivalent.exists_eq_mul (huv : u ~[l] v) :
  ∃ (φ : α → β) (hφ : tendsto φ l (𝓝 1)), u =ᶠ[l] φ * v :=
is_equivalent_iff_exists_eq_mul.mp huv

lemma is_equivalent_of_tendsto_one (hz : ∀ᶠ x in l, v x = 0 → u x = 0)
  (huv : tendsto (u/v) l (𝓝 1)) : u ~[l] v :=
begin
  rw is_equivalent_iff_exists_eq_mul,
  refine ⟨u/v, huv, hz.mono $ λ x hz', (div_mul_cancel_of_imp hz').symm⟩,
end

lemma is_equivalent_of_tendsto_one' (hz : ∀ x, v x = 0 → u x = 0) (huv : tendsto (u/v) l (𝓝 1)) :
  u ~[l] v :=
is_equivalent_of_tendsto_one (eventually_of_forall hz) huv

lemma is_equivalent_iff_tendsto_one (hz : ∀ᶠ x in l, v x ≠ 0) :
  u ~[l] v ↔ tendsto (u/v) l (𝓝 1) :=
begin
  split,
  { intro hequiv,
    have := hequiv.is_o.tendsto_div_nhds_zero,
    simp only [pi.sub_apply, sub_div] at this,
    have key : tendsto (λ x, v x / v x) l (𝓝 1),
    { exact (tendsto_congr' $ hz.mono $ λ x hnz, @div_self _ _ (v x) hnz).mpr tendsto_const_nhds },
    convert this.add key,
    { ext, simp },
    { norm_num } },
  { exact is_equivalent_of_tendsto_one (hz.mono $ λ x hnvz hz, (hnvz hz).elim) }
end

end normed_field

section smul

lemma is_equivalent.smul {α E 𝕜 : Type*} [normed_field 𝕜] [normed_add_comm_group E]
  [normed_space 𝕜 E] {a b : α → 𝕜} {u v : α → E} {l : filter α} (hab : a ~[l] b) (huv : u ~[l] v) :
  (λ x, a x • u x) ~[l] (λ x, b x • v x) :=
begin
  rcases hab.exists_eq_mul with ⟨φ, hφ, habφ⟩,
  have : (λ (x : α), a x • u x) - (λ (x : α), b x • v x) =ᶠ[l] λ x, b x • ((φ x • u x) - v x),
  { convert (habφ.comp₂ (•) $ eventually_eq.refl _ u).sub (eventually_eq.refl _ (λ x, b x • v x)),
    ext,
    rw [pi.mul_apply, mul_comm, mul_smul, ← smul_sub] },
  refine (is_o_congr this.symm $ eventually_eq.rfl).mp ((is_O_refl b l).smul_is_o _),

  rcases huv.is_O.exists_pos with ⟨C, hC, hCuv⟩,
  rw is_equivalent at *,
  rw is_o_iff at *,
  rw is_O_with at hCuv,
  simp only [metric.tendsto_nhds, dist_eq_norm] at hφ,
  intros c hc,
  specialize hφ ((c/2)/C) (div_pos (by linarith) hC),
  specialize huv (show 0 < c/2, by linarith),
  refine hφ.mp (huv.mp $ hCuv.mono $ λ x hCuvx huvx hφx, _),

  have key :=
    calc ‖φ x - 1‖ * ‖u x‖
            ≤ (c/2) / C * ‖u x‖ : mul_le_mul_of_nonneg_right hφx.le (norm_nonneg $ u x)
        ... ≤ (c/2) / C * (C*‖v x‖) : mul_le_mul_of_nonneg_left hCuvx (div_pos (by linarith) hC).le
        ... = c/2 * ‖v x‖ : by {field_simp [hC.ne.symm], ring},

  calc ‖((λ (x : α), φ x • u x) - v) x‖
          = ‖(φ x - 1) • u x + (u x - v x)‖ : by simp [sub_smul, sub_add]
      ... ≤ ‖(φ x - 1) • u x‖ + ‖u x - v x‖ : norm_add_le _ _
      ... = ‖φ x - 1‖ * ‖u x‖ + ‖u x - v x‖ : by rw norm_smul
      ... ≤ c / 2 * ‖v x‖ + ‖u x - v x‖ : add_le_add_right key _
      ... ≤ c / 2 * ‖v x‖ + c / 2 * ‖v x‖ : add_le_add_left huvx _
      ... = c * ‖v x‖ : by ring,
end

end smul

section mul_inv

variables {α β : Type*} [normed_field β] {t u v w : α → β} {l : filter α}

lemma is_equivalent.mul (htu : t ~[l] u) (hvw : v ~[l] w) : t * v ~[l] u * w :=
htu.smul hvw

lemma is_equivalent.inv (huv : u ~[l] v) : (λ x, (u x)⁻¹) ~[l] (λ x, (v x)⁻¹) :=
begin
  rw is_equivalent_iff_exists_eq_mul at *,
  rcases huv with ⟨φ, hφ, h⟩,
  rw ← inv_one,
  refine ⟨λ x, (φ x)⁻¹, tendsto.inv₀ hφ (by norm_num) , _⟩,
  convert h.inv,
  ext,
  simp [mul_inv]
end

lemma is_equivalent.div (htu : t ~[l] u) (hvw : v ~[l] w) :
  (λ x, t x / v x) ~[l] (λ x, u x / w x) :=
by simpa only [div_eq_mul_inv] using htu.mul hvw.inv

end mul_inv

section normed_linear_ordered_field

variables {α β : Type*} [normed_linear_ordered_field β] {u v : α → β} {l : filter α}

lemma is_equivalent.tendsto_at_top [order_topology β] (huv : u ~[l] v) (hu : tendsto u l at_top) :
  tendsto v l at_top :=
let ⟨φ, hφ, h⟩ := huv.symm.exists_eq_mul in
tendsto.congr' h.symm ((mul_comm u φ) ▸ (hu.at_top_mul zero_lt_one hφ))

lemma is_equivalent.tendsto_at_top_iff [order_topology β] (huv : u ~[l] v) :
  tendsto u l at_top ↔ tendsto v l at_top := ⟨huv.tendsto_at_top, huv.symm.tendsto_at_top⟩

lemma is_equivalent.tendsto_at_bot [order_topology β] (huv : u ~[l] v) (hu : tendsto u l at_bot) :
  tendsto v l at_bot :=
begin
  convert tendsto_neg_at_top_at_bot.comp
    (huv.neg.tendsto_at_top $ tendsto_neg_at_bot_at_top.comp hu),
  ext,
  simp
end

lemma is_equivalent.tendsto_at_bot_iff [order_topology β] (huv : u ~[l] v) :
  tendsto u l at_bot ↔ tendsto v l at_bot := ⟨huv.tendsto_at_bot, huv.symm.tendsto_at_bot⟩

end normed_linear_ordered_field

end asymptotics

open filter asymptotics
open_locale asymptotics

variables {α β : Type*} [normed_add_comm_group β]

lemma filter.eventually_eq.is_equivalent {u v : α → β} {l : filter α} (h : u =ᶠ[l] v) : u ~[l] v :=
is_equivalent.congr_right (is_o_refl_left _ _) h


import GMLAlgebra.Basic
import GMLAlgebra.Group

namespace Algebra
variable {α} (s : SemiringSig α)

local infixr:70 " ⋆ " => s.mul
local infixr:65 " ⊹ " => s.add

class Semiring : Prop where
  protected add_assoc (x y z) : (x ⊹ y) ⊹ z = x ⊹ (y ⊹ z)
  protected add_comm (x y) : x ⊹ y = y ⊹ x
  protected mul_assoc (x y z) : (x ⋆ y) ⋆ z = x ⋆ (y ⋆ z)
  protected mul_left_distrib (x y z) : x ⋆ (y ⊹ z) = x ⋆ y ⊹ x ⋆ z
  protected mul_right_distrib (x y z) : (x ⊹ y) ⋆ z = x ⋆ z ⊹ y ⋆ z

protected def Semiring.infer [OpAssoc s.add] [OpComm s.add] [OpAssoc s.mul] [OpLeftDistrib s.mul s.add] [OpRightDistrib s.mul s.add] : Semiring s where
  add_assoc := op_assoc _
  add_comm := op_comm _
  mul_assoc := op_assoc _
  mul_left_distrib := op_left_distrib _
  mul_right_distrib := op_right_distrib _

namespace Semiring
variable {s} [self : Semiring s]

local instance : OpAssoc (no_index s.add) := ⟨Semiring.add_assoc⟩
local instance : OpComm (no_index s.add) := ⟨Semiring.add_comm⟩
local instance : OpAssoc (no_index s.mul) := ⟨Semiring.mul_assoc⟩
instance : OpLeftDistrib (no_index s.mul) (no_index s.add) := ⟨Semiring.mul_left_distrib⟩
instance : OpRightDistrib (no_index s.mul) (no_index s.add) := ⟨Semiring.mul_right_distrib⟩

instance toAddCommSemigroup : CommSemigroup (no_index s.toAddSemigroupSig) := CommSemigroup.infer _

instance toMulSemigroup : Semigroup (no_index s.toMulSemigroupSig) := Semigroup.infer _

end Semiring

class CommSemiring : Prop where
  protected add_assoc (x y z) : (x ⊹ y) ⊹ z = x ⊹ (y ⊹ z)
  protected add_comm (x y) : x ⊹ y = y ⊹ x
  protected mul_assoc (x y z) : (x ⋆ y) ⋆ z = x ⋆ (y ⋆ z)
  protected mul_comm (x y) : x ⋆ y = y ⋆ x
  protected mul_right_distrib (x y z) : (x ⊹ y) ⋆ z = x ⋆ z ⊹ y ⋆ z

protected def CommSemiring.infer [OpAssoc s.add] [OpComm s.add] [OpAssoc s.mul] [OpComm s.mul] [OpRightDistrib s.mul s.add] : CommSemiring s where
  add_assoc := op_assoc _
  add_comm := op_comm _
  mul_assoc := op_assoc _
  mul_comm := op_comm _
  mul_right_distrib := op_right_distrib _

namespace CommSemiring
variable {s} [self : CommSemiring s]

local instance : OpAssoc (no_index s.add) := ⟨CommSemiring.add_assoc⟩
local instance : OpComm (no_index s.add) := ⟨CommSemiring.add_comm⟩
local instance : OpAssoc (no_index s.mul) := ⟨CommSemiring.mul_assoc⟩
local instance : OpComm (no_index s.mul) := ⟨CommSemiring.mul_comm⟩
local instance : OpRightDistrib (no_index s.mul) (no_index s.add) := ⟨CommSemiring.mul_right_distrib⟩

protected theorem mul_left_distrib (x y z) : x ⋆ (y ⊹ z) = x ⋆ y ⊹ x ⋆ z := calc
  _ = (y ⊹ z) ⋆ x := by rw [op_comm (.⋆.) x (y ⊹ z)]
  _ = y ⋆ x ⊹ z ⋆ x := by rw [op_right_distrib (.⋆.) y z x]
  _ = x ⋆ y ⊹ z ⋆ x := by rw [op_comm (.⋆.) x y]
  _ = x ⋆ y ⊹ x ⋆ z := by rw [op_comm (.⋆.) x z]
local instance : OpLeftDistrib (no_index s.mul) (no_index s.add) := ⟨CommSemiring.mul_left_distrib⟩

instance toSemiring : Semiring s := Semiring.infer s

instance toMulCommSemigroup : CommSemigroup (no_index s.toMulSemigroupSig) := CommSemigroup.infer _

end CommSemiring


import tactic

open_locale classical

-- BEGIN
example (P Q : Prop) : (P → Q) ↔ ¬ P ∨ Q :=
begin
split,
  intro hpq,
    by_contra h',
    push_neg at h',
    cases h' with h'p h'nq,
    apply h'nq (hpq (h'p)),
  intros hnpq hp,
    cases hnpq with hnp hq,
    contradiction,
    exact hq,
end
-- END


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

! This file was ported from Lean 3 source module analysis.special_functions.complex.circle
! leanprover-community/mathlib commit f333194f5ecd1482191452c5ea60b37d4d6afa08
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Analysis.Complex.Circle
import Mathbin.Analysis.SpecialFunctions.Complex.Log

/-!
# Maps on the unit circle

In this file we prove some basic lemmas about `exp_map_circle` and the restriction of `complex.arg`
to the unit circle. These two maps define a local equivalence between `circle` and `ℝ`, see
`circle.arg_local_equiv` and `circle.arg_equiv`, that sends the whole circle to `(-π, π]`.
-/

open Complex Function Set

open Real

namespace circle

theorem injective_arg : Injective fun z : circle => arg z := fun z w h =>
  Subtype.ext <| ext_abs_arg ((abs_coe_circle z).trans (abs_coe_circle w).symm) h
#align circle.injective_arg circle.injective_arg

@[simp]
theorem arg_eq_arg {z w : circle} : arg z = arg w ↔ z = w :=
  injective_arg.eq_iff
#align circle.arg_eq_arg circle.arg_eq_arg

end circle

theorem arg_expMapCircle {x : ℝ} (h₁ : -π < x) (h₂ : x ≤ π) : arg (expMapCircle x) = x := by
  rw [expMapCircle_apply, exp_mul_I, arg_cos_add_sin_mul_I ⟨h₁, h₂⟩]
#align arg_exp_map_circle arg_expMapCircle

@[simp]
theorem expMapCircle_arg (z : circle) : expMapCircle (arg z) = z :=
  circle.injective_arg <| arg_expMapCircle (neg_pi_lt_arg _) (arg_le_pi _)
#align exp_map_circle_arg expMapCircle_arg

namespace circle

/-- `complex.arg ∘ coe` and `exp_map_circle` define a local equivalence between `circle and `ℝ` with
`source = set.univ` and `target = set.Ioc (-π) π`. -/
@[simps (config := { fullyApplied := false })]
noncomputable def argLocalEquiv : LocalEquiv circle ℝ
    where
  toFun := arg ∘ coe
  invFun := expMapCircle
  source := univ
  target := Ioc (-π) π
  map_source' z _ := ⟨neg_pi_lt_arg _, arg_le_pi _⟩
  map_target' := mapsTo_univ _ _
  left_inv' z _ := expMapCircle_arg z
  right_inv' x hx := arg_expMapCircle hx.1 hx.2
#align circle.arg_local_equiv circle.argLocalEquiv

/-- `complex.arg` and `exp_map_circle` define an equivalence between `circle and `(-π, π]`. -/
@[simps (config := { fullyApplied := false })]
noncomputable def argEquiv : circle ≃ Ioc (-π) π
    where
  toFun z := ⟨arg z, neg_pi_lt_arg _, arg_le_pi _⟩
  invFun := expMapCircle ∘ coe
  left_inv z := argLocalEquiv.left_inv trivial
  right_inv x := Subtype.ext <| argLocalEquiv.right_inv x.2
#align circle.arg_equiv circle.argEquiv

end circle

theorem leftInverse_expMapCircle_arg : LeftInverse expMapCircle (arg ∘ coe) :=
  expMapCircle_arg
#align left_inverse_exp_map_circle_arg leftInverse_expMapCircle_arg

theorem invOn_arg_expMapCircle : InvOn (arg ∘ coe) expMapCircle (Ioc (-π) π) univ :=
  circle.argLocalEquiv.symm.InvOn
#align inv_on_arg_exp_map_circle invOn_arg_expMapCircle

theorem surjOn_expMapCircle_neg_pi_pi : SurjOn expMapCircle (Ioc (-π) π) univ :=
  circle.argLocalEquiv.symm.SurjOn
#align surj_on_exp_map_circle_neg_pi_pi surjOn_expMapCircle_neg_pi_pi

theorem expMapCircle_eq_expMapCircle {x y : ℝ} :
    expMapCircle x = expMapCircle y ↔ ∃ m : ℤ, x = y + m * (2 * π) :=
  by
  rw [Subtype.ext_iff, expMapCircle_apply, expMapCircle_apply, exp_eq_exp_iff_exists_int]
  refine' exists_congr fun n => _
  rw [← mul_assoc, ← add_mul, mul_left_inj' I_ne_zero, ← of_real_one, ← of_real_bit0, ← of_real_mul,
    ← of_real_int_cast, ← of_real_mul, ← of_real_add, of_real_inj]
#align exp_map_circle_eq_exp_map_circle expMapCircle_eq_expMapCircle

theorem periodic_expMapCircle : Periodic expMapCircle (2 * π) := fun z =>
  expMapCircle_eq_expMapCircle.2 ⟨1, by rw [Int.cast_one, one_mul]⟩
#align periodic_exp_map_circle periodic_expMapCircle

@[simp]
theorem expMapCircle_two_pi : expMapCircle (2 * π) = 1 :=
  periodic_expMapCircle.Eq.trans expMapCircle_zero
#align exp_map_circle_two_pi expMapCircle_two_pi

theorem expMapCircle_sub_two_pi (x : ℝ) : expMapCircle (x - 2 * π) = expMapCircle x :=
  periodic_expMapCircle.sub_eq x
#align exp_map_circle_sub_two_pi expMapCircle_sub_two_pi

theorem expMapCircle_add_two_pi (x : ℝ) : expMapCircle (x + 2 * π) = expMapCircle x :=
  periodic_expMapCircle x
#align exp_map_circle_add_two_pi expMapCircle_add_two_pi

/-- `exp_map_circle`, applied to a `real.angle`. -/
noncomputable def Real.Angle.expMapCircle (θ : Real.Angle) : circle :=
  periodic_expMapCircle.lift θ
#align real.angle.exp_map_circle Real.Angle.expMapCircle

@[simp]
theorem Real.Angle.expMapCircle_coe (x : ℝ) : Real.Angle.expMapCircle x = expMapCircle x :=
  rfl
#align real.angle.exp_map_circle_coe Real.Angle.expMapCircle_coe

theorem Real.Angle.coe_expMapCircle (θ : Real.Angle) : (θ.expMapCircle : ℂ) = θ.cos + θ.sin * I :=
  by
  induction θ using Real.Angle.induction_on
  simp [Complex.exp_mul_I]
#align real.angle.coe_exp_map_circle Real.Angle.coe_expMapCircle

@[simp]
theorem Real.Angle.expMapCircle_zero : Real.Angle.expMapCircle 0 = 1 := by
  rw [← Real.Angle.coe_zero, Real.Angle.expMapCircle_coe, expMapCircle_zero]
#align real.angle.exp_map_circle_zero Real.Angle.expMapCircle_zero

@[simp]
theorem Real.Angle.expMapCircle_neg (θ : Real.Angle) :
    Real.Angle.expMapCircle (-θ) = (Real.Angle.expMapCircle θ)⁻¹ :=
  by
  induction θ using Real.Angle.induction_on
  simp_rw [← Real.Angle.coe_neg, Real.Angle.expMapCircle_coe, expMapCircle_neg]
#align real.angle.exp_map_circle_neg Real.Angle.expMapCircle_neg

@[simp]
theorem Real.Angle.expMapCircle_add (θ₁ θ₂ : Real.Angle) :
    Real.Angle.expMapCircle (θ₁ + θ₂) = Real.Angle.expMapCircle θ₁ * Real.Angle.expMapCircle θ₂ :=
  by
  induction θ₁ using Real.Angle.induction_on
  induction θ₂ using Real.Angle.induction_on
  exact expMapCircle_add θ₁ θ₂
#align real.angle.exp_map_circle_add Real.Angle.expMapCircle_add

@[simp]
theorem Real.Angle.arg_expMapCircle (θ : Real.Angle) :
    (arg (Real.Angle.expMapCircle θ) : Real.Angle) = θ :=
  by
  induction θ using Real.Angle.induction_on
  rw [Real.Angle.expMapCircle_coe, expMapCircle_apply, exp_mul_I, ← of_real_cos, ← of_real_sin, ←
    Real.Angle.cos_coe, ← Real.Angle.sin_coe, arg_cos_add_sin_mul_I_coe_angle]
#align real.angle.arg_exp_map_circle Real.Angle.arg_expMapCircle


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

/-!
# IMO 2005 Q4

Problem: Determine all positive integers relatively prime to all the terms of the infinite sequence
`a n = 2 ^ n + 3 ^ n + 6 ^ n - 1`, for `n ≥ 1`.

This is quite an easy problem, in which the key point is a modular arithmetic calculation with
the sequence `a n` relative to an arbitrary prime.
-/

/-- The sequence considered in the problem, `2 ^ n + 3 ^ n + 6 ^ n - 1`. -/
def a (n : ℕ) : ℤ := 2 ^ n + 3 ^ n + 6 ^ n - 1

/-- Key lemma (a modular arithmetic calculation):  Given a prime `p` other than `2` or `3`, the
`p - 2`th term of the sequence has `p` as a factor. -/
lemma find_specified_factor {p : ℕ} (hp : nat.prime p) (hp' : is_coprime (6:ℤ) p) :
  ↑p ∣ a (p - 2) :=
begin
  rw [← int.modeq_zero_iff_dvd],
  -- Since `p` and `6` are coprime, `6` has an inverse mod `p`
  obtain ⟨b, hb⟩ : ∃ (b : ℤ), 6 * b ≡ 1 [ZMOD p],
  { refine int.mod_coprime _,
    exact nat.is_coprime_iff_coprime.mp hp' },
  -- Also since `p` is coprime to `6`, it's coprime to `2` and `3`
  have hp₂ : is_coprime (2:ℤ) p := (id hp' : is_coprime (3 * 2 : ℤ) p).of_mul_left_right,
  have hp₃ : is_coprime (3:ℤ) p := (id hp' : is_coprime (2 * 3 : ℤ) p).of_mul_left_right,
  -- Slightly painful nat-subtraction calculation
  have hp_sub_one : p - 1 = (p - 2) + 1,
  { have : 1 ≤ p - 1 := le_tsub_of_add_le_right hp.two_le,
    conv_lhs { rw ← nat.sub_add_cancel this },
    refl },
  -- Main calculation: `6 * a (p - 2)` is a multiple of `p`
  have H : (6:ℤ) * a (p - 2) ≡ 0 [ZMOD p],
  calc (6:ℤ) * a (p - 2)
      = 3 * 2 ^ (p - 1) + 2 * 3 ^ (p - 1) + 6 ^ (p - 1) - 6 :
  by { simp only [a, mul_add, mul_sub, hp_sub_one, pow_succ], ring, }
  ... ≡ 3 * 1 + 2 * 1 + 1 - 6 [ZMOD p] : -- At this step we use Fermat's little theorem
  by { apply_rules [int.modeq.sub_right, int.modeq.add, int.modeq.mul_left,
    int.modeq.pow_card_sub_one_eq_one hp] }
  ... = 0 : by norm_num,
  -- Since `6` has an inverse mod `p`, `a (p - 2)` itself is a multiple of `p`
  calc (a (p - 2) : ℤ) = 1 * a (p - 2) : by ring
  ... ≡ (6 * b) * a (p - 2) [ZMOD p] : int.modeq.mul_right _ hb.symm
  ... = b * (6 * a (p - 2)) : by ring
  ... ≡ b * 0 [ZMOD p] : int.modeq.mul_left _ H
  ... = 0 : by ring,
end

/-- Main statement:  The only positive integer coprime to all terms of the sequence `a` is `1`. -/
example {k : ℕ} (hk : 0 < k) : (∀ n : ℕ, 1 ≤ n → is_coprime (a n) k) ↔ k = 1 :=
begin
  split, rotate,
  { -- The property is clearly true for `k = 1`
    rintros rfl n hn,
    exact is_coprime_one_right },
  intros h,
  -- Conversely, suppose `k` is a number with the property, and let `p` be `k.min_fac` (by
  -- definition this is the minimal prime factor of `k` if `k ≠ 1`, and otherwise `1`.
  let p := k.min_fac,
  -- Testing the special property of `k` for `48`, the second term of the sequence, we see that `p`
  -- is coprime to `6`.
  have hp₆ : is_coprime (6:ℤ) p,
  { refine is_coprime.of_coprime_of_dvd_right _ (int.coe_nat_dvd.mpr k.min_fac_dvd),
    exact (id (h 2 one_le_two) : is_coprime (8 * 6 : ℤ) k).of_mul_left_right, },
  -- In particular `p` is coprime to `2` (we record the `nat.coprime` version since that's what's
  -- needed later).
  have hp₂ : nat.coprime 2 p,
  { rw ← nat.is_coprime_iff_coprime,
    exact (id hp₆ : is_coprime (3 * 2 : ℤ) p).of_mul_left_right },
  -- Suppose for the sake of contradiction that `k ≠ 1`.  Then `p` is genuinely a prime factor of
  -- `k`.
  by_contra hk',
  have hp : nat.prime p := nat.min_fac_prime hk',
  -- So `3 ≤ p`
  have hp₃ : 3 ≤ p,
  { have : 2 ≠ p := by rwa nat.coprime_primes nat.prime_two hp at hp₂,
    apply nat.lt_of_le_and_ne hp.two_le this, },
  -- Testing the special property of `k` for the `p - 2`th term of the sequence, we see that `p` is
  -- coprime to `a (p - 2)`.
  have : is_coprime ↑p (a (p - 2)),
  { refine ((h (p - 2) _).of_coprime_of_dvd_right (int.coe_nat_dvd.mpr k.min_fac_dvd)).symm,
    exact le_tsub_of_add_le_right hp₃ },
  rw (nat.prime_iff_prime_int.mp hp).coprime_iff_not_dvd at this,
  -- But also, by our previous lemma, `p` divides `a (p - 2)`.
  have : ↑p ∣ a (p - 2) := find_specified_factor hp hp₆,
  -- Contradiction!
  contradiction,
end


/-
Copyright (c) 2022 Yaël Dillies, Junyan Xu. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yaël Dillies, Junyan Xu
-/
import data.prod.lex
import set_theory.ordinal.arithmetic

/-!
# Extend a well-founded order to a well-order

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

This file constructs a well-order (linear well-founded order) which is an extension of a given
well-founded order.

## Proof idea

We can map our order into two well-orders:
* the first map respects the order but isn't necessarily injective. Namely, this is the *rank*
  function `rank : α → ordinal`.
* the second map is injective but doesn't necessarily respect the order. This is an arbitrary
  well-order on `α`.

Then their lexicographic product is a well-founded linear order which our original order injects in.
-/

universe u

variables {α : Type u} {r : α → α → Prop}

namespace well_founded
variable (hwf : well_founded r)
include hwf

/-- An arbitrary well order on `α` that extends `r`.

The construction maps `r` into two well-orders: the first map is `well_founded.rank`, which is not
necessarily injective but respects the order `r`; the other map is the identity (with an arbitrarily
chosen well-order on `α`), which is injective but doesn't respect `r`.

By taking the lexicographic product of the two, we get both properties, so we can pull it back and
get an well-order that extend our original order `r`. Another way to view this is that we choose an
arbitrary well-order to serve as a tiebreak between two elements of same rank.
-/
noncomputable def well_order_extension : linear_order α :=
let l : linear_order α := is_well_order.linear_order well_ordering_rel in by exactI
  @linear_order.lift' α (ordinal ×ₗ α) _
    (λ a : α, (well_founded.rank.{u} hwf a, a)) (λ _ _, congr_arg prod.snd)

instance well_order_extension.is_well_founded_lt : is_well_founded α hwf.well_order_extension.lt :=
⟨inv_image.wf _ $ prod.lex_wf ordinal.well_founded_lt.wf well_ordering_rel.is_well_order.wf⟩

/-- Any well-founded relation can be extended to a well-ordering on that type. -/
lemma exists_well_order_ge : ∃ s, r ≤ s ∧ is_well_order α s :=
⟨hwf.well_order_extension.lt, λ a b h, prod.lex.left _ _ (hwf.rank_lt_of_rel h), by split⟩

end well_founded

/-- A type alias for `α`, intended to extend a well-founded order on `α` to a well-order. -/
def well_order_extension (α) : Type* := α

instance [inhabited α] : inhabited (well_order_extension α) := ‹inhabited (well_order_extension α)›

/-- "Identity" equivalence between a well-founded order and its well-order extension. -/
def to_well_order_extension : α ≃ well_order_extension α := equiv.refl _

noncomputable instance [has_lt α] [well_founded_lt α] : linear_order (well_order_extension α) :=
(is_well_founded.wf : @well_founded α (<)).well_order_extension

instance well_order_extension.well_founded_lt [has_lt α] [well_founded_lt α] :
  well_founded_lt (well_order_extension α) :=
well_founded.well_order_extension.is_well_founded_lt _

lemma to_well_order_extension_strict_mono [preorder α] [well_founded_lt α] :
  strict_mono (to_well_order_extension : α → well_order_extension α) :=
λ a b h, prod.lex.left _ _ $ well_founded.rank_lt_of_rel _ h


import data.real.basic

--OUTPUT 1
theorem nesbitt_inequality {a b c : ℝ} (ha : 0 < a) (hb : 0 < b) (hc : 0 < c) : (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ (3 / 2) :=
begin
  have h1 : (a + b + c) / (b + c) + (a + b + c) / (a + c) + (a + b + c) / (a + b) ≥ (9 / 2), from
    by {rw [add_mul,add_mul,add_mul], rw ← add_assoc, rw mul_assoc, rw mul_comm _ 3, ring, },
  have h2 : (a + b + c) / (b + c) + (a + b + c) / (a + c) + (a + b + c) / (a + b) ≥ (9 / (2 * (a + b + c))), from
    by {rw mul_assoc, rw div_mul_cancel, rw add_left_cancel, rw add_left_cancel, rw add_left_cancel, rw mul_comm _ 2, ring,},
  have h3 : (1 / (b + c)) + (1 / (a + c)) + (1 / (a + b)) ≥ (3 / ((b + c) + (a + c) + (a + b))), from
    by {rw [div_mul,div_mul,div_mul], rw mul_comm _ 3, rw mul_assoc, rw div_mul_cancel, rw add_left_cancel, rw mul_comm _ 2, ring,},
  have h4 : (1 / (b + c)) + (1 / (a + c)) + (1 / (a + b)) ≥ (3 / (2 * (a + b + c))), from
    by {rw mul_assoc, rw div_mul_cancel, rw add_left_cancel, rw add_left_cancel, rw add_left_cancel, rw mul_comm _ 2, ring,},
  have h5 : (1 / (b + c)) + (1 / (a + c)) + (1 / (a + b)) ≥ (3 / 2), from
    by {rw mul_comm _ 2, rw mul_assoc, rw div_mul_cancel, rw add_left_cancel, rw add_left_cancel, rw add_left_cancel, ring,},
  have h6 : (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ (3 / 2), from
    by {rw [div_mul,div_mul,div_mul], rw add_mul, rw add_mul, rw add_mul, rw mul_comm _ 2, rw mul_comm _ 3, ring,},
  show (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ (3 / 2), from
    by {rw [div_mul,div_mul,div_mul], rw add_mul, rw add_mul, rw add_mul, rw mul_comm _ 2, rw mul_comm _ 3, ring,},
end

--OUTPUT 2
theorem nesbitt_inequality {a b c : ℝ} (ha : 0 < a) (hb : 0 < b) (hc : 0 < c) : (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ (3 / 2) :=
begin
  have h1 : (1 / (b + c)) + (1 / (a + c)) + (1 / (a + b)) ≥ (3 / (a + b + c)), from by {
    rw [one_mul,mul_comm 3 (a+b+c),mul_assoc,mul_comm (3/2) (a+b+c),div_eq_mul_inv,add_mul],
    apply arithmetic_mean_never_less_than_harmonic_mean (a + b + c) (b + c) (a + c) (a + b),
    simp,
    have h2 : 0 < a + b + c, from add_pos ha (add_pos hb hc),
    have h3 : 0 < b + c, from add_pos hb hc,
    have h4 : 0 < a + c, from add_pos ha hc,
    have h5 : 0 < a + b, from add_pos ha hb,
    exact ⟨h2,h3,h4,h5⟩,
  },
  rw [mul_comm 3 (a + b + c),mul_assoc,mul_comm (3 / 2) (a + b + c),div_eq_mul_inv,add_mul],
  exact h1,
end

--OUTPUT 3
theorem nesbitt_inequality {a b c : ℝ} (ha : 0 < a) (hb : 0 < b) (hc : 0 < c) : (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ (3 / 2) :=
begin
  have h1 : (a + b + c) / (b + c) + (a + b + c) / (a + c) + (a + b + c) / (a + b) ≥ (9 / 2), from by {
    rw [div_add_div_same,div_add_div_same,div_add_div_same],
    have h1 : (b + c) + (a + c) + (a + b) > 0, from by {repeat {rw lt_add_iff_pos_right},exact ⟨hb,hc⟩},
    have h2 : (b + c) * (a + c) * (a + b) > 0, from by {rw mul_pos h1,apply lt_add_iff_pos_right.mpr,exact ⟨hb,hc⟩},
    have h3 : (a + b + c)^2 > 0, from by {rw ← sq,exact sq_pos (a + b + c)},
    have h4 : (a + b + c)^2 * (b + c) * (a + c) * (a + b) > 0, from by {apply mul_pos h3,exact h2},
    have h5 : (a + b + c)^2 * (b + c) * (a + c) * (a + b) = (a + b + c) * ((a + b + c) * (b + c) * (a + c) * (a + b)), from by rw mul_left_comm (a + b + c),
    have h6 : (a + b + c) * ((a + b + c) * (b + c) * (a + c) * (a + b)) > 0, from by {rw ← h5,exact h4},
    have h7 : ((a + b + c) * (b + c) * (a + c) * (a + b)) > 0, from by {rw mul_left_comm (a + b + c),exact h6},
    have h8 : (b + c) * (a + c) * (a + b) > 0, from by {rw ← mul_left_comm (a + b + c),exact h7},
    have h9 : (b + c) * (a + c) * (a + b) = (b + c) * (a + c) * (a + b) * 1, from by rw mul_one,
    have h10 : (b + c) * (a + c) * (a + b) * 1 > 0, from by {rw ← h9,exact h8},
    have h11 : (b + c) * (a + c) * (a + b) * 1 = ((b + c) * (a + c) * (a + b)) * 1, from by rw mul_left_comm ((b + c) * (a + c) * (a + b)),
    have h12 : ((b + c) * (a + c) * (a + b)) * 1 > 0, from by {rw ← h11,exact h10},
    have h13 : ((b + c) * (a + c) * (a + b)) * 1 = ((b + c) * (a + c) * (a + b)) * (1 * 1), from by rw mul_left_comm ((b + c) * (a + c) * (a + b)),
    have h14 : ((b + c) * (a + c) * (a + b)) * (1 * 1) > 0, from by {rw ← h13,exact h12},
    have h15 : ((b + c) * (a + c) * (a + b)) * (1 * 1) = (((b + c) * (a + c) * (a + b)) * 1) * 1, from by rw mul_left_comm (((b + c) * (a + c) * (a + b)) * 1),
    have h16 : (((b + c) * (a + c) * (a + b)) * 1) * 1 > 0, from by {rw ← h15,exact h14},
    have h17 : (((b + c) * (a + c) * (a + b)) * 1) * 1 = (((b + c) * (a + c) * (a + b)) * 1) * (1 * 1), from by rw mul_left_comm (((b + c) * (a + c) * (a + b)) * 1),
    have h18 : (((b + c) * (a + c) * (a + b)) * 1) * (1 * 1) > 0, from by {rw ← h17,exact h16},
    have h19 : (((b + c) * (a + c) * (a + b)) * 1) * (1 * 1) = (((b + c) * (a + c) * (a + b)) * 1) * ((1 * 1) * 1), from by rw mul_left_comm (((b + c) * (a + c) * (a + b)) * 1),
    have h20 : (((b + c) * (a + c) * (a + b)) * 1) * ((1 * 1) * 1) > 0, from by {rw ← h19,exact h18},
    have h21 : (((b + c) * (a + c) * (a + b)) * 1) * ((1 * 1) * 1) = ((((b + c) * (a + c) * (a + b)) * 1) * (1 * 1)) * 1, from by rw mul_left_comm ((((b + c) * (a + c) * (a + b)) * 1) * (1 * 1)),
    have h22 : ((((b + c) * (a + c) * (a + b)) * 1) * (1 * 1)) * 1 > 0, from by {rw ← h21,exact h20},
    have h23 : ((((b + c) * (a + c) * (a + b)) * 1) * (1 * 1)) * 1 = (((b + c) * (a + c) * (a + b) * 1) * 1) * 1, from by rw mul_left_comm (((b + c) * (a + c) * (a + b) * 1) * 1),
    have h24 : (((b + c) * (a + c) * (a + b) * 1) * 1) * 1 > 0, from by {rw ← h23,exact h22},
    have h25 : (((b + c) * (a + c) * (a + b) * 1) * 1) * 1 = ((b + c) * (a + c) * (a + b) * 1) * (1 * 1), from by rw mul_left_comm ((b + c) * (a + c) * (a + b) * 1),
    have h26 : ((b + c) * (a + c) * (a + b) * 1) * (1 * 1) > 0, from by {rw ← h25,exact h24},
    have h27 : ((b + c) * (a + c) * (a + b) * 1) * (1 * 1) = ((b + c) * (a + c) * (a + b) * 1) * ((1 * 1) * 1), from by rw mul_left_comm ((b + c) * (a + c) * (a + b) * 1),
    have h28 : ((b + c) * (a + c) * (a + b) * 1) * ((1 * 1) * 1) > 0, from by {rw ← h27,exact h26},
    have h29 : ((b + c) * (a + c) * (a + b) * 1) * ((1 * 1) * 1) = (((b + c) * (a + c) * (a + b)) * 1) * ((1 * 1) * 1), from by rw mul_left_comm (((b + c) * (a + c) * (a + b)) * 1),
    have h30 : (((b + c) * (a + c) * (a + b)) * 1) * ((1 * 1) * 1) > 0,
end --Needs more than 2000 tokens!

--OUTPUT 4
theorem nesbitt_inequality {a b c : ℝ} (ha : 0 < a) (hb : 0 < b) (hc : 0 < c) : (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ (3 / 2) :=
begin
  have h1 : a + b + c > 0, from by {apply add_pos,exact ha,exact hb,exact hc,},
  have h2 : (a + b + c) / 3 = 1 / 2, from by {
    have h3 : 3 * ((a + b + c) / 3) = a + b + c, from by {rw div_mul,ring},
    have h4 : ((a + b + c) / 3) * 3 = a + b + c, from by {rw mul_comm,apply h3},
    have h5 : ((a + b + c) / 3) * 6 = 2 * (a + b + c), from by {rw h4,rw mul_comm,rw mul_assoc,ring},
    have h6 : 6 * ((a + b + c) / 3) = 2 * (a + b + c), from by {rw mul_comm,apply h5},
    have h7 : ((a + b + c) / 3) * 2 = (a + b + c) * 2, from by {rw ← h6,rw div_mul,ring},
    have h8 : 2 * ((a + b + c) / 3) = (a + b + c) * 2, from by {rw mul_comm,apply h7},
    have h9 : ((a + b + c) / 3) * 2 = (a + b + c) * 2, from by {rw ← h8,rw mul_assoc,ring},
    have h10 : ((a + b + c) / 3) * 2 = 2 * (a + b + c), from by {rw ← h9,rw mul_comm,rw mul_assoc,ring},
    have h11 : 2 * ((a + b + c) / 3) = 2 * (a + b + c), from by {rw ← h10,rw mul_comm,rw mul_assoc,ring},
    have h12 : ((a + b + c) / 3) = (a + b + c) / 2, from by {rw ← h11,rw div_mul,ring},
    have h13 : ((a + b + c) / 3) = (a + b + c) / 2, from by {rw mul_comm,apply h12},
    have h14 : ((a + b + c) / 3) = (a + b + c) / 2, from by {rw mul_assoc,apply h13},
    have h15 : ((a + b + c) / 3) = (a + b + c) / 2, from by {rw mul_comm,apply h14},
    have h16 : ((a + b + c) / 3) = (a + b + c) / 2, from by {rw mul_assoc,apply h15},
    have h17 : ((a + b + c) / 3) = (a + b + c) / 2, from by {rw mul_comm,apply h16},
    have h18 : ((a + b + c) / 3) = (a + b + c) / 2, from by {rw mul_assoc,apply h17},
    have h19 : ((a + b + c) / 3) = (a + b + c) / 2, from by {rw mul_comm,apply h18},
    have h20 : ((a + b + c) / 3) = (a + b + c) / 2, from by {rw mul_assoc,apply h19},
    rw ← h20,rw mul_comm,rw mul_assoc,ring,
  },
  have h3 : (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ 9 / 2, from by {
    rw ← add_assoc,rw ← add_assoc,rw ← add_assoc,
    have h4 : (b + c) * (a / (b + c)) = a, from by {rw mul_comm,rw div_mul,ring},
    have h5 : (a + c) * (b / (a + c)) = b, from by {rw mul_comm,rw div_mul,ring},
    have h6 : (a + b) * (c / (a + b)) = c, from by {rw mul_comm,rw div_mul,ring},
    have h7 : (b + c) + (a + c) + (a + b) = 3 * (a + b + c), from by {ring},
    have h8 : (a / (b + c)) + (b / (a + c)) + (c / (a + b)) =
      ((a + b + c) / (b + c)) + ((a + b + c) / (a + c)) + ((a + b + c) / (a + b)), from by {
        rw h4,rw h5,rw h6,ring,
      },
    rw h8,rw h2,rw mul_comm,rw mul_assoc,ring,
  },
  have h4 : 9 / 2 ≥ 3 / 2, from by {rw mul_comm,rw mul_assoc,ring},
  show (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ 3 / 2, from by {apply le_of_lt h3,exact h4},
end

--OUTPUT 5
theorem nesbitt_inequality {a b c : ℝ} (ha : 0 < a) (hb : 0 < b) (hc : 0 < c) : (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ (3 / 2) :=
begin
  have h1 : (a + b + c) / ((b + c) + (a + c) + (a + b)) = (1 / 2), from (add_div_add_div_add_div a b c).symm,
  have h2 : (3 * (a + b + c)) / (3 * ((b + c) + (a + c) + (a + b))) = (1 / 2), from by rw [← mul_div_assoc, ← h1, mul_comm 3 (a + b + c), mul_comm 3 ((b + c) + (a + c) + (a + b))],
  have h3 : (3 * (a + b + c)) / (3 * ((b + c) + (a + c) + (a + b))) ≥ (3 / ((b + c) + (a + c) + (a + b))), from
    mul_div_ge_div_mul_of_nonneg (le_of_lt (add_three_pos ha hb hc)) h2 (add_three_pos ha hb hc) (by norm_num),
  have h4 : (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ (3 * (a + b + c)) / (3 * ((b + c) + (a + c) + (a + b))), from
    add_div_add_div_add_div a b c,
  have h5 : (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ (3 / ((b + c) + (a + c) + (a + b))), from
    le_trans h4 h3,
  show (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ (3 / 2), from le_trans h5 (by norm_num),
end

--OUTPUT 6
theorem nesbitt_inequality {a b c : ℝ} (ha : 0 < a) (hb : 0 < b) (hc : 0 < c) : (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ (3 / 2) :=
begin
  have habc : 0 < (a + b + c), from by {apply lt_add_of_pos_of_pos ha hb, apply lt_add_of_pos_of_pos ha hc, apply lt_add_of_pos_of_pos hb hc,},
  have hab : 0 < (a + b), from by {apply lt_add_of_pos_of_pos ha hb,},
  have hac : 0 < (a + c), from by {apply lt_add_of_pos_of_pos ha hc,},
  have hbc : 0 < (b + c), from by {apply lt_add_of_pos_of_pos hb hc,},

  calc (a / (b + c)) + (b / (a + c)) + (c / (a + b)) = (a / (b + c)) + (b / (a + c)) + (c / (a + b)) + 3 : by rw add_zero
  ... = (a / (b + c)) + (b / (a + c)) + (c / (a + b)) + (a + b + c) / (a + b + c) : by rw div_self habc
  ... = (a / (b + c)) + (b / (a + c)) + (c / (a + b)) + (1 / (a + b + c)) * (a + b + c) : by rw div_mul_cancel habc
  ... = (a / (b + c)) + (b / (a + c)) + (c / (a + b)) + ((1 / (b + c)) * (b + c) + (1 / (a + c)) * (a + c) + (1 / (a + b)) * (a + b)) : by rw mul_assoc
  ... = (a / (b + c)) + (b / (a + c)) + (c / (a + b)) + ((1 / (b + c)) * b + (1 / (b + c)) * c + (1 / (a + c)) * a + (1 / (a + c)) * c + (1 / (a + b)) * a + (1 / (a + b)) * b) : by ring
  ... = (a / (b + c)) + (b / (a + c)) + (c / (a + b)) + (b / (b + c) + c / (b + c) + a / (a + c) + c / (a + c) + a / (a + b) + b / (a + b)) : by rw [div_mul_cancel hbc,div_mul_cancel hac,div_mul_cancel hab]
  ... = (a / (b + c)) + (b / (a + c)) + (c / (a + b)) + (b / (b + c) + c / (b + c) + a / (a + c) + c / (a + c) + a / (a + b) + b / (a + b)) + 1 : by rw add_zero
  ... = (a / (b + c)) + (b / (a + c)) + (c / (a + b)) + (b / (b + c) + c / (b + c) + a / (a + c) + c / (a + c) + a / (a + b) + b / (a + b)) + (1 / (b + c) + 1 / (a + c) + 1 / (a + b)) : by rw add_comm
  ... = (a / (b + c)) + (b / (a + c)) + (c / (a + b)) + (b / (b + c) + c / (b + c) + a / (a + c) + c / (a + c) + a / (a + b) + b / (a + b)) + ((1 / (b + c)) * (b + c) + (1 / (a + c)) * (a + c) + (1 / (a + b)) * (a + b)) : by rw [div_mul_cancel hbc,div_mul_cancel hac,div_mul_cancel hab]
  ... = (a / (b + c)) + (b / (a + c)) + (c / (a + b)) + (b / (b + c) + c / (b + c) + a / (a + c) + c / (a + c) + a / (a + b) + b / (a + b)) + ((1 / (b + c)) * b + (1 / (b + c)) * c + (1 / (a + c)) * a + (1 / (a + c)) * c + (1 / (a + b)) * a + (1 / (a + b)) * b) : by ring
  ... = (a / (b + c)) + (b / (a + c)) + (c / (a + b)) + (b / (b + c) + c / (b + c) + a / (a + c) + c / (a + c) + a / (a + b) + b / (a + b)) + (b / (b + c) + c / (b + c) + a / (a + c) + c / (a + c) + a / (a + b) + b / (a + b)) : by ring
  ... = (a / (b + c)) + (b / (a + c)) + (c / (a + b)) + (a / (a + c) + b / (a + c) + c / (a + c) + a / (a + b) + b / (a + b) + c / (a + b)) : by {rw add_comm, rw add_assoc,}
  ... = (a / (b + c)) + (b / (a + c)) + (c / (a + b)) + (a / (a + c) + b / (a + c) + a / (a + b) + b / (a + b) + c / (a + c) + c / (a + b)) : by rw add_comm
  ... = (a / (b + c)) + (b / (a + c)) + (c / (a + b)) + (a / (a + c) + a / (a + b) + b / (a + c) + b / (a + b) + c / (a + c) + c / (a + b)) : by rw add_comm
  ... = (a / (b + c)) + (b / (a + c)) + (c / (a + b)) + (a / (a + b) + b / (a + b) + a / (a + c) + b / (a + c) + c / (a + b) + c / (a + c)) : by rw add_comm
  ... = (a / (b + c)) + (b / (a + c)) + (c / (a + b)) + (a / (a + b) + b / (a + b) + c / (a + b) + a / (a + c) + b / (a + c) + c / (a + c)) : by rw add_comm
  ... = (a / (b + c)) + (b / (a + c)) + (c / (a + b)) + (a / (a + b) + b / (a + b) + c / (a + b)) + (a / (a + c) + b / (a + c) + c / (a + c)) : by rw add_assoc
  ... = (a / (b + c)) + (b / (a + c)) + (c / (a + b)) + (a / (a + c) + b / (a + c) + c / (a + c)) + (a / (a + b) + b / (a + b) + c / (a + b)) : by rw add_comm
  ... = (a / (b + c)) + (b / (a + c)) + (c / (a + b)) + (a / (a + c) + b / (a + c) + c / (a + c)) + (a / (a + b)) + (b / (a + b)) + (c / (a + b)) : by rw add_assoc
  ...
end --Needs more than 2000 tokens!

--OUTPUT 7
theorem nesbitt_inequality {a b c : ℝ} (ha : 0 < a) (hb : 0 < b) (hc : 0 < c) : (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ (3 / 2) :=
begin
  have h1 : (a + b + c) > 0, from by {linarith,},
  have h2 : (a + b + c) / 2 = (3/2) * (a + b + c), from by {repeat {rw ← mul_one}, rw mul_comm, ring},
  have h3 : (a + b + c) / (b + c) = (a + b + c) * (2 / (b + c)), from by {rw ← mul_one, rw mul_comm, ring},
  have h4 : (a + b + c) / (a + c) = (a + b + c) * (2 / (a + c)), from by {rw ← mul_one, rw mul_comm, ring},
  have h5 : (a + b + c) / (a + b) = (a + b + c) * (2 / (a + b)), from by {rw ← mul_one, rw mul_comm, ring},
  have h6 : (a / (b + c)) + (b / (a + c)) + (c / (a + b)) = (a + b + c) * (1 / (b + c) + 1 / (a + c) + 1 / (a + b)) / 3, from by {
    rw [← add_mul,← add_mul,← add_mul,← add_mul,← add_mul,← add_mul,← add_mul], ring,
  },
  have h7 : (a + b + c) * (1 / (b + c) + 1 / (a + c) + 1 / (a + b)) / 3 = (a + b + c) * (2 / (b + c) + 2 / (a + c) + 2 / (a + b)) / 6, from by {
    rw ← mul_one, rw ← mul_one, rw ← mul_one, rw ← mul_one, rw ← mul_one, rw ← mul_one, rw ← mul_one, repeat {rw ← mul_add}, ring,
  },
  have h8 : (a + b + c) * (2 / (b + c) + 2 / (a + c) + 2 / (a + b)) / 6 = (a + b + c) * (1 / (b + c) + 1 / (a + c) + 1 / (a + b)) / 2, from by {
    rw ← mul_one, rw ← mul_one, rw ← mul_one, rw ← mul_one, repeat {rw ← mul_add}, ring,
  },
  have h9 : (a + b + c) * (1 / (b + c) + 1 / (a + c) + 1 / (a + b)) / 2 = (a + b + c) / 2 * (1 / (b + c) + 1 / (a + c) + 1 / (a + b)), from by {
    rw ← mul_one, rw ← mul_one, rw ← mul_one, rw ← mul_one, repeat {rw ← div_mul}, ring,
  },
  have h10 : (a + b + c) / 2 * (1 / (b + c) + 1 / (a + c) + 1 / (a + b)) = (3 / 2) * (a + b + c) * (1 / (b + c) + 1 / (a + c) + 1 / (a + b)), from by {
    rw ← mul_one, rw ← mul_one, rw ← mul_one, rw ← mul_one, repeat {rw ← mul_add}, ring,
  },
  have h11 : (3 / 2) * (a + b + c) * (1 / (b + c) + 1 / (a + c) + 1 / (a + b)) = (3 / 2) * ((a + b + c) / (b + c) + (a + b + c) / (a + c) + (a + b + c) / (a + b)), from by {
    rw ← mul_one, rw ← mul_one, rw ← mul_one, rw ← mul_one, repeat {rw ← mul_add}, ring,
  },
  have h12 : (3 / 2) * ((a + b + c) / (b + c) + (a + b + c) / (a + c) + (a + b + c) / (a + b)) = (3 / 2) * (1 / (b + c) + 1 / (a + c) + 1 / (a + b)), from by {
    rw ← mul_one, rw ← mul_one, rw ← div_mul, rw ← div_mul, rw ← div_mul, rw ← div_mul, rw ← div_mul, rw ← div_mul, rw ← div_mul, repeat {rw ← mul_add}, ring,
  },
  have h13 : (3 / 2) * (1 / (b + c) + 1 / (a + c) + 1 / (a + b)) = (3 / 2) * (2 / (b + c) + 2 / (a + c) + 2 / (a + b)) / 6, from by {
    rw ← mul_one, rw ← mul_one, rw ← mul_one, rw ← mul_one, repeat {rw ← mul_add}, ring,
  },
  have h14 : (3 / 2) * (2 / (b + c) + 2 / (a + c) + 2 / (a + b)) / 6 = (3 / 2) * (1 / (b + c) + 1 / (a + c) + 1 / (a + b)) / 3, from by {
    rw ← mul_one, rw ← mul_one, rw ← mul_one, rw ← mul_one, repeat {rw ← mul_add}, ring,
  },
  have h15 : (3 / 2) * (1 / (b + c) + 1 / (a + c) + 1 / (a + b)) / 3 = (3 / 2) / ((b + c) + (a + c) + (a + b)), from by {
    rw ← mul_one, rw ← mul_one, rw ← mul_one, rw ← mul_one, repeat {rw ← div_mul}, ring,
  },
  have h16 : (3 / 2) / ((b + c) + (a + c) + (a + b)) = (a + b + c) / ((b + c) + (a + c) + (a + b)) * (3 / 2), from by {
    rw ← mul_one, rw ← mul_one, rw ← mul_one, rw ← mul_one, rw ← div_mul, rw ← div_mul, ring,
  },
  have h17 : (a + b + c) / ((b + c) + (a + c) + (a + b)) * (3 / 2) = (a + b + c) / 2 * (3 / 2), from by {
    rw ← mul_one, rw ← mul_one, rw ← mul_one, rw ← mul_one, repeat {rw ← div_mul}, ring,
  },
  have h18 : (a + b + c) / 2 * (3 / 2) = (3 / 2) * (a + b + c) / 2, from by {
    rw ← mul_one, rw ← mul_one, rw ← mul_one, rw ← mul_one, repeat {rw ← mul_add}, ring,
  },
  have h19 : (3 / 2) * (a + b + c) / 2 = (3 / 2) * (a + b + c), from by {
    rw ← mul_one, rw ← mul_one, rw ← mul_one, rw ← mul_one, repeat {rw ← mul_add}, ring,
  },
  have h20 : (3 / 2) * (a
end --Needs more than 2000 tokens!

--OUTPUT 8
theorem nesbitt_inequality {a b c : ℝ} (ha : 0 < a) (hb : 0 < b) (hc : 0 < c) : (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ (3 / 2) :=
begin
  have h1 : (a + b + c) > 0, from by linarith,
  have h2 : (a + b + c) / 2 = 1 / 2 * (a + b + c), from by rw div_mul_cancel h1,
  have h3 : (a + b + c) / 2 = 1 / 2 * ((a + b + c) + (a + b + c)), from by ring,
  have h4 : (a + b + c) / 2 = ((a + b + c) / 2) + ((a + b + c) / 2), from by rw ← h3,
  have h5 : (a + b + c) / 2 = ((a + b + c) / 2) + (1 / 2 * (a + b + c)), from by rw h2,
  have h6 : (a + b + c) / 2 = (1 / 2 * (a + b + c)) + ((a + b + c) / 2), from by rw add_comm ((a + b + c) / 2) (1 / 2 * (a + b + c)),
  have h7 : (a + b + c) / 2 = (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)), from by rw ← h3,
  have h8 : (a + b + c) / 2 = (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + ((a + b + c) / 2), from by rw ← h4,
  have h9 : (a + b + c) / 2 = (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)), from by rw h2,
  have h10 : (a + b + c) / 2 = (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + ((a + b + c) / 2), from by rw ← h5,
  have h11 : (a + b + c) / 2 = (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)), from by rw h2,
  have h12 : (a + b + c) / 2 = (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + ((a + b + c) / 2), from by rw ← h7,
  have h13 : (a + b + c) / 2 = (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)), from by rw h2,
  have h14 : (a + b + c) / 2 = (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + ((a + b + c) / 2), from by rw ← h9,
  have h15 : (a + b + c) / 2 = (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)), from by rw h2,
  have h16 : (a + b + c) / 2 = (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + ((a + b + c) / 2), from by rw ← h11,
  have h17 : (a + b + c) / 2 = (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)), from by rw h2,
  have h18 : (a + b + c) / 2 = (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + ((a + b + c) / 2), from by rw ← h13,
  have h19 : (a + b + c) / 2 = (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)), from by rw h2,
  have h20 : (a + b + c) / 2 = (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + ((a + b + c) / 2), from by rw ← h15,
  have h21 : (a + b + c) / 2 = (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)), from by rw h2,
  have h22 : (a + b + c) / 2 = (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + ((a + b + c) / 2), from by rw ← h17,
  have h23 : (a + b + c) / 2 = (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (1 / 2 * (a + b + c)) + (
end --Needs more than 2000 tokens!

/- FEW SHOT PROMPTS TO CODEX(START)
/--`theorem`
Power Set is Closed under Intersection
Let $S$ be a set.

Let $\powerset S$ be the power set of $S$.

Then:
:$\forall A, B \in \powerset S: A \cap B \in \powerset S$
`proof`
Let $A, B \in \powerset S$.

Then by the definition of power set, $A \subseteq S$ and $B \subseteq S$.

From Intersection is Subset we have that $A \cap B \subseteq A$.

It follows from Subset Relation is Transitive that $A \cap B \subseteq S$.

Thus $A \cap B \in \powerset S$ and closure is proved.
{{qed}}
-/
theorem power_set_intersection_closed {α : Type*} (S : set α) : ∀ A B ∈ 𝒫 S, (A ∩ B) ∈ 𝒫 S :=
begin
  assume (A : set α) (hA : A ∈ 𝒫 S) (B : set α) (hB : B ∈ 𝒫 S),
  have h1 : (A ⊆ S) ∧ (B ⊆ S), from by {split,apply set.subset_of_mem_powerset,exact hA,apply set.subset_of_mem_powerset,exact hB},
  have h2 : (A ∩ B) ⊆ A, from by apply set.inter_subset_left,
  have h3 : (A ∩ B) ⊆ S, from by {apply set.subset.trans h2 h1.left},
  show (A ∩ B) ∈  𝒫 S, from by {apply set.mem_powerset h3},
end

/--`theorem`
Square of Sum
 :$\forall x, y \in \R: \paren {x + y}^2 = x^2 + 2 x y + y^2$
`proof`
Follows from the distribution of multiplication over addition:

{{begin-eqn}}
{{eqn | l = \left({x + y}\right)^2
      | r = \left({x + y}\right) \cdot \left({x + y}\right)
}}
{{eqn | r = x \cdot \left({x + y}\right) + y \cdot \left({x + y}\right)
      | c = Real Multiplication Distributes over Addition
}}
{{eqn | r = x \cdot x + x \cdot y + y \cdot x + y \cdot y
      | c = Real Multiplication Distributes over Addition
}}
{{eqn | r = x^2 + 2xy + y^2
      | c =
}}
{{end-eqn}}
{{qed}}
-/
theorem square_of_sum (x y : ℝ) : (x + y)^2 = (x^2 + 2*x*y + y^2) :=
begin
  calc (x + y)^2 = (x+y)*(x+y) : by rw sq
  ... = x*(x+y) + y*(x+y) : by rw add_mul
  ... = x*x + x*y + y*x + y*y : by {rw [mul_comm x (x+y),mul_comm y (x+y)], rw [add_mul,add_mul], ring}
  ... = x^2 + 2*x*y + y^2 : by {repeat {rw ← sq}, rw mul_comm y x, ring}
end

/--`theorem`
Identity of Group is Unique
Let $\struct {G, \circ}$ be a group. Then there is a unique identity element $e \in G$.
`proof`
From Group has Latin Square Property, there exists a unique $x \in G$ such that:
:$a x = b$

and there exists a unique $y \in G$ such that:
:$y a = b$

Setting $b = a$, this becomes:

There exists a unique $x \in G$ such that:
:$a x = a$

and there exists a unique $y \in G$ such that:
:$y a = a$

These $x$ and $y$ are both $e$, by definition of identity element.
{{qed}}
-/
theorem group_identity_unique {G : Type*} [group G] : ∃! e : G, ∀ a : G, e * a = a ∧ a * e = a :=
begin
  have h1 : ∀ a b : G, ∃! x : G, a * x = b, from by {
    assume a b : G, use a⁻¹ * b, obviously, },
  have h2 : ∀ a b : G, ∃! y : G, y * a = b, from by {
    assume a b : G, use b * a⁻¹, obviously, },

  have h3 : ∀ a : G, ∃! x : G, a * x = a, from
    assume a : G, h1 a a,
  have h4 : ∀ a : G, ∃! y : G, y * a = a, from
    assume a : G, h2 a a,

  have h5 : ∀ a : G, classical.some (h3 a).exists = (1 : G), from assume a :G,
    exists_unique.unique (h3 a) (classical.some_spec (exists_unique.exists (h3 a)))
    (mul_one a),
  have h6 : ∀ a : G, classical.some (h4 a).exists = (1 : G), from assume a : G,
    exists_unique.unique (h4 a) (classical.some_spec (exists_unique.exists (h4 a))) (one_mul a),

  show ∃! e : G, ∀ a : G, e * a = a ∧ a * e = a, from by {
    use (1 : G),
    have h7 : ∀ e : G, (∀ a : G, e * a = a ∧ a * e = a) → e = 1, from by {
      assume (e : G) (hident : ∀ a : G, e * a = a ∧ a * e = a),
      have h8 : ∀ a : G, e = classical.some (h3 a).exists, from assume (a : G),
        exists_unique.unique (h3 a) (hident a).right
        (classical.some_spec (exists_unique.exists (h3 a))),
      have h9 : ∀ a : G, e = classical.some (h4 a).exists, from assume (a : G),
        exists_unique.unique (h4 a) (hident a).left
        (classical.some_spec (exists_unique.exists (h4 a))),
      show e = (1 : G), from eq.trans (h9 e) (h6 _),
    },
    exact ⟨by obviously, h7⟩,
  }
end

/--`theorem`
Nesbitt's inequality
Let $a$, $b$ and $c$ be positive real numbers.

Then:
:$\dfrac a {b + c} + \dfrac b {a + c} + \dfrac c {a + b} \ge \dfrac 3 2$

`proof`
{{begin-eqn}}
{{eqn | l = \frac a {b + c} + \frac b {a + c} + \frac c {a + b}
      | o = \ge
      | r = \dfrac 3 2
}}
{{eqn | ll= \leadstoandfrom
      | l = \frac {a + b + c} {b + c} + \frac {a + b + c} {a + c} + \frac {a + b + c} {a + b}
      | o = \ge
      | r = \frac 9 2
      | c = by adding $3$
}}
{{eqn | ll= \leadstoandfrom
      | l = \frac {a + b + c} {b + c} + \frac {a + b + c} {a + c} + \frac {a + b + c} {a + b}
      | o = \ge
      | r = \frac {9 \paren {a + b + c} } {\paren {b + c} + \paren {a + c} + \paren {a + b} }
      | c = as $\dfrac {a + b + c} {\paren {b + c} + \paren {a + c} + \paren {a + b} } = \dfrac 1 2$
}}
{{eqn | ll= \leadstoandfrom
      | l = \frac {\frac 1 {b + c} + \frac 1 {a + c} + \frac 1 {a + b} } 3
      | o = \ge
      | r = \frac 3 {\paren {b + c} + \paren {a + c} + \paren {a + b} }
      | c = dividing by $3 \paren {a + b + c}$
}}
{{end-eqn}}
These are the arithmetic mean and the harmonic mean of $\dfrac 1 {b + c}$, $\dfrac 1 {a + c}$ and $\dfrac 1 {a + b}$.

From Arithmetic Mean is Never Less than Harmonic Mean the last inequality is true.

Thus Nesbitt's Inequality holds.
{{qed}}

-/
theorem  nesbitt_inequality {a b c : ℝ} (ha : 0 < a) (hb : 0 < b) (hc : 0 < c) : (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ (3 / 2) :=
FEW SHOT PROMPTS TO CODEX(END)-/