import divided_powers.rat_algebra
import basic_lemmas

open finset

namespace divided_powers

variables {A : Type*} [comm_ring A] {I : ideal A} (hI : divided_powers I)

namespace ideal_add

/-- Some complicated numerical coefficients for the proof of ideal_add.dpow_comp -/
private
def cnik := λ (n i : ℕ) (k : multiset ℕ),
    ite (i = 0)
        (mchoose (multiset.count i k) n)
        (ite (i = n)
          (mchoose (multiset.count i k) n)
          ((multiset.count i k).factorial * (mchoose (multiset.count i k) i) * (mchoose (multiset.count i k) (n - i))))

/-- Divided power function on a sup of two ideals -/
noncomputable
def dpow {J : ideal A} (hJ : divided_powers J) :
  ℕ → A → A := λ n,
function.extend
  (λ ⟨a, b⟩, (a : A) + (b : A) : I × J → A)
  (λ ⟨a, b⟩, finset.sum (finset.range (n + 1))
    (λ k, (hI.dpow k (a : A)) * (hJ.dpow (n - k) (b : A))))
  (λ (a : A), 0)

/-- Independence on choices for `dpow` -/
lemma dpow_eq_aux {J : ideal A} (hJ : divided_powers J)
  (hIJ : ∀ (n : ℕ) {a} (ha : a ∈ I ⊓ J), hI.dpow n a = hJ.dpow n a)
  (n : ℕ) {a} (ha : a ∈ I) {b} (hb : b ∈ J) {a'} (ha' : a' ∈ I) {b'} (hb' : b' ∈ J)
  (H : a + b = a' + b') :
  finset.sum (finset.range (n + 1)) (λ k, (hI.dpow k a) * (hJ.dpow (n - k) b))
    = finset.sum (finset.range (n + 1)) (λ k, (hI.dpow k a') * (hJ.dpow (n - k) b'))  :=
begin
  let c := a - a',
  suffices haa' : a = a' + c,
  suffices hbb' : b' = b + c,
  have hcI : c ∈ I := sub_mem ha ha',
  suffices hcJ : c ∈ J,
  rw [haa',  hbb'],
  have Ha'c : (finset.range (n + 1)).sum (λ (k : ℕ), hI.dpow k (a' + c) * hJ.dpow (n - k) b)
   = (finset.range (n+1)).sum (λ (k : ℕ),
    (finset.range (k+1)).sum
      (λ (l : ℕ), (hI.dpow l a') * (hJ.dpow (n-k) b) * (hI.dpow (k-l) c))),
  { apply finset.sum_congr rfl,
    intros k hk,
    rw hI.dpow_add k ha' hcI,
    rw finset.sum_mul,
    apply finset.sum_congr rfl,
    intros l hl,
    ring, },
  rw Ha'c,
  rw finset.sum_sigma',
  have Hbc : (finset.range (n + 1)).sum (λ (k : ℕ), hI.dpow k a' * hJ.dpow (n - k) (b + c))
   = (finset.range (n+1)).sum (λ (k : ℕ),
    (finset.range (n-k+1)).sum
      (λ (l : ℕ), (hI.dpow k a') * (hJ.dpow l b) * (hJ.dpow (n-k-l) c))),
  { apply finset.sum_congr rfl,
    intros k hk,
    rw hJ.dpow_add (n - k) hb hcJ,
    rw finset.mul_sum, ring_nf, },
  rw Hbc,
  rw finset.sum_sigma',

  let s := ((finset.range (n + 1)).sigma (λ (a : ℕ), finset.range (a + 1))),
  let i : Π (x : (Σ (i : ℕ), ℕ)), (x ∈ s) → (Σ (i : ℕ), ℕ) := λ ⟨k, m⟩ h, ⟨m, n-k⟩,
  let t := ((finset.range (n + 1)).sigma (λ (a : ℕ), finset.range (n - a + 1))),
  let j : Π (y : (Σ (i : ℕ), ℕ)), (y ∈ t) → (Σ (i : ℕ), ℕ) := λ ⟨k, m⟩ h, ⟨n-m,k⟩,
  rw finset.sum_bij' i _ _ j _ _ ,
  { rintros ⟨k,m⟩ h,
    change i ⟨n-m,k⟩ _ = _,
    change (⟨k,n - (n-m)⟩ : (Σ (i : ℕ), ℕ)) = _,
    simp only [eq_self_iff_true, heq_iff_eq, true_and],
    simp only [finset.mem_sigma, finset.mem_range, nat.lt_succ_iff] at h,
    apply nat.sub_sub_self , apply le_trans h.2, apply nat.sub_le, },
  { rintros ⟨k, m⟩ h,
    change (⟨m, n - k⟩ : (Σ (i : ℕ), ℕ)) ∈ t,
    simp only [finset.mem_sigma, finset.mem_range, nat.lt_succ_iff] at h ⊢,

    apply and.intro (le_trans h.2 h.1),
    apply tsub_le_tsub_left h.2, },

    { rintros ⟨u,v⟩ h,
      -- split all factors
      apply congr_arg2,
      apply congr_arg2,
      -- a' : no problemo
      refl,
      -- b : more difficult
      apply congr_arg2, refl, refl,
      -- c :
      rw hIJ _ ⟨hcI, hcJ⟩,
      apply congr_arg2,
      change u - v = n - v - (n - u),
      simp only [finset.mem_sigma, finset.mem_range, nat.lt_succ_iff] at h,
      rw tsub_tsub_tsub_cancel_left h.1,
      refl, },

    { rintros ⟨k,m⟩ h,
      change (⟨n-m, k⟩ : (Σ (i : ℕ), ℕ)) ∈ s,
      simp only [finset.mem_sigma, finset.mem_range, nat.lt_succ_iff] at h ⊢,
      apply and.intro (nat.sub_le _ _),
      rw nat.le_sub_iff_right (le_trans h.2 (nat.sub_le n k)),
      rw add_comm,
      rw ← nat.le_sub_iff_right h.1,
      exact h.2, },

    { rintros ⟨k,m⟩ h,
      change j ⟨m, n - k⟩ _ = _,
      change (⟨n - (n-k), m⟩ : (Σ (i : ℕ), ℕ)) = _,
            simp only [finset.mem_sigma, finset.mem_range, nat.lt_succ_iff] at h,
      simp only [heq_iff_eq, eq_self_iff_true, and_true],
      exact nat.sub_sub_self h.1, },

    { rw ← (sub_eq_iff_eq_add'.mpr hbb'), exact sub_mem hb' hb },

    { rw ← sub_eq_iff_eq_add' at H, rw ← H, rw haa', ring, },

    { simp only [c], ring, },
end

lemma dpow_eq {J : ideal A} (hJ : divided_powers J)
  (hIJ : ∀ (n : ℕ) (a ∈ I ⊓ J), hI.dpow n a = hJ.dpow n a)
  (n) {a} (ha : a ∈ I) {b} (hb : b ∈ J) :
  dpow hI hJ n (a + b) =
    finset.sum (finset.range (n + 1)) (λ k, (hI.dpow k a) * (hJ.dpow (n - k) b))  :=
begin
  simp only [dpow],
  convert function.extend_apply_first _ _ _ _ (⟨(⟨a, ha⟩ : I), (⟨b, hb⟩ : J)⟩ : I × J),
  rintros ⟨⟨a, ha⟩, ⟨b, hb⟩⟩ ⟨⟨a', ha'⟩, ⟨b', hb'⟩⟩,
  intro H,
  refine dpow_eq_aux hI hJ _ n ha hb ha' hb' H,
  { intros n a, exact hIJ n a, },
end

lemma dpow_eq_of_mem_left {J : ideal A} (hJ : divided_powers J)
  (hIJ : ∀ (n : ℕ) (a ∈ I ⊓ J), hI.dpow n a = hJ.dpow n a)
  (n : ℕ) {x : A} (hx : x ∈ I) : dpow hI hJ n x = hI.dpow n x :=
begin
  rw ← add_zero x,
  rw dpow_eq,
  { rw finset.sum_eq_single n,
    { simp only [nat.sub_self, add_zero, hJ.dpow_zero],
      rw [hJ.dpow_zero (J.zero_mem), mul_one], },
    { intros b hb hb',
      rw [hJ.dpow_eval_zero, mul_zero],
      intro h, apply hb',
      rw [finset.mem_range, nat.lt_succ_iff] at hb,
      rw [← nat.sub_add_cancel hb, h, zero_add], },
    { intro hn, exfalso, apply hn, rw finset.mem_range,
      exact lt_add_one n, } },
  exact hIJ, exact hx, exact J.zero_mem,
end

lemma dpow_eq_of_mem_right {J : ideal A} (hJ : divided_powers J)
  (hIJ : ∀ (n : ℕ) (a ∈ I ⊓ J), hI.dpow n a = hJ.dpow n a)
  (n : ℕ) {x : A} (hx : x ∈ J) : dpow hI hJ n x = hJ.dpow n x :=
begin
  rw ← zero_add x,
  rw dpow_eq,
  { rw finset.sum_eq_single 0,
    { simp only [nat.sub_zero, zero_add, hI.dpow_zero (I.zero_mem), one_mul], },
    { intros b hb hb',
      rw [hI.dpow_eval_zero, zero_mul], exact hb', },
    { intro hn, exfalso, apply hn, rw finset.mem_range,
      exact ne_zero.pos (n + 1), } },
  exact hIJ, exact I.zero_mem, exact hx,
end

lemma dpow_zero {J : ideal A} (hJ : divided_powers J)
  (hIJ :  ∀ (n : ℕ) (a ∈ I ⊓ J), hI.dpow n a = hJ.dpow n a) :
∀ (x : A) (hx : x ∈ I + J), dpow hI hJ 0 x = 1:=
begin
  intro x,
  rw [ideal.add_eq_sup, submodule.mem_sup],
  rintro ⟨a, ha, b, hb, rfl⟩,
  rw dpow_eq hI hJ hIJ (0 : ℕ) ha hb,
  simp only [finset.range_one, zero_tsub, finset.sum_singleton],
  rw [hI.dpow_zero ha, hJ.dpow_zero hb, mul_one],
end

lemma dpow_mul {J : ideal A} (hJ : divided_powers J)
(hIJ : ∀ (n : ℕ) (a : A), a ∈ I ⊓ J → hI.dpow n a = hJ.dpow n a)
(m n : ℕ) {x : A} : x ∈ I + J →
    dpow hI hJ m x * dpow hI hJ n x =
      ↑((m + n).choose m) * dpow hI hJ (m + n) x :=
begin
  rw [ideal.add_eq_sup, submodule.mem_sup],
  rintro ⟨a, ha, b, hb, rfl⟩,
  rw dpow_eq hI hJ hIJ m ha hb,
  rw ← finset.nat.sum_antidiagonal_eq_sum_range_succ
    (λ i j, hI.dpow i a * hJ.dpow j b),
  rw dpow_eq hI hJ hIJ n ha hb,
  rw ← finset.nat.sum_antidiagonal_eq_sum_range_succ
    (λ k l, hI.dpow k a * hJ.dpow l b),
  rw finset.sum_mul, simp_rw finset.mul_sum,
  rw ← finset.sum_product',

  have hf : ∀ (x : (ℕ × ℕ) × (ℕ × ℕ)),
    hI.dpow x.fst.fst a * hJ.dpow x.fst.snd b * (hI.dpow x.snd.fst a * hJ.dpow x.snd.snd b)
    = (x.fst.fst + x.snd.fst).choose (x.fst.fst) * (x.fst.snd + x.snd.snd).choose x.fst.snd
     * hI.dpow (x.fst.fst + x.snd.fst) a * hJ.dpow (x.fst.snd + x.snd.snd) b,
  { rintro ⟨⟨i, j⟩, ⟨k, l⟩⟩,
    dsimp,
    rw mul_assoc, rw ← mul_assoc (hJ.dpow j b) _ _, rw mul_comm (hJ.dpow j b),
    rw mul_assoc, rw hJ.dpow_mul j l hb,
    rw ← mul_assoc, rw hI.dpow_mul i k ha,
    ring, },

    rw finset.sum_congr rfl (λ x hx, hf x),

    let s : (ℕ × ℕ) × (ℕ × ℕ) → (ℕ × ℕ) := λ x,
      ⟨x.fst.fst + x.snd.fst, x.fst.snd + x.snd.snd⟩,
    have hs : ∀ (x ∈ finset.nat.antidiagonal m ×ˢ finset.nat.antidiagonal n),
      s x ∈ finset.nat.antidiagonal (m + n),
    { rintros ⟨⟨i,j⟩, ⟨k,l⟩⟩ h, dsimp [s],
      simp only [finset.nat.mem_antidiagonal, finset.mem_product] at h ⊢,
      rw [add_assoc, ← add_assoc k j l, add_comm k _, add_assoc, h.2, ← add_assoc, h.1], },
    rw ←  finset.sum_fiberwise_of_maps_to hs,

    have hs' : (finset.nat.antidiagonal (m + n)).sum
    (λ (y : ℕ × ℕ),
       (finset.filter (λ (x : (ℕ × ℕ) × ℕ × ℕ), (λ (x : (ℕ × ℕ) × ℕ × ℕ), s x) x = y)
          (finset.nat.antidiagonal m ×ˢ finset.nat.antidiagonal n)).sum
         (λ (x : (ℕ × ℕ) × ℕ × ℕ),
            ↑((x.fst.fst + x.snd.fst).choose x.fst.fst) *
            ↑((x.fst.snd + x.snd.snd).choose x.fst.snd) * hI.dpow (x.fst.fst + x.snd.fst) a *
              hJ.dpow (x.fst.snd + x.snd.snd) b))
  = (finset.nat.antidiagonal (m + n)).sum
    (λ (y : ℕ × ℕ),
       (finset.filter
          (λ (x : (ℕ × ℕ) × ℕ × ℕ), (λ (x : (ℕ × ℕ) × ℕ × ℕ), s x) x = y)
          (finset.nat.antidiagonal m ×ˢ finset.nat.antidiagonal n)).sum
         (λ (x : (ℕ × ℕ) × ℕ × ℕ),
            ↑((y.fst).choose x.fst.fst) * ↑((y.snd).choose x.fst.snd)) *
                hI.dpow (y.fst) a *
              hJ.dpow (y.snd) b),
{ apply finset.sum_congr rfl, rintros ⟨u,v⟩ hy,
  simp only [finset.sum_mul],
  apply finset.sum_congr rfl, rintros ⟨⟨i,j⟩,⟨k,l⟩⟩ hx,
  simp [s] at hx,
  dsimp,
  rw hx.2.1, rw hx.2.2, },
  rw hs',

  rw dpow_eq hI hJ hIJ (m + n) ha hb,
  rw ← finset.nat.sum_antidiagonal_eq_sum_range_succ
    (λ i j, hI.dpow i a * hJ.dpow j b),
  rw finset.mul_sum,
  apply finset.sum_congr rfl, rintros ⟨u,v⟩ h,
  -- simp only [nat.cast_sum, nat.cast_mul],
  -- rw finset.sum_mul, simp_rw finset.mul_sum,
  simp only [prod.mk.inj_iff],
  rw ← mul_assoc,
  apply congr_arg2 _ _ rfl,
  apply congr_arg2 _ _ rfl,

  simp only [← nat.cast_sum, ← nat.cast_mul],
  apply congr_arg,

  simp only [finset.nat.mem_antidiagonal] at h,

  rw rewriting_4_fold_sums h.symm (λ x, u.choose x.fst * v.choose x.snd) rfl _,
  { rw ← nat.add_choose_eq, rw h, },

  { intros x h,
    cases h with h h;
    { simp only [nat.choose_eq_zero_of_lt h, zero_mul, mul_zero], } },
end

lemma dpow_mem {J : ideal A} (hJ : divided_powers J)
  (hIJ :  ∀ (n : ℕ) (a ∈ I ⊓ J), hI.dpow n a = hJ.dpow n a) :
  ∀ {n : ℕ} (hn : n ≠ 0) {x : A} (hx : x ∈ I + J),
  dpow hI hJ n x ∈ I + J :=
begin
  intros n hn x,
  rw [ideal.add_eq_sup, submodule.mem_sup],
  rintro ⟨a, ha, b, hb, rfl⟩,
  rw dpow_eq hI hJ hIJ _ ha hb,
  apply submodule.sum_mem (I ⊔ J),
  intros k hk,
  by_cases hk0 : k = 0,
  { rw hk0, --rw tsub_zero,
    apply submodule.mem_sup_right, apply ideal.mul_mem_left,
    exact hJ.dpow_mem hn hb, },
  { apply submodule.mem_sup_left, apply ideal.mul_mem_right,
    exact hI.dpow_mem hk0 ha, }
  end

lemma dpow_smul {J : ideal A} (hJ : divided_powers J)
  (hIJ :  ∀ (n : ℕ) (a ∈ I ⊓ J), hI.dpow n a = hJ.dpow n a) :
∀ (n : ℕ) {c x : A} (hx : x ∈ I + J),
  dpow hI hJ n (c * x) = (c ^ n) * dpow hI hJ n x :=
begin
  intros n c x,
  rw [ideal.add_eq_sup, submodule.mem_sup],
  rintro ⟨a, ha, b, hb, rfl⟩,
  rw dpow_eq hI hJ hIJ n ha hb,
  rw mul_add,
  rw dpow_eq hI hJ hIJ n (ideal.mul_mem_left I c ha) (ideal.mul_mem_left J c hb),
  rw finset.mul_sum,
  apply finset.sum_congr rfl,
  intros k hk,
  simp only [finset.mem_range, nat.lt_succ_iff] at hk,
  rw hI.dpow_smul, rw hJ.dpow_smul,
  simp only [← mul_assoc],
  apply congr_arg2 (*) _ rfl,
  rw [mul_comm, ← mul_assoc],
  apply congr_arg2 (*) _ rfl,
  rw [← pow_add, nat.sub_add_cancel hk],
  exact hb,
  exact ha,
end

lemma dpow_add {J : ideal A} (hJ : divided_powers J)
  (hIJ :  ∀ (n : ℕ) (a ∈ I ⊓ J), hI.dpow n a = hJ.dpow n a) :
  ∀ (n : ℕ) {x y : A} (hx : x ∈ I + J) (hy : y ∈ I + J),
dpow hI hJ n (x + y) = finset.sum (finset.range (n + 1))
  (λ k, (dpow hI hJ k x) * (dpow hI hJ (n - k) y)) :=
begin
  intros n x y,
  rw [ideal.add_eq_sup, submodule.mem_sup],
  rintro ⟨a, ha, b, hb, rfl⟩,
  rw [submodule.mem_sup],
  rintro ⟨a', ha', b', hb', rfl⟩,
  rw add_add_add_comm a b a' b',
  rw dpow_eq hI hJ hIJ n (submodule.add_mem I ha ha') (submodule.add_mem J hb hb'),

  let f : ℕ × ℕ × ℕ × ℕ → A := λ ⟨i,j,k,l⟩,
    (hI.dpow i a) * (hI.dpow j a') * (hJ.dpow k b) * (hJ.dpow l b'),
  have hf1 : ∀ (k ∈ finset.range (n + 1)),
    hI.dpow k (a + a') * hJ.dpow (n - k) (b + b') =
    (finset.range (k + 1)).sum (λ i, (finset.range (n - k + 1)).sum (λ l,
    hI.dpow i a * hI.dpow (k - i) a' * hJ.dpow l b * hJ.dpow (n - k - l) b')),
  { intros k hk,
    rw hI.dpow_add k ha ha', rw hJ.dpow_add (n - k) hb hb',
    rw finset.sum_mul,
    apply finset.sum_congr rfl,
    intros i hi,
    rw finset.mul_sum,
    apply finset.sum_congr rfl,
    intros l hl,
    ring, },
  rw finset.sum_congr rfl hf1,
  have hf2 : ∀ (k ∈ finset.range (n + 1)),
    dpow hI hJ k (a + b) * dpow hI hJ (n - k) (a' + b') =
    (finset.range (k + 1)).sum (λ i, (finset.range (n - k + 1)).sum (λ l,
    hI.dpow i a * hI.dpow l a' * hJ.dpow (k - i) b * hJ.dpow (n - k - l) b')),
  { intros k hk,
    rw dpow_eq hI hJ hIJ k ha hb,
    rw dpow_eq hI hJ hIJ (n - k) ha' hb',
    rw finset.sum_mul,
    apply finset.sum_congr rfl,
    intros i hi,
    rw finset.mul_sum,
    apply finset.sum_congr rfl,
    intros j hj,
    ring, },
  rw finset.sum_congr rfl hf2,
  convert finset.sum_4_rw f n,
end

  /- si on développe, on obtient une somme indexée par
  les c : fin (n+1) → ℕ  de somme m
  de  ∏   (hI.dpow k a)^(c k) (hJ.dpow (n-k) b)^(c k)
  sans coefficients multinomiaux !
    par récurrence, en utilisant dpow_mul,
    a^[k] a^[k'] = (k + k')!/k! k'! a^ [k + k']
    a^[k] a^[k'] a^[k''] = (k+k'+k'')!/k!k'!k''!
   ∏ (hI.dpow k a)^(c k) = multinomial (k ^ (c k)) hI.dpow (∑ k (c k)) a
    tandis que Π (hJ.dpow (n-k) b)^(c k)
     = multinomial ((n-k)^ (c k)) hJ.dpow (∑ (n-k) c k) b
    la puissance est n∑ c k - ∑ k (c k) = n m - ∑ k (c k)
    = N!/∏ k!^(c k) * (nm - N)!/∏ (n-k)!^(c k) * a^[N] * b^[nm -N]

    Lorsqu'on somme sur les c de somme m et de poids N,
    il faudra trouver (mchoose m n)…
    Il est probable que le plus facile est d'identifier
    ce qui se passe pour Q[a,b] avec sa structure de puissances divisées canonique.

  -/

lemma dpow_comp_aux
{J : ideal A} (hJ : divided_powers J)
  (hIJ :  ∀ (n : ℕ) (a ∈ I ⊓ J), hI.dpow n a = hJ.dpow n a)
  (m : ℕ) {n : ℕ} (hn : n ≠ 0)
{a : A} (ha : a ∈ I) {b : A} (hb : b ∈ J) :
dpow hI hJ m (dpow hI hJ n (a + b)) =
finset.sum (finset.range (m * n + 1))
(λ (p : ℕ),
(finset.filter (λ (l : sym ℕ m),
  (finset.range (n + 1)).sum (λ i, multiset.count i ↑l * i) = p) ((finset.range (n + 1)).sym m)).sum
  (λ (x : sym ℕ m),
    (finset.range (n + 1)).prod (λ (i : ℕ), (cnik n i ↑x))
    * (nat.multinomial (finset.range (n + 1)) (λ (i : ℕ), multiset.count i ↑x * i))
    * (nat.multinomial (finset.range (n + 1)) (λ (i : ℕ), multiset.count i ↑x * (n - i)))) * hI.dpow p a * hJ.dpow (m * n - p) b)
:=
begin
  rw dpow_eq hI hJ hIJ n ha hb,
  rw sum_dpow_aux (dpow hI hJ) (dpow_zero hI hJ hIJ) (dpow_add hI hJ hIJ),

  /- i ≠0, n :
   (a^[i]*b^[n-i])^[c i]
    = a^[i]^ (c i) * (b^[n-i])^[c i]
    = (c i)! a^[i])^[c i] * b^[n-i]^[c i]
    = (c i)! * mchoose (i, c i) * mchoose (n-i, c i)
    * a^[i * c i] * b^[(n-i) * c i]
  i = 0 :
    (a^[0] * b^[n])^[c i]
     = b^[n]^[c i] = mchoose (c i) n * b ^[n * c i]
  i = n :
    (a^[n] * b^[0]) ^[c i]
     = a^[n]^[c i] = mchoose (c i) n * a^[n * c i]
  -/

  have L1 : ∀ (k : sym ℕ m) (i : ℕ) (hi : i ∈ finset.range (n+1)),
    dpow hI hJ (multiset.count i ↑k) ((hI.dpow i a) * hJ.dpow (n-i) b)
    = (cnik n i ↑k) * hI.dpow ((multiset.count i ↑k) * i) a
    * hJ.dpow ((multiset.count i ↑k) * (n - i)) b,
  { intros k i hi,
    simp only [cnik],
    by_cases hi2 : i = n,
    { rw hi2, rw nat.sub_self,
      rw if_neg hn, rw if_pos rfl,
      simp only [mchoose_zero',mul_one, nat.cast_one, mul_zero, hJ.dpow_zero hb],
      rw dpow_eq_of_mem_left hI hJ hIJ _ (hI.dpow_mem hn ha),
      rw hI.dpow_comp _ hn ha, },
    have hi2' : n - i ≠ 0,
    { intro h, apply hi2,
      rw [finset.mem_range, nat.lt_succ_iff] at hi,
      rw [← nat.sub_add_cancel hi, h, zero_add], } ,
    by_cases hi1 : i = 0,
    { rw hi1, rw mchoose_zero', rw hI.dpow_zero ha, rw nat.sub_zero, rw one_mul,
      rw if_pos rfl,
      rw dpow_eq_of_mem_right hI hJ hIJ _ (hJ.dpow_mem hn hb),
      rw hJ.dpow_comp _ hn hb,
      rw mul_zero,
      rw hI.dpow_zero ha,
      simp only [nat.cast_one, one_mul, mul_one], },
    -- i ≠ 0  and i ≠ n
    { rw if_neg hi1, rw if_neg hi2,
      rw [mul_comm, dpow_smul hI hJ hIJ, mul_comm],
      rw dpow_eq_of_mem_left hI hJ hIJ _ (hI.dpow_mem hi1 ha),
      rw ← hJ.factorial_mul_dpow_eq_pow (multiset.count i k),
      rw hI.dpow_comp _ hi1 ha,
      rw hJ.dpow_comp _ hi2' hb,
      simp only [← mul_assoc], apply congr_arg2 _ _ rfl,
      simp only [mul_assoc], rw mul_comm (hI.dpow _ a),
      simp only [←  mul_assoc],
      apply congr_arg2 _ _ rfl,
      rw mul_comm ↑(mchoose _ i),
      simp only [nat.cast_mul],
      exact hJ.dpow_mem hi2' hb,
      apply submodule.mem_sup_left, exact hI.dpow_mem hi1 ha, }, },

  let φ : sym ℕ m → ℕ  := λ k, (finset.range (n + 1)).sum (λ i, multiset.count i ↑k * i),
  suffices hφ : ∀ (k : sym ℕ m), k ∈ (finset.range (n + 1)).sym m →
    φ k ∈ finset.range (m * n + 1),

  rw ← finset.sum_fiberwise_of_maps_to hφ _,

  suffices L4 : ∀(p : ℕ) (hp : p ∈ finset.range (m * n + 1)),
  (finset.filter (λ (x : sym ℕ m), (λ (k : sym ℕ m), φ k) x = p) ((finset.range (n + 1)).sym m)).sum
    (λ (x : sym ℕ m),
    (finset.range (n + 1)).prod (λ (i : ℕ), dpow hI hJ (multiset.count i ↑x) (hI.dpow i a * hJ.dpow (n - i) b)))
  = (finset.filter (λ (x : sym ℕ m), (λ (k : sym ℕ m), φ k) x = p) ((finset.range (n + 1)).sym m)).sum
  (λ (k : sym ℕ m),
  (finset.range (n + 1)).prod (λ (i : ℕ), ↑(cnik n i ↑k)) *
      ↑(nat.multinomial (finset.range (n + 1)) (λ (i : ℕ), multiset.count i ↑k * i)) *
            ↑(nat.multinomial (finset.range (n + 1)) (λ (i : ℕ), multiset.count i ↑k * (n - i))) *
          hI.dpow p a *
        hJ.dpow (m * n - p) b),

  rw finset.sum_congr rfl L4,
  simp_rw finset.sum_mul,

  { -- L4
    intros p hp,
    apply finset.sum_congr rfl,
    intros k hk,
    rw finset.mem_filter at hk,
    rw finset.prod_congr rfl (L1 k),
    rw finset.prod_mul_distrib,
    rw finset.prod_mul_distrib,
    rw hI.prod_dpow_self _ ha,
    rw hJ.prod_dpow_self _ hb,
    simp only [mul_assoc], apply congr_arg2 _ rfl,
    apply congr_arg2 _ rfl,
    rw sum_range_sym_mul_compl hk.1,
    simp only [← mul_assoc],
    simp only [← hk.2, φ],  apply congr_arg2 _ _ rfl,
    rw mul_comm,

    apply_instance, apply_instance, },
  { -- hφ
    intros k hk,
    simp only [φ, finset.mem_range, nat.lt_succ_iff],
    exact range_sym_weighted_sum_le k hk, },
  { intros n hn,
    rw dpow_eq_of_mem_left hI hJ hIJ n (I.zero_mem),
    exact dpow_eval_zero hI hn, },
  { intros i hi,
    by_cases hi0 : i = 0,
    { rw hi0,
      apply submodule.mem_sup_right, apply ideal.mul_mem_left,
      exact hJ.dpow_mem hn hb, },
    { apply submodule.mem_sup_left, apply ideal.mul_mem_right,
      exact hI.dpow_mem hi0 ha, }, },
end

open polynomial

open_locale classical

lemma polynomial.inv_C_eq_C_inv {R : Type*} [comm_ring R] (a : R) :
  ring.inverse (C a) = C (ring.inverse a) :=
begin
  simp only [ring.inverse],
  by_cases ha : is_unit a,
  { simp only [dif_pos ha],
    have hCa : is_unit (C a),
    { rw ← is_unit.unit_spec ha,
      exact ring_hom.is_unit_map C ha, },
    rw [dif_pos hCa],
    apply is_unit.mul_right_cancel hCa,
    rw ← map_mul,
    simp only [is_unit.coe_inv_mul, map_one], },
  { simp only [ring.inverse, dif_neg ha, map_zero],
    rw dif_neg,
    intro hCa,  apply ha,
    rw is_unit_iff_exists_inv at hCa ⊢,
    obtain ⟨b, hb⟩ := hCa,
    use b.coeff 0,
    convert congr_arg2 coeff hb rfl,
    rw polynomial.coeff_C_mul ,
    simp only [coeff_one_zero],},
end

lemma dpow_comp_coeffs {m n p : ℕ} (hn : n ≠ 0) (hp : p ≤ m * n):
mchoose m n =
  (finset.filter (λ (l : sym ℕ m),
  (finset.range (n + 1)).sum (λ (i : ℕ), multiset.count i ↑l * i) = p)
      ((finset.range (n + 1)).sym m)).sum (λ (x : sym ℕ m),
        (finset.range (n + 1)).prod (λ (i : ℕ), (cnik n i ↑x))
        * ((nat.multinomial (finset.range (n + 1)) (λ (i : ℕ), multiset.count i ↑x * i))
        * (nat.multinomial (finset.range (n + 1)) (λ (i : ℕ), multiset.count i ↑x * (n - i))))) :=
begin
  rw ← mul_left_inj' (pos_iff_ne_zero.mp (nat.choose_pos hp)),

  have : function.injective (coe : ℕ → ℚ) := nat.cast_injective,
  apply this,
  simp only [nat.cast_mul, nat.cast_sum, nat.cast_prod, nat.cast_eq_zero],

  conv_lhs { rw ← polynomial.coeff_X_add_one_pow ℚ (m * n) p, },
  let A := polynomial ℚ,
--   let X : A :=  1 1,
  let I : ideal A := ⊤,
  let hI : divided_powers I := rat_algebra.divided_powers ⊤,
  let hII : ∀ (n : ℕ) (a : A), a ∈ I ⊓ I → hI.dpow n a = hI.dpow n a := λ n a ha, rfl,
  let h1 : (1 : A) ∈ I := submodule.mem_top,
  let hX : X ∈ I := submodule.mem_top,

  suffices hdpow : ∀ (k : ℕ) (a : A) (ha : a ∈ I),
  hI.dpow k a =  C (1/↑(k.factorial)) * a ^ k,
  rw ← hI.factorial_mul_dpow_eq_pow (m * n) (X + 1) (submodule.mem_top),
  rw ← polynomial.coeff_C_mul,
  rw [← mul_assoc, mul_comm (C ↑(mchoose m n)), mul_assoc ],
  simp only [C_eq_nat_cast],
  rw ← hI.dpow_comp m hn (submodule.mem_top),
  rw [← dpow_eq_of_mem_left hI hI hII n (submodule.mem_top),
    ← dpow_eq_of_mem_left hI hI hII m (submodule.mem_top)],
  rw dpow_comp_aux hI hI hII m hn hX h1,
  rw [← C_eq_nat_cast ],
  simp only [finset.mul_sum],
  -- simp only [← C_eq_nat_cast, ← ring_hom.map_sum, ← ring_hom.map_prod],
  -- simp only [← ring_hom.map_sum, ← ring_hom.map_prod],
  simp only [finset.sum_mul, finset.mul_sum],
  simp only [finset_sum_coeff],

  rw finset.sum_eq_single p,

  { simp only [hdpow _ _ (submodule.mem_top), one_pow, mul_one, one_mul],
    simp_rw [polynomial.coeff_C_mul],
    simp only [←C_eq_nat_cast, ← ring_hom.map_prod],
    simp_rw [mul_assoc, polynomial.coeff_C_mul, polynomial.coeff_mul_C],
    simp only [coeff_X_pow_self p],
    apply finset.sum_congr rfl,
    intros x hx,
    rw mul_comm,
    simp only [mul_assoc],
    apply congr_arg2 (*) rfl,
    apply congr_arg2 (*) rfl,
    apply congr_arg2 (*) rfl,
    rw ← nat.choose_mul_factorial_mul_factorial hp,
    simp only [one_div, nat.cast_mul, one_mul],
    rw mul_comm, simp only [mul_assoc], rw mul_comm ↑(m * n - p).factorial,
    rw mul_comm, simp only [mul_assoc],
    convert mul_one _,
    rw ←mul_assoc,

    convert mul_one _,
    { rw mul_inv_cancel,
      simp only [ne.def, nat.cast_eq_zero],
      exact nat.factorial_ne_zero _, },
    { rw mul_inv_cancel,
      simp only [ne.def, nat.cast_eq_zero],
      exact nat.factorial_ne_zero _, }, },

  { intros k hk hk',
    rw finset.sum_eq_zero,
    intros x hx,
    simp only [hdpow _ _ (submodule.mem_top), one_pow, mul_one, one_mul],
    simp_rw [polynomial.coeff_C_mul],
    simp only [←C_eq_nat_cast, ← ring_hom.map_prod],
    simp_rw [mul_assoc, polynomial.coeff_C_mul, polynomial.coeff_mul_C],
    simp only [polynomial.coeff_X_pow , if_neg (ne.symm hk')],
    simp only [mul_zero, zero_mul], },

  { intro hp,
    convert finset.sum_empty,
    rw finset.eq_empty_iff_forall_not_mem ,
    intros x hx,
    simp only [finset.mem_filter] at hx,
    apply hp,
    rw [finset.mem_range, nat.lt_succ_iff],
    rw ← hx.2,
    exact range_sym_weighted_sum_le x hx.1, },

  { intros k a ha,
    simp only [rat_algebra.divided_powers_dpow_apply, rat_algebra.dpow,
      of_invertible_factorial.dpow, dif_pos ha, one_div],
    simp only [← C_eq_nat_cast],
    rw polynomial.inv_C_eq_C_inv,
    simp only [ring.inverse_eq_inv'], },
end

include hI
lemma dpow_comp {J : ideal A} (hJ : divided_powers J)
  (hIJ :  ∀ (n : ℕ) (a ∈ I ⊓ J), hI.dpow n a = hJ.dpow n a)
  (m : ℕ) {n : ℕ} (hn : n ≠ 0) {x : A} (hx : x ∈ I + J) :
  dpow hI hJ m (dpow hI hJ n x) =
    ↑(mchoose m n) * dpow hI hJ (m * n) x :=
begin
  rw [ideal.add_eq_sup, submodule.mem_sup] at hx,
  obtain ⟨a, ha, b, hb, rfl⟩ := hx,
  rw dpow_comp_aux hI hJ hIJ m hn ha hb,
  rw dpow_add hI hJ hIJ _ (submodule.mem_sup_left ha) (submodule.mem_sup_right hb),
  rw finset.mul_sum,
  apply finset.sum_congr rfl,
  intros p hp,
  rw dpow_eq_of_mem_left hI hJ hIJ _ ha,
  rw dpow_eq_of_mem_right hI hJ hIJ _ hb,
  simp only [mul_assoc], apply congr_arg2 (*) _ rfl,
  -- it remains to check coefficients
  rw dpow_comp_coeffs hn (nat.lt_succ_iff.mp (finset.mem_range.mp hp)),
  simp only [nat.cast_sum, nat.cast_mul, nat.cast_prod],
end

-- MOVE dpow_null and dpow_one outside of the definition
noncomputable
def divided_powers {J : ideal A} (hJ : divided_powers J)
  (hIJ : ∀ (n : ℕ) (a ∈ I ⊓ J), hI.dpow n a = hJ.dpow n a) :
divided_powers (I + J) := {
dpow := dpow hI hJ,
dpow_null :=
begin
  intros n x hx,
  simp only [dpow],
  rw function.extend_apply',
  rintro ⟨⟨⟨a, ha⟩, ⟨b, hb⟩⟩, h⟩, apply hx,
  rw ← h,
--  change a + b ∈ I + J,
  exact submodule.add_mem_sup ha hb,
end,
dpow_zero := dpow_zero hI hJ hIJ,
dpow_one :=
begin
  intro x,
  rw [ideal.add_eq_sup, submodule.mem_sup],
  rintro ⟨a, ha, b, hb, rfl⟩,
  rw dpow_eq hI hJ hIJ _ ha hb,
  suffices : finset.range (1 + 1) = {0, 1}, rw this,
  simp only [finset.sum_insert, finset.mem_singleton, nat.zero_ne_one, not_false_iff,
    tsub_zero, finset.sum_singleton, tsub_self],
  rw [hI.dpow_zero ha, hI.dpow_one ha, hJ.dpow_zero hb, hJ.dpow_one hb],
  ring,
  { rw [finset.range_succ, finset.range_one], ext k, simp, exact or.comm, },
end,
dpow_mem := λ n, dpow_mem hI hJ hIJ,
dpow_add := dpow_add hI hJ hIJ,
dpow_smul := dpow_smul hI hJ hIJ,
dpow_mul := dpow_mul hI hJ hIJ,
dpow_comp := dpow_comp hI hJ hIJ, }

lemma dpow_unique {J : ideal A} (hJ : _root_.divided_powers J)
  (hsup : _root_.divided_powers (I + J))
  (hI' : ∀ (n : ℕ) (a ∈ I), hI.dpow n a = hsup.dpow n a)
  (hJ' : ∀ (n : ℕ) (b ∈ J), hJ.dpow n b = hsup.dpow n b) :
let hIJ :  ∀ (n : ℕ) (a ∈ I ⊓ J), hI.dpow n a = hJ.dpow n a :=
λ n a ha, by simp only [hI' n a (submodule.mem_inf.mp ha).1, hJ' n a (submodule.mem_inf.mp ha).2] in
  hsup = divided_powers hI hJ hIJ :=
begin
  intro hIJ,
  apply eq_of_eq_on_ideal,
  intros n x hx,
  rw [ideal.add_eq_sup, submodule.mem_sup] at hx,
  obtain ⟨a, ha, b, hb, rfl⟩ := hx,
  simp only [divided_powers, dpow_eq hI hJ hIJ n ha hb],
  rw hsup.dpow_add n (submodule.mem_sup_left ha) (submodule.mem_sup_right hb),
  apply finset.sum_congr rfl,
  intros k hk,
  apply congr_arg2 (*) (hI' _ a ha).symm (hJ' _ b hb).symm,
end

end ideal_add
end divided_powers


/-
Copyright (c) 2014 Jeremy Avigad. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jeremy Avigad, Leonardo de Moura, Floris van Doorn, Yury Kudryashov, Neil Strickland

! This file was ported from Lean 3 source module algebra.ring.semiconj
! leanprover-community/mathlib commit 448144f7ae193a8990cb7473c9e9a01990f64ac7
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Algebra.Group.Semiconj
import Mathbin.Algebra.Ring.Defs

/-!
# Semirings and rings

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

This file gives lemmas about semirings, rings and domains.
This is analogous to `algebra.group.basic`,
the difference being that the former is about `+` and `*` separately, while
the present file is about their interaction.

For the definitions of semirings and rings see `algebra.ring.defs`.

-/

universe u v w x

variable {α : Type u} {β : Type v} {γ : Type w} {R : Type x}

open Function

namespace SemiconjBy

/- warning: semiconj_by.add_right -> SemiconjBy.add_right is a dubious translation:
lean 3 declaration is
  forall {R : Type.{u1}} [_inst_1 : Distrib.{u1} R] {a : R} {x : R} {y : R} {x' : R} {y' : R}, (SemiconjBy.{u1} R (Distrib.toHasMul.{u1} R _inst_1) a x y) -> (SemiconjBy.{u1} R (Distrib.toHasMul.{u1} R _inst_1) a x' y') -> (SemiconjBy.{u1} R (Distrib.toHasMul.{u1} R _inst_1) a (HAdd.hAdd.{u1, u1, u1} R R R (instHAdd.{u1} R (Distrib.toHasAdd.{u1} R _inst_1)) x x') (HAdd.hAdd.{u1, u1, u1} R R R (instHAdd.{u1} R (Distrib.toHasAdd.{u1} R _inst_1)) y y'))
but is expected to have type
  forall {R : Type.{u1}} [_inst_1 : Distrib.{u1} R] {a : R} {x : R} {y : R} {x' : R} {y' : R}, (SemiconjBy.{u1} R (Distrib.toMul.{u1} R _inst_1) a x y) -> (SemiconjBy.{u1} R (Distrib.toMul.{u1} R _inst_1) a x' y') -> (SemiconjBy.{u1} R (Distrib.toMul.{u1} R _inst_1) a (HAdd.hAdd.{u1, u1, u1} R R R (instHAdd.{u1} R (Distrib.toAdd.{u1} R _inst_1)) x x') (HAdd.hAdd.{u1, u1, u1} R R R (instHAdd.{u1} R (Distrib.toAdd.{u1} R _inst_1)) y y'))
Case conversion may be inaccurate. Consider using '#align semiconj_by.add_right SemiconjBy.add_rightₓ'. -/
@[simp]
theorem add_right [Distrib R] {a x y x' y' : R} (h : SemiconjBy a x y) (h' : SemiconjBy a x' y') :
    SemiconjBy a (x + x') (y + y') := by
  simp only [SemiconjBy, left_distrib, right_distrib, h.eq, h'.eq]
#align semiconj_by.add_right SemiconjBy.add_right

/- warning: semiconj_by.add_left -> SemiconjBy.add_left is a dubious translation:
lean 3 declaration is
  forall {R : Type.{u1}} [_inst_1 : Distrib.{u1} R] {a : R} {b : R} {x : R} {y : R}, (SemiconjBy.{u1} R (Distrib.toHasMul.{u1} R _inst_1) a x y) -> (SemiconjBy.{u1} R (Distrib.toHasMul.{u1} R _inst_1) b x y) -> (SemiconjBy.{u1} R (Distrib.toHasMul.{u1} R _inst_1) (HAdd.hAdd.{u1, u1, u1} R R R (instHAdd.{u1} R (Distrib.toHasAdd.{u1} R _inst_1)) a b) x y)
but is expected to have type
  forall {R : Type.{u1}} [_inst_1 : Distrib.{u1} R] {a : R} {b : R} {x : R} {y : R}, (SemiconjBy.{u1} R (Distrib.toMul.{u1} R _inst_1) a x y) -> (SemiconjBy.{u1} R (Distrib.toMul.{u1} R _inst_1) b x y) -> (SemiconjBy.{u1} R (Distrib.toMul.{u1} R _inst_1) (HAdd.hAdd.{u1, u1, u1} R R R (instHAdd.{u1} R (Distrib.toAdd.{u1} R _inst_1)) a b) x y)
Case conversion may be inaccurate. Consider using '#align semiconj_by.add_left SemiconjBy.add_leftₓ'. -/
@[simp]
theorem add_left [Distrib R] {a b x y : R} (ha : SemiconjBy a x y) (hb : SemiconjBy b x y) :
    SemiconjBy (a + b) x y := by simp only [SemiconjBy, left_distrib, right_distrib, ha.eq, hb.eq]
#align semiconj_by.add_left SemiconjBy.add_left

section

variable [Mul R] [HasDistribNeg R] {a x y : R}

/- warning: semiconj_by.neg_right -> SemiconjBy.neg_right is a dubious translation:
lean 3 declaration is
  forall {R : Type.{u1}} [_inst_1 : Mul.{u1} R] [_inst_2 : HasDistribNeg.{u1} R _inst_1] {a : R} {x : R} {y : R}, (SemiconjBy.{u1} R _inst_1 a x y) -> (SemiconjBy.{u1} R _inst_1 a (Neg.neg.{u1} R (InvolutiveNeg.toHasNeg.{u1} R (HasDistribNeg.toHasInvolutiveNeg.{u1} R _inst_1 _inst_2)) x) (Neg.neg.{u1} R (InvolutiveNeg.toHasNeg.{u1} R (HasDistribNeg.toHasInvolutiveNeg.{u1} R _inst_1 _inst_2)) y))
but is expected to have type
  forall {R : Type.{u1}} [_inst_1 : Mul.{u1} R] [_inst_2 : HasDistribNeg.{u1} R _inst_1] {a : R} {x : R} {y : R}, (SemiconjBy.{u1} R _inst_1 a x y) -> (SemiconjBy.{u1} R _inst_1 a (Neg.neg.{u1} R (InvolutiveNeg.toNeg.{u1} R (HasDistribNeg.toInvolutiveNeg.{u1} R _inst_1 _inst_2)) x) (Neg.neg.{u1} R (InvolutiveNeg.toNeg.{u1} R (HasDistribNeg.toInvolutiveNeg.{u1} R _inst_1 _inst_2)) y))
Case conversion may be inaccurate. Consider using '#align semiconj_by.neg_right SemiconjBy.neg_rightₓ'. -/
theorem neg_right (h : SemiconjBy a x y) : SemiconjBy a (-x) (-y) := by
  simp only [SemiconjBy, h.eq, neg_mul, mul_neg]
#align semiconj_by.neg_right SemiconjBy.neg_right

/- warning: semiconj_by.neg_right_iff -> SemiconjBy.neg_right_iff is a dubious translation:
lean 3 declaration is
  forall {R : Type.{u1}} [_inst_1 : Mul.{u1} R] [_inst_2 : HasDistribNeg.{u1} R _inst_1] {a : R} {x : R} {y : R}, Iff (SemiconjBy.{u1} R _inst_1 a (Neg.neg.{u1} R (InvolutiveNeg.toHasNeg.{u1} R (HasDistribNeg.toHasInvolutiveNeg.{u1} R _inst_1 _inst_2)) x) (Neg.neg.{u1} R (InvolutiveNeg.toHasNeg.{u1} R (HasDistribNeg.toHasInvolutiveNeg.{u1} R _inst_1 _inst_2)) y)) (SemiconjBy.{u1} R _inst_1 a x y)
but is expected to have type
  forall {R : Type.{u1}} [_inst_1 : Mul.{u1} R] [_inst_2 : HasDistribNeg.{u1} R _inst_1] {a : R} {x : R} {y : R}, Iff (SemiconjBy.{u1} R _inst_1 a (Neg.neg.{u1} R (InvolutiveNeg.toNeg.{u1} R (HasDistribNeg.toInvolutiveNeg.{u1} R _inst_1 _inst_2)) x) (Neg.neg.{u1} R (InvolutiveNeg.toNeg.{u1} R (HasDistribNeg.toInvolutiveNeg.{u1} R _inst_1 _inst_2)) y)) (SemiconjBy.{u1} R _inst_1 a x y)
Case conversion may be inaccurate. Consider using '#align semiconj_by.neg_right_iff SemiconjBy.neg_right_iffₓ'. -/
@[simp]
theorem neg_right_iff : SemiconjBy a (-x) (-y) ↔ SemiconjBy a x y :=
  ⟨fun h => neg_neg x ▸ neg_neg y ▸ h.neg_right, SemiconjBy.neg_right⟩
#align semiconj_by.neg_right_iff SemiconjBy.neg_right_iff

/- warning: semiconj_by.neg_left -> SemiconjBy.neg_left is a dubious translation:
lean 3 declaration is
  forall {R : Type.{u1}} [_inst_1 : Mul.{u1} R] [_inst_2 : HasDistribNeg.{u1} R _inst_1] {a : R} {x : R} {y : R}, (SemiconjBy.{u1} R _inst_1 a x y) -> (SemiconjBy.{u1} R _inst_1 (Neg.neg.{u1} R (InvolutiveNeg.toHasNeg.{u1} R (HasDistribNeg.toHasInvolutiveNeg.{u1} R _inst_1 _inst_2)) a) x y)
but is expected to have type
  forall {R : Type.{u1}} [_inst_1 : Mul.{u1} R] [_inst_2 : HasDistribNeg.{u1} R _inst_1] {a : R} {x : R} {y : R}, (SemiconjBy.{u1} R _inst_1 a x y) -> (SemiconjBy.{u1} R _inst_1 (Neg.neg.{u1} R (InvolutiveNeg.toNeg.{u1} R (HasDistribNeg.toInvolutiveNeg.{u1} R _inst_1 _inst_2)) a) x y)
Case conversion may be inaccurate. Consider using '#align semiconj_by.neg_left SemiconjBy.neg_leftₓ'. -/
theorem neg_left (h : SemiconjBy a x y) : SemiconjBy (-a) x y := by
  simp only [SemiconjBy, h.eq, neg_mul, mul_neg]
#align semiconj_by.neg_left SemiconjBy.neg_left

/- warning: semiconj_by.neg_left_iff -> SemiconjBy.neg_left_iff is a dubious translation:
lean 3 declaration is
  forall {R : Type.{u1}} [_inst_1 : Mul.{u1} R] [_inst_2 : HasDistribNeg.{u1} R _inst_1] {a : R} {x : R} {y : R}, Iff (SemiconjBy.{u1} R _inst_1 (Neg.neg.{u1} R (InvolutiveNeg.toHasNeg.{u1} R (HasDistribNeg.toHasInvolutiveNeg.{u1} R _inst_1 _inst_2)) a) x y) (SemiconjBy.{u1} R _inst_1 a x y)
but is expected to have type
  forall {R : Type.{u1}} [_inst_1 : Mul.{u1} R] [_inst_2 : HasDistribNeg.{u1} R _inst_1] {a : R} {x : R} {y : R}, Iff (SemiconjBy.{u1} R _inst_1 (Neg.neg.{u1} R (InvolutiveNeg.toNeg.{u1} R (HasDistribNeg.toInvolutiveNeg.{u1} R _inst_1 _inst_2)) a) x y) (SemiconjBy.{u1} R _inst_1 a x y)
Case conversion may be inaccurate. Consider using '#align semiconj_by.neg_left_iff SemiconjBy.neg_left_iffₓ'. -/
@[simp]
theorem neg_left_iff : SemiconjBy (-a) x y ↔ SemiconjBy a x y :=
  ⟨fun h => neg_neg a ▸ h.neg_left, SemiconjBy.neg_left⟩
#align semiconj_by.neg_left_iff SemiconjBy.neg_left_iff

end

section

variable [MulOneClass R] [HasDistribNeg R] {a x y : R}

/- warning: semiconj_by.neg_one_right -> SemiconjBy.neg_one_right is a dubious translation:
lean 3 declaration is
  forall {R : Type.{u1}} [_inst_1 : MulOneClass.{u1} R] [_inst_2 : HasDistribNeg.{u1} R (MulOneClass.toHasMul.{u1} R _inst_1)] (a : R), SemiconjBy.{u1} R (MulOneClass.toHasMul.{u1} R _inst_1) a (Neg.neg.{u1} R (InvolutiveNeg.toHasNeg.{u1} R (HasDistribNeg.toHasInvolutiveNeg.{u1} R (MulOneClass.toHasMul.{u1} R _inst_1) _inst_2)) (OfNat.ofNat.{u1} R 1 (OfNat.mk.{u1} R 1 (One.one.{u1} R (MulOneClass.toHasOne.{u1} R _inst_1))))) (Neg.neg.{u1} R (InvolutiveNeg.toHasNeg.{u1} R (HasDistribNeg.toHasInvolutiveNeg.{u1} R (MulOneClass.toHasMul.{u1} R _inst_1) _inst_2)) (OfNat.ofNat.{u1} R 1 (OfNat.mk.{u1} R 1 (One.one.{u1} R (MulOneClass.toHasOne.{u1} R _inst_1)))))
but is expected to have type
  forall {R : Type.{u1}} [_inst_1 : MulOneClass.{u1} R] [_inst_2 : HasDistribNeg.{u1} R (MulOneClass.toMul.{u1} R _inst_1)] (a : R), SemiconjBy.{u1} R (MulOneClass.toMul.{u1} R _inst_1) a (Neg.neg.{u1} R (InvolutiveNeg.toNeg.{u1} R (HasDistribNeg.toInvolutiveNeg.{u1} R (MulOneClass.toMul.{u1} R _inst_1) _inst_2)) (OfNat.ofNat.{u1} R 1 (One.toOfNat1.{u1} R (MulOneClass.toOne.{u1} R _inst_1)))) (Neg.neg.{u1} R (InvolutiveNeg.toNeg.{u1} R (HasDistribNeg.toInvolutiveNeg.{u1} R (MulOneClass.toMul.{u1} R _inst_1) _inst_2)) (OfNat.ofNat.{u1} R 1 (One.toOfNat1.{u1} R (MulOneClass.toOne.{u1} R _inst_1))))
Case conversion may be inaccurate. Consider using '#align semiconj_by.neg_one_right SemiconjBy.neg_one_rightₓ'. -/
@[simp]
theorem neg_one_right (a : R) : SemiconjBy a (-1) (-1) :=
  (one_right a).neg_right
#align semiconj_by.neg_one_right SemiconjBy.neg_one_right

/- warning: semiconj_by.neg_one_left -> SemiconjBy.neg_one_left is a dubious translation:
lean 3 declaration is
  forall {R : Type.{u1}} [_inst_1 : MulOneClass.{u1} R] [_inst_2 : HasDistribNeg.{u1} R (MulOneClass.toHasMul.{u1} R _inst_1)] (x : R), SemiconjBy.{u1} R (MulOneClass.toHasMul.{u1} R _inst_1) (Neg.neg.{u1} R (InvolutiveNeg.toHasNeg.{u1} R (HasDistribNeg.toHasInvolutiveNeg.{u1} R (MulOneClass.toHasMul.{u1} R _inst_1) _inst_2)) (OfNat.ofNat.{u1} R 1 (OfNat.mk.{u1} R 1 (One.one.{u1} R (MulOneClass.toHasOne.{u1} R _inst_1))))) x x
but is expected to have type
  forall {R : Type.{u1}} [_inst_1 : MulOneClass.{u1} R] [_inst_2 : HasDistribNeg.{u1} R (MulOneClass.toMul.{u1} R _inst_1)] (x : R), SemiconjBy.{u1} R (MulOneClass.toMul.{u1} R _inst_1) (Neg.neg.{u1} R (InvolutiveNeg.toNeg.{u1} R (HasDistribNeg.toInvolutiveNeg.{u1} R (MulOneClass.toMul.{u1} R _inst_1) _inst_2)) (OfNat.ofNat.{u1} R 1 (One.toOfNat1.{u1} R (MulOneClass.toOne.{u1} R _inst_1)))) x x
Case conversion may be inaccurate. Consider using '#align semiconj_by.neg_one_left SemiconjBy.neg_one_leftₓ'. -/
@[simp]
theorem neg_one_left (x : R) : SemiconjBy (-1) x x :=
  (SemiconjBy.one_left x).neg_left
#align semiconj_by.neg_one_left SemiconjBy.neg_one_left

end

section

variable [NonUnitalNonAssocRing R] {a b x y x' y' : R}

/- warning: semiconj_by.sub_right -> SemiconjBy.sub_right is a dubious translation:
lean 3 declaration is
  forall {R : Type.{u1}} [_inst_1 : NonUnitalNonAssocRing.{u1} R] {a : R} {x : R} {y : R} {x' : R} {y' : R}, (SemiconjBy.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R _inst_1))) a x y) -> (SemiconjBy.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R _inst_1))) a x' y') -> (SemiconjBy.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R _inst_1))) a (HSub.hSub.{u1, u1, u1} R R R (instHSub.{u1} R (SubNegMonoid.toHasSub.{u1} R (AddGroup.toSubNegMonoid.{u1} R (AddCommGroup.toAddGroup.{u1} R (NonUnitalNonAssocRing.toAddCommGroup.{u1} R _inst_1))))) x x') (HSub.hSub.{u1, u1, u1} R R R (instHSub.{u1} R (SubNegMonoid.toHasSub.{u1} R (AddGroup.toSubNegMonoid.{u1} R (AddCommGroup.toAddGroup.{u1} R (NonUnitalNonAssocRing.toAddCommGroup.{u1} R _inst_1))))) y y'))
but is expected to have type
  forall {R : Type.{u1}} [_inst_1 : NonUnitalNonAssocRing.{u1} R] {a : R} {x : R} {y : R} {x' : R} {y' : R}, (SemiconjBy.{u1} R (NonUnitalNonAssocRing.toMul.{u1} R _inst_1) a x y) -> (SemiconjBy.{u1} R (NonUnitalNonAssocRing.toMul.{u1} R _inst_1) a x' y') -> (SemiconjBy.{u1} R (NonUnitalNonAssocRing.toMul.{u1} R _inst_1) a (HSub.hSub.{u1, u1, u1} R R R (instHSub.{u1} R (SubNegMonoid.toSub.{u1} R (AddGroup.toSubNegMonoid.{u1} R (AddCommGroup.toAddGroup.{u1} R (NonUnitalNonAssocRing.toAddCommGroup.{u1} R _inst_1))))) x x') (HSub.hSub.{u1, u1, u1} R R R (instHSub.{u1} R (SubNegMonoid.toSub.{u1} R (AddGroup.toSubNegMonoid.{u1} R (AddCommGroup.toAddGroup.{u1} R (NonUnitalNonAssocRing.toAddCommGroup.{u1} R _inst_1))))) y y'))
Case conversion may be inaccurate. Consider using '#align semiconj_by.sub_right SemiconjBy.sub_rightₓ'. -/
@[simp]
theorem sub_right (h : SemiconjBy a x y) (h' : SemiconjBy a x' y') :
    SemiconjBy a (x - x') (y - y') := by simpa only [sub_eq_add_neg] using h.add_right h'.neg_right
#align semiconj_by.sub_right SemiconjBy.sub_right

/- warning: semiconj_by.sub_left -> SemiconjBy.sub_left is a dubious translation:
lean 3 declaration is
  forall {R : Type.{u1}} [_inst_1 : NonUnitalNonAssocRing.{u1} R] {a : R} {b : R} {x : R} {y : R}, (SemiconjBy.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R _inst_1))) a x y) -> (SemiconjBy.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R _inst_1))) b x y) -> (SemiconjBy.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R _inst_1))) (HSub.hSub.{u1, u1, u1} R R R (instHSub.{u1} R (SubNegMonoid.toHasSub.{u1} R (AddGroup.toSubNegMonoid.{u1} R (AddCommGroup.toAddGroup.{u1} R (NonUnitalNonAssocRing.toAddCommGroup.{u1} R _inst_1))))) a b) x y)
but is expected to have type
  forall {R : Type.{u1}} [_inst_1 : NonUnitalNonAssocRing.{u1} R] {a : R} {b : R} {x : R} {y : R}, (SemiconjBy.{u1} R (NonUnitalNonAssocRing.toMul.{u1} R _inst_1) a x y) -> (SemiconjBy.{u1} R (NonUnitalNonAssocRing.toMul.{u1} R _inst_1) b x y) -> (SemiconjBy.{u1} R (NonUnitalNonAssocRing.toMul.{u1} R _inst_1) (HSub.hSub.{u1, u1, u1} R R R (instHSub.{u1} R (SubNegMonoid.toSub.{u1} R (AddGroup.toSubNegMonoid.{u1} R (AddCommGroup.toAddGroup.{u1} R (NonUnitalNonAssocRing.toAddCommGroup.{u1} R _inst_1))))) a b) x y)
Case conversion may be inaccurate. Consider using '#align semiconj_by.sub_left SemiconjBy.sub_leftₓ'. -/
@[simp]
theorem sub_left (ha : SemiconjBy a x y) (hb : SemiconjBy b x y) : SemiconjBy (a - b) x y := by
  simpa only [sub_eq_add_neg] using ha.add_left hb.neg_left
#align semiconj_by.sub_left SemiconjBy.sub_left

end

end SemiconjBy


/-
Copyright (c) 2021 OpenAI. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kunhao Zheng, Stanislas Polu, David Renshaw, OpenAI GPT-f
-/
import mathzoo.imports.miniF2F

open_locale nat rat real big_operators topological_space

theorem amc12a_2002_p6
  (n : ℕ+) :
  ∃ m, (m > n ∧ ∃ p, m * p ≤ m + p) :=
begin
  use (n : ℕ).succ,
  { apply nat.succ_pos },
  norm_num,
  split,
  { exact_mod_cast (nat.lt_succ_self _) },
  use 1,
  rw mul_one,
  apply nat.succ_le_succ,
  exact le_of_lt (nat.lt_succ_self n),
end


-- Copyright (c) 2018 Keeley Hoek. All rights reserved.
-- Released under Apache 2.0 license as described in the file LICENSE.
-- Authors: Keeley Hoek, Scott Morrison
import tactic.rewrite_all
import data.vector

structure F :=
(a : ℕ)
(v : vector ℕ a)
(p : v.val = [])

example (f : F) : f.v.val = [] :=
begin
  perform_nth_rewrite 0 [f.p],
end

structure cat :=
  (O : Type)
  (H : O → O → Type)
  (i : Π o : O, H o o)
  (c : Π {X Y Z : O} (f : H X Y) (g : H Y Z), H X Z)
  (li : Π {X Y : O} (f : H X Y), c (i X) f = f)
  (ri : Π {X Y : O} (f : H X Y), c f (i Y) = f)
  (a : Π {W X Y Z : O} (f : H W X) (g : H X Y) (h : H Y Z), c (c f g) h = c f (c g h))

open tactic

example (C : cat) (W X Y Z : C.O) (f : C.H X Y) (g : C.H W X) (h k : C.H Y Z) : C.c (C.c g f) h = C.c g (C.c f h) :=
begin
  perform_nth_rewrite 0 [C.a],
end

example (C : cat) (X Y : C.O) (f : C.H X Y) : C.c f (C.i Y) = f :=
begin
  perform_nth_rewrite 0 [C.ri],
end

axiom foo : [1] = [2]

example : [[1], [1], [1]] = [[1], [2], [1]] :=
begin
  nth_rewrite_lhs 1 foo,
end

axiom foo' : [6] = [7]
axiom bar' : [[5],[5]] = [[6],[6]]

example : [[7],[6]] = [[5],[5]] :=
begin
  nth_rewrite_lhs 0 foo',
  nth_rewrite_rhs 0 bar',
  nth_rewrite_lhs 0 ←foo',
  nth_rewrite_lhs 0 ←foo'
end

axioms (a b : ℕ)

example (a b c : ℕ) : a + b + c = a + c + b :=
begin
  nth_rewrite_rhs 0 add_comm,
end


import condensed.filtered_colimits_commute_with_finite_limits
import condensed.ab
import condensed.short_exact
import condensed.bd_ses_aux
import for_mathlib.nnreal_to_nat_colimit
import for_mathlib.pow_functor

open category_theory
open category_theory.limits

namespace Condensed

open_locale nnreal
open opposite

noncomputable theory

universes u
variables (F : CondensedSet.{u} ⥤ Condensed.{u} Ab.{u+1})
  [preserves_filtered_colimits F] (A : CompHausFiltPseuNormGrp.{u})
  (ι : ℕ →o ℝ≥0) (hι : ∀ r : ℝ≥0, ∃ n : ℕ, r ≤ ι n)

set_option pp.universes true

def as_nat_diagram : as_small.{u+1} ℕ ⥤ CondensedSet.{u} :=
restrict_diagram ι A.level_Condensed_diagram'

@[simp, reassoc]
lemma ι_colimit_iso_Condensed_obj (i) :
  colimit.ι _ i ≫ A.colimit_iso_Condensed_obj.hom =
  A.level_Condensed_diagram_cocone.ι.app _ :=
begin
  dsimp [CompHausFiltPseuNormGrp.colimit_iso_Condensed_obj],
  erw colimit.ι_desc_assoc,
  apply CondensedSet_to_presheaf.map_injective,
  dsimp [CompHausFiltPseuNormGrp.colimit_iso_Condensed_obj_aux_nat_iso],
  ext S : 2, dsimp,
  erw
    ((is_colimit_of_preserves.{u+1 u+1 u+1 u+1 u+2 u+2}
    ((category_theory.evaluation.{u u+1 u+1 u+2} Profinite.{u}ᵒᵖ
    (Type (u+1))).obj S) (colimit.is_colimit.{u+1 u+1 u+1 u+2}
    (A.level_Condensed_diagram' ⋙ Sheaf_to_presheaf.{u u+1 u+1 u+2}
    proetale_topology.{u} (Type (u+1)))))).fac_assoc,
  erw colimit.ι_desc_assoc,
  refl,
end

def is_colimit_level_Condensed_diagram_cocone :
  is_colimit A.level_Condensed_diagram_cocone :=
{ desc := λ S, A.colimit_iso_Condensed_obj.inv ≫ colimit.desc _ S,
  fac' := begin
    intros S j, rw ← category.assoc,
    let t := _, change t ≫ _ = _,
    have ht : t = colimit.ι _ _,
    { dsimp [t], rw [iso.comp_inv_eq, ι_colimit_iso_Condensed_obj] },
    simp [ht],
  end,
  uniq' := begin
    intros S m hm,
    rw iso.eq_inv_comp,
    apply colimit.hom_ext, intros j,
    specialize hm j,
    simpa,
  end }

def as_nat_cocone : cocone (as_nat_diagram A ι) :=
restrict_cocone ι A.level_Condensed_diagram_cocone

def is_colimit_as_nat_cocone : is_colimit (as_nat_cocone A ι) :=
is_colimit_restrict_cocone ι hι _ (is_colimit_level_Condensed_diagram_cocone _)

/-- The map `∐ F(A_≤(n * c)) ⟶ F(A)`-/
def coproduct_presentation :
  (∐ (λ i : as_small.{u+1} ℕ, F.obj ((as_nat_diagram A ι).obj i))) ⟶
  F.obj (as_nat_cocone A ι).X :=
sigma.desc $ λ i, F.map ((as_nat_cocone A ι).ι.app i)

def presentation_point_isomorphism :
  F.obj ((as_nat_cocone A ι).X) ≅ colimit (as_nat_diagram A ι ⋙ F) :=
(is_colimit_of_preserves F (is_colimit_as_nat_cocone A ι hι)).cocone_point_unique_up_to_iso
  (colimit.is_colimit _)

lemma coproduct_presentation_eq :
  coproduct_presentation F A ι ≫ (presentation_point_isomorphism F A ι hι).hom =
  coproduct_to_colimit ((as_nat_diagram A ι ⋙ F)) :=
begin
  apply colimit.hom_ext, intros j,
  erw colimit.ι_desc,
  erw colimit.ι_desc_assoc,
  dsimp,
  erw (is_colimit_of_preserves F (is_colimit_as_nat_cocone A ι hι)).fac,
  refl,
end

theorem coproduct_to_colimit_short_exact_sequence (hι : ∀ r : ℝ≥0, ∃ n, r ≤ ι n):
  short_exact (coproduct_to_coproduct (as_nat_diagram A ι ⋙ F) - 𝟙 _)
  (coproduct_presentation F A ι) :=
{ mono := infer_instance,
  epi := begin
    have := coproduct_presentation_eq F A ι hι,
    rw ← iso.eq_comp_inv at this, rw this,
    apply_with epi_comp { instances := ff },
    apply_instance,
    apply_instance,
  end,
  exact := begin
    have := coproduct_presentation_eq F A ι hι,
    rw ← iso.eq_comp_inv at this, rw this,
    rw exact_comp_iso,
    exact (short_exact_sequence_aux (as_nat_diagram A ι ⋙ F)).exact,
  end }

variables (n : ℕ)

/-- The map `(∐ i, F((A_≤ i * c)^n)) ⟶ F(A^n)`. -/
def coproduct_presentation_with_pow :
  (∐ (λ i : as_small.{u+1} ℕ, F.obj (∏ λ (j : ulift.{u+1} (fin n)), (as_nat_diagram A ι).obj i))) ⟶
  F.obj (∏ λ j : ulift.{u+1} (fin n), (as_nat_cocone A ι).X) :=
sigma.desc $ λ i, F.map (pi.map $ λ j, (as_nat_cocone A ι).ι.app i)

def as_nat_diagram_pow : as_small.{u+1} ℕ ⥤ CondensedSet.{u} :=
as_nat_diagram A ι ⋙ pow_functor _ (ulift.{u+1} (fin n))

def as_nat_cocone_pow : cocone (as_nat_diagram_pow A ι n) :=
(pow_functor _ (ulift.{u+1} (fin n))).map_cocone (as_nat_cocone A ι)

def is_colimit_as_nat_cocone_pow : is_colimit (as_nat_cocone_pow A ι n) :=
is_colimit_of_preserves _ (is_colimit_as_nat_cocone _ _ hι)

def presentation_point_isomorphism_with_pow :
  F.obj (∏ λ (j : ulift.{u+1} (fin n)), (as_nat_cocone A ι).X) ≅
  colimit (as_nat_diagram_pow A ι n ⋙ F) :=
(is_colimit_of_preserves F
(is_colimit_as_nat_cocone_pow A ι hι n)).cocone_point_unique_up_to_iso
(colimit.is_colimit _)

lemma coproduct_presentation_with_pow_eq :
  coproduct_presentation_with_pow F A ι n =
  coproduct_to_colimit (as_nat_diagram_pow A ι n ⋙ F) ≫
  (presentation_point_isomorphism_with_pow F A ι hι n).inv :=
begin
  apply colimit.hom_ext, intros j,
  erw colimit.ι_desc,
  erw colimit.ι_desc_assoc,
  dsimp,
  erw colimit.ι_desc,
  refl,
end

/--
This is the short exact sequence of condensed abelian groups of the form
`0 → ∐ i, F((A_{c * i})^n) → ∐ i, F((A_{c * i})^n) → F(A^n) → 0`.
-/
lemma short_exact_sequence_with_pow (hι : ∀ r : ℝ≥0, ∃ n, r ≤ ι n) :
  short_exact (coproduct_to_coproduct (as_nat_diagram_pow A ι n ⋙ F) - 𝟙 _)
    (coproduct_presentation_with_pow F A ι n) :=
{ mono := infer_instance,
  epi := begin
    rw coproduct_presentation_with_pow_eq,
    apply epi_comp,
    apply hι,
  end,
  exact := begin
    rw coproduct_presentation_with_pow_eq,
    rw exact_comp_iso,
    exact (short_exact_sequence_aux _).exact,
    apply hι,
  end }

end Condensed


import data.complex.exponential data.real.basic
open real
noncomputable theory
variables (x a b c : ℝ)

def arsinh := ℝ → ℝ

#check arsinh

def inverse_func := ∀ y,  f (g y) = y

lemma sinh_bijective : function.bijective (sinh) :=
begin
  unfold function.bijective function.injective function.surjective,
  split,
  {
    intros a b,
    sorry
  },
  {
      intros a,
      use (sinh a)⁻¹,
      sorry
  }
end

lemma comp_sq : a * x * x + b * x + c = 0 → a * (x - (b / 2*a)) * (x - (b / 2*a)) + c - b*b/4*a*a = 0 :=
begin
  intros fabc,
  sorry
end


lemma imp_trans (P Q R : Prop) : (P → Q) → ((Q → R) → (P → R)) :=
begin
intro hpq,
intro hqr,
intro a,
apply hqr,
exact hpq(a),
end


/-
Copyright (c) 2022 Bhavik Mehta All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Bhavik Mehta, Yaël Dillies

! This file was ported from Lean 3 source module analysis.normed_space.hahn_banach.separation
! leanprover-community/mathlib commit 70fd9563a21e7b963887c9360bd29b2393e6225a
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Analysis.Convex.Cone.Basic
import Mathbin.Analysis.Convex.Gauge
import Mathbin.Topology.Algebra.Module.LocallyConvex

/-!
# Separation Hahn-Banach theorem

In this file we prove the geometric Hahn-Banach theorem. For any two disjoint convex sets, there
exists a continuous linear functional separating them, geometrically meaning that we can intercalate
a plane between them.

We provide many variations to stricten the result under more assumptions on the convex sets:
* `geometric_hahn_banach_open`: One set is open. Weak separation.
* `geometric_hahn_banach_open_point`, `geometric_hahn_banach_point_open`: One set is open, the
  other is a singleton. Weak separation.
* `geometric_hahn_banach_open_open`: Both sets are open. Semistrict separation.
* `geometric_hahn_banach_compact_closed`, `geometric_hahn_banach_closed_compact`: One set is closed,
  the other one is compact. Strict separation.
* `geometric_hahn_banach_point_closed`, `geometric_hahn_banach_closed_point`: One set is closed, the
  other one is a singleton. Strict separation.
* `geometric_hahn_banach_point_point`: Both sets are singletons. Strict separation.

## TODO

* Eidelheit's theorem
* `convex ℝ s → interior (closure s) ⊆ s`
-/

open Set

open Pointwise

variable {𝕜 E : Type _}

/-- Given a set `s` which is a convex neighbourhood of `0` and a point `x₀` outside of it, there is
a continuous linear functional `f` separating `x₀` and `s`, in the sense that it sends `x₀` to 1 and
all of `s` to values strictly below `1`. -/
theorem separate_convex_open_set [TopologicalSpace E] [AddCommGroup E] [TopologicalAddGroup E]
    [Module ℝ E] [ContinuousSMul ℝ E] {s : Set E} (hs₀ : (0 : E) ∈ s) (hs₁ : Convex ℝ s)
    (hs₂ : IsOpen s) {x₀ : E} (hx₀ : x₀ ∉ s) : ∃ f : E →L[ℝ] ℝ, f x₀ = 1 ∧ ∀ x ∈ s, f x < 1 :=
  by
  let f : E →ₗ.[ℝ] ℝ := LinearPMap.mkSpanSingleton x₀ 1 (ne_of_mem_of_not_mem hs₀ hx₀).symm
  obtain ⟨φ, hφ₁, hφ₂⟩ :=
    exists_extension_of_le_sublinear f (gauge s) (fun c hc => gauge_smul_of_nonneg hc.le)
      (gauge_add_le hs₁ <| absorbent_nhds_zero <| hs₂.mem_nhds hs₀) _
  have hφ₃ : φ x₀ = 1 := by
    rw [← Submodule.coe_mk x₀ (Submodule.mem_span_singleton_self _), hφ₁,
      LinearPMap.mkSpanSingleton'_apply_self]
  have hφ₄ : ∀ x ∈ s, φ x < 1 := fun x hx =>
    (hφ₂ x).trans_lt (gauge_lt_one_of_mem_of_open hs₁ hs₀ hs₂ hx)
  · refine' ⟨⟨φ, _⟩, hφ₃, hφ₄⟩
    refine'
      φ.continuous_of_nonzero_on_open _ (hs₂.vadd (-x₀)) (nonempty.vadd_set ⟨0, hs₀⟩)
        (vadd_set_subset_iff.mpr fun x hx => _)
    change φ (-x₀ + x) ≠ 0
    rw [map_add, map_neg]
    specialize hφ₄ x hx
    linarith
  rintro ⟨x, hx⟩
  obtain ⟨y, rfl⟩ := Submodule.mem_span_singleton.1 hx
  rw [LinearPMap.mkSpanSingleton'_apply]
  simp only [mul_one, Algebra.id.smul_eq_mul, Submodule.coe_mk]
  obtain h | h := le_or_lt y 0
  · exact h.trans (gauge_nonneg _)
  · rw [gauge_smul_of_nonneg h.le, smul_eq_mul, le_mul_iff_one_le_right h]
    exact
      one_le_gauge_of_not_mem (hs₁.star_convex hs₀)
        (absorbent_nhds_zero <| hs₂.mem_nhds hs₀).Absorbs hx₀
    infer_instance
#align separate_convex_open_set separate_convex_open_set

variable [TopologicalSpace E] [AddCommGroup E] [TopologicalAddGroup E] [Module ℝ E]
  [ContinuousSMul ℝ E] {s t : Set E} {x y : E}

/-- A version of the **Hahn-Banach theorem**: given disjoint convex sets `s`, `t` where `s` is open,
there is a continuous linear functional which separates them. -/
theorem geometric_hahn_banach_open (hs₁ : Convex ℝ s) (hs₂ : IsOpen s) (ht : Convex ℝ t)
    (disj : Disjoint s t) : ∃ (f : E →L[ℝ] ℝ)(u : ℝ), (∀ a ∈ s, f a < u) ∧ ∀ b ∈ t, u ≤ f b :=
  by
  obtain rfl | ⟨a₀, ha₀⟩ := s.eq_empty_or_nonempty
  · exact ⟨0, 0, by simp, fun b hb => le_rfl⟩
  obtain rfl | ⟨b₀, hb₀⟩ := t.eq_empty_or_nonempty
  · exact ⟨0, 1, fun a ha => zero_lt_one, by simp⟩
  let x₀ := b₀ - a₀
  let C := x₀ +ᵥ (s - t)
  have : (0 : E) ∈ C :=
    ⟨a₀ - b₀, sub_mem_sub ha₀ hb₀, by rw [vadd_eq_add, sub_add_sub_cancel', sub_self]⟩
  have : Convex ℝ C := (hs₁.sub ht).vadd _
  have : x₀ ∉ C := by
    intro hx₀
    rw [← add_zero x₀] at hx₀
    exact disj.zero_not_mem_sub_set (vadd_mem_vadd_set_iff.1 hx₀)
  obtain ⟨f, hf₁, hf₂⟩ := separate_convex_open_set ‹0 ∈ C› ‹_› (hs₂.sub_right.vadd _) ‹x₀ ∉ C›
  have : f b₀ = f a₀ + 1 := by simp [← hf₁]
  have forall_le : ∀ a ∈ s, ∀ b ∈ t, f a ≤ f b :=
    by
    intro a ha b hb
    have := hf₂ (x₀ + (a - b)) (vadd_mem_vadd_set <| sub_mem_sub ha hb)
    simp only [f.map_add, f.map_sub, hf₁] at this
    linarith
  refine' ⟨f, Inf (f '' t), image_subset_iff.1 (_ : f '' s ⊆ Iio (Inf (f '' t))), fun b hb => _⟩
  · rw [← interior_Iic]
    refine' interior_maximal (image_subset_iff.2 fun a ha => _) (f.is_open_map_of_ne_zero _ _ hs₂)
    · exact le_cinfₛ (nonempty.image _ ⟨_, hb₀⟩) (ball_image_of_ball <| forall_le _ ha)
    · rintro rfl
      simpa using hf₁
  · exact cinfₛ_le ⟨f a₀, ball_image_of_ball <| forall_le _ ha₀⟩ (mem_image_of_mem _ hb)
#align geometric_hahn_banach_open geometric_hahn_banach_open

theorem geometric_hahn_banach_open_point (hs₁ : Convex ℝ s) (hs₂ : IsOpen s) (disj : x ∉ s) :
    ∃ f : E →L[ℝ] ℝ, ∀ a ∈ s, f a < f x :=
  let ⟨f, s, hs, hx⟩ :=
    geometric_hahn_banach_open hs₁ hs₂ (convex_singleton x) (disjoint_singleton_right.2 disj)
  ⟨f, fun a ha => lt_of_lt_of_le (hs a ha) (hx x (mem_singleton _))⟩
#align geometric_hahn_banach_open_point geometric_hahn_banach_open_point

theorem geometric_hahn_banach_point_open (ht₁ : Convex ℝ t) (ht₂ : IsOpen t) (disj : x ∉ t) :
    ∃ f : E →L[ℝ] ℝ, ∀ b ∈ t, f x < f b :=
  let ⟨f, hf⟩ := geometric_hahn_banach_open_point ht₁ ht₂ disj
  ⟨-f, by simpa⟩
#align geometric_hahn_banach_point_open geometric_hahn_banach_point_open

theorem geometric_hahn_banach_open_open (hs₁ : Convex ℝ s) (hs₂ : IsOpen s) (ht₁ : Convex ℝ t)
    (ht₃ : IsOpen t) (disj : Disjoint s t) :
    ∃ (f : E →L[ℝ] ℝ)(u : ℝ), (∀ a ∈ s, f a < u) ∧ ∀ b ∈ t, u < f b :=
  by
  obtain rfl | ⟨a₀, ha₀⟩ := s.eq_empty_or_nonempty
  · exact ⟨0, -1, by simp, fun b hb => by norm_num⟩
  obtain rfl | ⟨b₀, hb₀⟩ := t.eq_empty_or_nonempty
  · exact ⟨0, 1, fun a ha => by norm_num, by simp⟩
  obtain ⟨f, s, hf₁, hf₂⟩ := geometric_hahn_banach_open hs₁ hs₂ ht₁ disj
  have hf : IsOpenMap f := by
    refine' f.is_open_map_of_ne_zero _
    rintro rfl
    exact (hf₁ _ ha₀).not_le (hf₂ _ hb₀)
  refine' ⟨f, s, hf₁, image_subset_iff.1 (_ : f '' t ⊆ Ioi s)⟩
  rw [← interior_Ici]
  refine' interior_maximal (image_subset_iff.2 hf₂) (f.is_open_map_of_ne_zero _ _ ht₃)
  rintro rfl
  exact (hf₁ _ ha₀).not_le (hf₂ _ hb₀)
#align geometric_hahn_banach_open_open geometric_hahn_banach_open_open

variable [LocallyConvexSpace ℝ E]

/-- A version of the **Hahn-Banach theorem**: given disjoint convex sets `s`, `t` where `s` is
compact and `t` is closed, there is a continuous linear functional which strongly separates them. -/
theorem geometric_hahn_banach_compact_closed (hs₁ : Convex ℝ s) (hs₂ : IsCompact s)
    (ht₁ : Convex ℝ t) (ht₂ : IsClosed t) (disj : Disjoint s t) :
    ∃ (f : E →L[ℝ] ℝ)(u v : ℝ), (∀ a ∈ s, f a < u) ∧ u < v ∧ ∀ b ∈ t, v < f b :=
  by
  obtain rfl | hs := s.eq_empty_or_nonempty
  · exact ⟨0, -2, -1, by simp, by norm_num, fun b hb => by norm_num⟩
  obtain rfl | ht := t.eq_empty_or_nonempty
  · exact ⟨0, 1, 2, fun a ha => by norm_num, by norm_num, by simp⟩
  obtain ⟨U, V, hU, hV, hU₁, hV₁, sU, tV, disj'⟩ := disj.exists_open_convexes hs₁ hs₂ ht₁ ht₂
  obtain ⟨f, u, hf₁, hf₂⟩ := geometric_hahn_banach_open_open hU₁ hU hV₁ hV disj'
  obtain ⟨x, hx₁, hx₂⟩ := hs₂.exists_forall_ge hs f.continuous.continuous_on
  have : f x < u := hf₁ x (sU hx₁)
  exact
    ⟨f, (f x + u) / 2, u, fun a ha => by linarith [hx₂ a ha], by linarith, fun b hb =>
      hf₂ b (tV hb)⟩
#align geometric_hahn_banach_compact_closed geometric_hahn_banach_compact_closed

/-- A version of the **Hahn-Banach theorem**: given disjoint convex sets `s`, `t` where `s` is
closed, and `t` is compact, there is a continuous linear functional which strongly separates them.
-/
theorem geometric_hahn_banach_closed_compact (hs₁ : Convex ℝ s) (hs₂ : IsClosed s)
    (ht₁ : Convex ℝ t) (ht₂ : IsCompact t) (disj : Disjoint s t) :
    ∃ (f : E →L[ℝ] ℝ)(u v : ℝ), (∀ a ∈ s, f a < u) ∧ u < v ∧ ∀ b ∈ t, v < f b :=
  let ⟨f, s, t, hs, st, ht⟩ := geometric_hahn_banach_compact_closed ht₁ ht₂ hs₁ hs₂ disj.symm
  ⟨-f, -t, -s, by simpa using ht, by simpa using st, by simpa using hs⟩
#align geometric_hahn_banach_closed_compact geometric_hahn_banach_closed_compact

theorem geometric_hahn_banach_point_closed (ht₁ : Convex ℝ t) (ht₂ : IsClosed t) (disj : x ∉ t) :
    ∃ (f : E →L[ℝ] ℝ)(u : ℝ), f x < u ∧ ∀ b ∈ t, u < f b :=
  let ⟨f, u, v, ha, hst, hb⟩ :=
    geometric_hahn_banach_compact_closed (convex_singleton x) isCompact_singleton ht₁ ht₂
      (disjoint_singleton_left.2 disj)
  ⟨f, v, hst.trans' <| ha x <| mem_singleton _, hb⟩
#align geometric_hahn_banach_point_closed geometric_hahn_banach_point_closed

theorem geometric_hahn_banach_closed_point (hs₁ : Convex ℝ s) (hs₂ : IsClosed s) (disj : x ∉ s) :
    ∃ (f : E →L[ℝ] ℝ)(u : ℝ), (∀ a ∈ s, f a < u) ∧ u < f x :=
  let ⟨f, s, t, ha, hst, hb⟩ :=
    geometric_hahn_banach_closed_compact hs₁ hs₂ (convex_singleton x) isCompact_singleton
      (disjoint_singleton_right.2 disj)
  ⟨f, s, ha, hst.trans <| hb x <| mem_singleton _⟩
#align geometric_hahn_banach_closed_point geometric_hahn_banach_closed_point

/-- See also `normed_space.eq_iff_forall_dual_eq`. -/
theorem geometric_hahn_banach_point_point [T1Space E] (hxy : x ≠ y) : ∃ f : E →L[ℝ] ℝ, f x < f y :=
  by
  obtain ⟨f, s, t, hs, st, ht⟩ :=
    geometric_hahn_banach_compact_closed (convex_singleton x) isCompact_singleton
      (convex_singleton y) isClosed_singleton (disjoint_singleton.2 hxy)
  exact ⟨f, by linarith [hs x rfl, ht y rfl]⟩
#align geometric_hahn_banach_point_point geometric_hahn_banach_point_point

/-- A closed convex set is the intersection of the halfspaces containing it. -/
theorem interᵢ_halfspaces_eq (hs₁ : Convex ℝ s) (hs₂ : IsClosed s) :
    (⋂ l : E →L[ℝ] ℝ, { x | ∃ y ∈ s, l x ≤ l y }) = s :=
  by
  rw [Set.interᵢ_setOf]
  refine' Set.Subset.antisymm (fun x hx => _) fun x hx l => ⟨x, hx, le_rfl⟩
  by_contra
  obtain ⟨l, s, hlA, hl⟩ := geometric_hahn_banach_closed_point hs₁ hs₂ h
  obtain ⟨y, hy, hxy⟩ := hx l
  exact ((hxy.trans_lt (hlA y hy)).trans hl).not_le le_rfl
#align Inter_halfspaces_eq interᵢ_halfspaces_eq


/-
Copyright (c) 2018 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johannes Hölzl
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.algebra.big_operators.order
import Mathlib.group_theory.coset
import Mathlib.data.nat.totient
import Mathlib.data.int.gcd
import Mathlib.data.set.finite
import Mathlib.PostPort

universes u_1 u_2 l

namespace Mathlib

-- TODO mem_range_iff_mem_finset_range_of_mod_eq should be moved elsewhere.

namespace finset

theorem mem_range_iff_mem_finset_range_of_mod_eq {α : Type u_1} [DecidableEq α] {f : ℤ → α} {a : α} {n : ℕ} (hn : 0 < n) (h : ∀ (i : ℤ), f (i % ↑n) = f i) : a ∈ set.range f ↔ a ∈ image (fun (i : ℕ) => f ↑i) (range n) := sorry

end finset

theorem mem_normalizer_fintype {α : Type u_1} [group α] {s : set α} [fintype ↥s] {x : α} (h : ∀ (n : α), n ∈ s → x * n * (x⁻¹) ∈ s) : x ∈ subgroup.set_normalizer s := sorry

protected instance fintype_bot {α : Type u_1} [group α] : fintype ↥⊥ :=
  fintype.mk (singleton 1) sorry

@[simp] theorem card_trivial {α : Type u_1} [group α] : fintype.card ↥⊥ = 1 := sorry

theorem card_eq_card_quotient_mul_card_subgroup {α : Type u_1} [group α] [fintype α] (s : subgroup α) [fintype ↥s] [decidable_pred fun (a : α) => a ∈ s] : fintype.card α = fintype.card (quotient_group.quotient s) * fintype.card ↥s := sorry

theorem card_subgroup_dvd_card {α : Type u_1} [group α] [fintype α] (s : subgroup α) [fintype ↥s] : fintype.card ↥s ∣ fintype.card α := sorry

theorem card_quotient_dvd_card {α : Type u_1} [group α] [fintype α] (s : subgroup α) [decidable_pred fun (a : α) => a ∈ s] [fintype ↥s] : fintype.card (quotient_group.quotient s) ∣ fintype.card α := sorry

theorem exists_gpow_eq_one {α : Type u_1} [group α] [fintype α] (a : α) : ∃ (i : ℤ), ∃ (H : i ≠ 0), a ^ i = 1 := sorry

theorem exists_pow_eq_one {α : Type u_1} [group α] [fintype α] (a : α) : ∃ (i : ℕ), ∃ (H : i > 0), a ^ i = 1 := sorry

/-- `order_of a` is the order of the element `a`, i.e. the `n ≥ 1`, s.t. `a ^ n = 1` -/
def order_of {α : Type u_1} [group α] [fintype α] [dec : DecidableEq α] (a : α) : ℕ :=
  nat.find (exists_pow_eq_one a)

theorem pow_order_of_eq_one {α : Type u_1} [group α] [fintype α] [dec : DecidableEq α] (a : α) : a ^ order_of a = 1 := sorry

theorem order_of_pos {α : Type u_1} [group α] [fintype α] [dec : DecidableEq α] (a : α) : 0 < order_of a := sorry

theorem pow_injective_of_lt_order_of {α : Type u_1} [group α] [fintype α] [dec : DecidableEq α] {n : ℕ} {m : ℕ} (a : α) (hn : n < order_of a) (hm : m < order_of a) (eq : a ^ n = a ^ m) : n = m :=
  or.elim (le_total n m) (fun (h : n ≤ m) => pow_injective_aux a h hn hm eq)
    fun (h : m ≤ n) => Eq.symm (pow_injective_aux a h hm hn (Eq.symm eq))

theorem order_of_le_card_univ {α : Type u_1} {a : α} [group α] [fintype α] [dec : DecidableEq α] : order_of a ≤ fintype.card α :=
  finset.card_le_of_inj_on (pow a) (fun (n : ℕ) (_x : n < order_of a) => fintype.complete (a ^ n))
    fun (i j : ℕ) => pow_injective_of_lt_order_of a

theorem pow_eq_mod_order_of {α : Type u_1} {a : α} [group α] [fintype α] [dec : DecidableEq α] {n : ℕ} : a ^ n = a ^ (n % order_of a) := sorry

theorem gpow_eq_mod_order_of {α : Type u_1} {a : α} [group α] [fintype α] [dec : DecidableEq α] {i : ℤ} : a ^ i = a ^ (i % ↑(order_of a)) := sorry

theorem mem_gpowers_iff_mem_range_order_of {α : Type u_1} [group α] [fintype α] [dec : DecidableEq α] {a : α} {a' : α} : a' ∈ subgroup.gpowers a ↔ a' ∈ finset.image (pow a) (finset.range (order_of a)) :=
  finset.mem_range_iff_mem_finset_range_of_mod_eq (order_of_pos a) fun (i : ℤ) => Eq.symm gpow_eq_mod_order_of

protected instance decidable_gpowers {α : Type u_1} {a : α} [group α] [fintype α] [dec : DecidableEq α] : decidable_pred ↑(subgroup.gpowers a) :=
  fun (a' : α) => decidable_of_iff' (a' ∈ finset.image (pow a) (finset.range (order_of a))) sorry

theorem order_of_dvd_of_pow_eq_one {α : Type u_1} {a : α} [group α] [fintype α] [dec : DecidableEq α] {n : ℕ} (h : a ^ n = 1) : order_of a ∣ n := sorry

theorem order_of_dvd_iff_pow_eq_one {α : Type u_1} {a : α} [group α] [fintype α] [dec : DecidableEq α] {n : ℕ} : order_of a ∣ n ↔ a ^ n = 1 := sorry

theorem order_of_le_of_pow_eq_one {α : Type u_1} {a : α} [group α] [fintype α] [dec : DecidableEq α] {n : ℕ} (hn : 0 < n) (h : a ^ n = 1) : order_of a ≤ n :=
  nat.find_min' (exists_pow_eq_one a) (Exists.intro hn h)

theorem sum_card_order_of_eq_card_pow_eq_one {α : Type u_1} [group α] [fintype α] [dec : DecidableEq α] {n : ℕ} (hn : 0 < n) : (finset.sum (finset.filter (fun (_x : ℕ) => _x ∣ n) (finset.range (Nat.succ n)))
    fun (m : ℕ) => finset.card (finset.filter (fun (a : α) => order_of a = m) finset.univ)) =
  finset.card (finset.filter (fun (a : α) => a ^ n = 1) finset.univ) := sorry

theorem order_eq_card_gpowers {α : Type u_1} {a : α} [group α] [fintype α] [dec : DecidableEq α] : order_of a = fintype.card ↥↑(subgroup.gpowers a) := sorry

@[simp] theorem order_of_one {α : Type u_1} [group α] [fintype α] [dec : DecidableEq α] : order_of 1 = 1 := sorry

@[simp] theorem order_of_eq_one_iff {α : Type u_1} {a : α} [group α] [fintype α] [dec : DecidableEq α] : order_of a = 1 ↔ a = 1 := sorry

theorem order_of_eq_prime {α : Type u_1} {a : α} [group α] [fintype α] [dec : DecidableEq α] {p : ℕ} [hp : fact (nat.prime p)] (hg : a ^ p = 1) (hg1 : a ≠ 1) : order_of a = p :=
  or.resolve_left (and.right hp (order_of a) (order_of_dvd_of_pow_eq_one hg)) (mt (iff.mp order_of_eq_one_iff) hg1)

/- TODO: use cardinal theory, introduce `card : set α → ℕ`, or setup decidability for cosets -/

theorem order_of_dvd_card_univ {α : Type u_1} {a : α} [group α] [fintype α] [dec : DecidableEq α] : order_of a ∣ fintype.card α := sorry

@[simp] theorem pow_card_eq_one {α : Type u_1} [group α] [fintype α] (a : α) : a ^ fintype.card α = 1 := sorry

theorem mem_powers_iff_mem_gpowers {α : Type u_1} [group α] [fintype α] {a : α} {x : α} : x ∈ submonoid.powers a ↔ x ∈ subgroup.gpowers a := sorry

theorem powers_eq_gpowers {α : Type u_1} [group α] [fintype α] (a : α) : ↑(submonoid.powers a) = ↑(subgroup.gpowers a) :=
  set.ext fun (x : α) => mem_powers_iff_mem_gpowers

theorem order_of_pow {α : Type u_1} [group α] [fintype α] [dec : DecidableEq α] (a : α) (n : ℕ) : order_of (a ^ n) = order_of a / nat.gcd (order_of a) n := sorry

theorem image_range_order_of {α : Type u_1} [group α] [fintype α] [dec : DecidableEq α] (a : α) : finset.image (fun (i : ℕ) => a ^ i) (finset.range (order_of a)) = set.to_finset ↑(subgroup.gpowers a) := sorry

theorem pow_gcd_card_eq_one_iff {α : Type u_1} [group α] [fintype α] {n : ℕ} {a : α} : a ^ n = 1 ↔ a ^ nat.gcd n (fintype.card α) = 1 := sorry

/-- A group is called *cyclic* if it is generated by a single element. -/
class is_cyclic (α : Type u_2) [group α]
where
  exists_generator : ∃ (g : α), ∀ (x : α), x ∈ subgroup.gpowers g

/-- A cyclic group is always commutative. This is not an `instance` because often we have a better
proof of `comm_group`. -/
def is_cyclic.comm_group {α : Type u_1} [hg : group α] [is_cyclic α] : comm_group α :=
  comm_group.mk group.mul group.mul_assoc group.one group.one_mul group.mul_one group.inv group.div group.mul_left_inv
    sorry

theorem is_cyclic_of_order_of_eq_card {α : Type u_1} [group α] [DecidableEq α] [fintype α] (x : α) (hx : order_of x = fintype.card α) : is_cyclic α := sorry

theorem is_cyclic_of_prime_card {α : Type u_1} [group α] [fintype α] {p : ℕ} [hp : fact (nat.prime p)] (h : fintype.card α = p) : is_cyclic α := sorry

theorem order_of_eq_card_of_forall_mem_gpowers {α : Type u_1} [group α] [DecidableEq α] [fintype α] {g : α} (hx : ∀ (x : α), x ∈ subgroup.gpowers g) : order_of g = fintype.card α := sorry

protected instance bot.is_cyclic {α : Type u_1} [group α] : is_cyclic ↥⊥ :=
  is_cyclic.mk
    (Exists.intro 1 fun (x : ↥⊥) => Exists.intro 0 (subtype.eq (Eq.symm (iff.mp subgroup.mem_bot (subtype.property x)))))

protected instance subgroup.is_cyclic {α : Type u_1} [group α] [is_cyclic α] (H : subgroup α) : is_cyclic ↥H :=
  sorry

theorem is_cyclic.card_pow_eq_one_le {α : Type u_1} [group α] [DecidableEq α] [fintype α] [is_cyclic α] {n : ℕ} (hn0 : 0 < n) : finset.card (finset.filter (fun (a : α) => a ^ n = 1) finset.univ) ≤ n := sorry

theorem is_cyclic.exists_monoid_generator (α : Type u_1) [group α] [fintype α] [is_cyclic α] : ∃ (x : α), ∀ (y : α), y ∈ submonoid.powers x := sorry

theorem is_cyclic.image_range_order_of {α : Type u_1} {a : α} [group α] [DecidableEq α] [fintype α] (ha : ∀ (x : α), x ∈ subgroup.gpowers a) : finset.image (fun (i : ℕ) => a ^ i) (finset.range (order_of a)) = finset.univ := sorry

theorem is_cyclic.image_range_card {α : Type u_1} {a : α} [group α] [DecidableEq α] [fintype α] (ha : ∀ (x : α), x ∈ subgroup.gpowers a) : finset.image (fun (i : ℕ) => a ^ i) (finset.range (fintype.card α)) = finset.univ := sorry

theorem card_pow_eq_one_eq_order_of_aux {α : Type u_1} [group α] [DecidableEq α] [fintype α] (hn : ∀ (n : ℕ), 0 < n → finset.card (finset.filter (fun (a : α) => a ^ n = 1) finset.univ) ≤ n) (a : α) : finset.card (finset.filter (fun (b : α) => b ^ order_of a = 1) finset.univ) = order_of a := sorry

theorem card_order_of_eq_totient_aux₂ {α : Type u_1} [group α] [DecidableEq α] [fintype α] (hn : ∀ (n : ℕ), 0 < n → finset.card (finset.filter (fun (a : α) => a ^ n = 1) finset.univ) ≤ n) {d : ℕ} (hd : d ∣ fintype.card α) : finset.card (finset.filter (fun (a : α) => order_of a = d) finset.univ) = nat.totient d := sorry

theorem is_cyclic_of_card_pow_eq_one_le {α : Type u_1} [group α] [DecidableEq α] [fintype α] (hn : ∀ (n : ℕ), 0 < n → finset.card (finset.filter (fun (a : α) => a ^ n = 1) finset.univ) ≤ n) : is_cyclic α := sorry

theorem is_cyclic.card_order_of_eq_totient {α : Type u_1} [group α] [is_cyclic α] [DecidableEq α] [fintype α] {d : ℕ} (hd : d ∣ fintype.card α) : finset.card (finset.filter (fun (a : α) => order_of a = d) finset.univ) = nat.totient d :=
  card_order_of_eq_totient_aux₂ (fun (n : ℕ) => is_cyclic.card_pow_eq_one_le) hd