/-
Copyright (c) 2021 Kevin Buzzard. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kevin Buzzard, Ines Wright, Joachim Breitner

! This file was ported from Lean 3 source module group_theory.nilpotent
! leanprover-community/mathlib commit 2bbc7e3884ba234309d2a43b19144105a753292e
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.GroupTheory.QuotientGroup
import Mathbin.GroupTheory.Solvable
import Mathbin.GroupTheory.PGroup
import Mathbin.GroupTheory.Sylow
import Mathbin.Data.Nat.Factorization.Basic
import Mathbin.Tactic.Tfae

/-!

# Nilpotent groups

An API for nilpotent groups, that is, groups for which the upper central series
reaches `⊤`.

## Main definitions

Recall that if `H K : subgroup G` then `⁅H, K⁆ : subgroup G` is the subgroup of `G` generated
by the commutators `hkh⁻¹k⁻¹`. Recall also Lean's conventions that `⊤` denotes the
subgroup `G` of `G`, and `⊥` denotes the trivial subgroup `{1}`.

* `upper_central_series G : ℕ → subgroup G` : the upper central series of a group `G`.
     This is an increasing sequence of normal subgroups `H n` of `G` with `H 0 = ⊥` and
     `H (n + 1) / H n` is the centre of `G / H n`.
* `lower_central_series G : ℕ → subgroup G` : the lower central series of a group `G`.
     This is a decreasing sequence of normal subgroups `H n` of `G` with `H 0 = ⊤` and
     `H (n + 1) = ⁅H n, G⁆`.
* `is_nilpotent` : A group G is nilpotent if its upper central series reaches `⊤`, or
    equivalently if its lower central series reaches `⊥`.
* `nilpotency_class` : the length of the upper central series of a nilpotent group.
* `is_ascending_central_series (H : ℕ → subgroup G) : Prop` and
* `is_descending_central_series (H : ℕ → subgroup G) : Prop` : Note that in the literature
    a "central series" for a group is usually defined to be a *finite* sequence of normal subgroups
    `H 0`, `H 1`, ..., starting at `⊤`, finishing at `⊥`, and with each `H n / H (n + 1)`
    central in `G / H (n + 1)`. In this formalisation it is convenient to have two weaker predicates
    on an infinite sequence of subgroups `H n` of `G`: we say a sequence is a *descending central
    series* if it starts at `G` and `⁅H n, ⊤⁆ ⊆ H (n + 1)` for all `n`. Note that this series
    may not terminate at `⊥`, and the `H i` need not be normal. Similarly a sequence is an
    *ascending central series* if `H 0 = ⊥` and `⁅H (n + 1), ⊤⁆ ⊆ H n` for all `n`, again with no
    requirement that the series reaches `⊤` or that the `H i` are normal.

## Main theorems

`G` is *defined* to be nilpotent if the upper central series reaches `⊤`.
* `nilpotent_iff_finite_ascending_central_series` : `G` is nilpotent iff some ascending central
    series reaches `⊤`.
* `nilpotent_iff_finite_descending_central_series` : `G` is nilpotent iff some descending central
    series reaches `⊥`.
* `nilpotent_iff_lower` : `G` is nilpotent iff the lower central series reaches `⊥`.
* The `nilpotency_class` can likeways be obtained from these equivalent
  definitions, see `least_ascending_central_series_length_eq_nilpotency_class`,
  `least_descending_central_series_length_eq_nilpotency_class` and
  `lower_central_series_length_eq_nilpotency_class`.
* If `G` is nilpotent, then so are its subgroups, images, quotients and preimages.
  Binary and finite products of nilpotent groups are nilpotent.
  Infinite products are nilpotent if their nilpotent class is bounded.
  Corresponding lemmas about the `nilpotency_class` are provided.
* The `nilpotency_class` of `G ⧸ center G` is given explicitly, and an induction principle
  is derived from that.
* `is_nilpotent.to_is_solvable`: If `G` is nilpotent, it is solvable.

## Warning

A "central series" is usually defined to be a finite sequence of normal subgroups going
from `⊥` to `⊤` with the property that each subquotient is contained within the centre of
the associated quotient of `G`. This means that if `G` is not nilpotent, then
none of what we have called `upper_central_series G`, `lower_central_series G` or
the sequences satisfying `is_ascending_central_series` or `is_descending_central_series`
are actually central series. Note that the fact that the upper and lower central series
are not central series if `G` is not nilpotent is a standard abuse of notation.

-/

open Subgroup

section WithGroup

variable {G : Type _} [Group G] (H : Subgroup G) [Normal H]

/-- If `H` is a normal subgroup of `G`, then the set `{x : G | ∀ y : G, x*y*x⁻¹*y⁻¹ ∈ H}`
is a subgroup of `G` (because it is the preimage in `G` of the centre of the
quotient group `G/H`.)
-/
def upperCentralSeriesStep : Subgroup G
    where
  carrier := { x : G | ∀ y : G, x * y * x⁻¹ * y⁻¹ ∈ H }
  one_mem' y := by simp [Subgroup.one_mem]
  mul_mem' a b ha hb y :=
    by
    convert Subgroup.mul_mem _ (ha (b * y * b⁻¹)) (hb y) using 1
    group
  inv_mem' x hx y := by
    specialize hx y⁻¹
    rw [mul_assoc, inv_inv] at hx⊢
    exact Subgroup.Normal.mem_comm inferInstance hx
#align upper_central_series_step upperCentralSeriesStep

theorem mem_upperCentralSeriesStep (x : G) :
    x ∈ upperCentralSeriesStep H ↔ ∀ y, x * y * x⁻¹ * y⁻¹ ∈ H :=
  Iff.rfl
#align mem_upper_central_series_step mem_upperCentralSeriesStep

open QuotientGroup

/-- The proof that `upper_central_series_step H` is the preimage of the centre of `G/H` under
the canonical surjection. -/
theorem upperCentralSeriesStep_eq_comap_center :
    upperCentralSeriesStep H = Subgroup.comap (mk' H) (center (G ⧸ H)) :=
  by
  ext
  rw [mem_comap, mem_center_iff, forall_coe]
  apply forall_congr'
  intro y
  rw [coe_mk', ← QuotientGroup.mk_mul, ← QuotientGroup.mk_mul, eq_comm, eq_iff_div_mem,
    div_eq_mul_inv, mul_inv_rev, mul_assoc]
#align upper_central_series_step_eq_comap_center upperCentralSeriesStep_eq_comap_center

instance : Normal (upperCentralSeriesStep H) :=
  by
  rw [upperCentralSeriesStep_eq_comap_center]
  infer_instance

variable (G)

/-- An auxiliary type-theoretic definition defining both the upper central series of
a group, and a proof that it is normal, all in one go. -/
def upperCentralSeriesAux : ℕ → Σ'H : Subgroup G, Normal H
  | 0 => ⟨⊥, inferInstance⟩
  | n + 1 =>
    let un := upperCentralSeriesAux n
    let un_normal := un.2
    ⟨upperCentralSeriesStep un.1, inferInstance⟩
#align upper_central_series_aux upperCentralSeriesAux

/-- `upper_central_series G n` is the `n`th term in the upper central series of `G`. -/
def upperCentralSeries (n : ℕ) : Subgroup G :=
  (upperCentralSeriesAux G n).1
#align upper_central_series upperCentralSeries

instance (n : ℕ) : Normal (upperCentralSeries G n) :=
  (upperCentralSeriesAux G n).2

@[simp]
theorem upperCentralSeries_zero : upperCentralSeries G 0 = ⊥ :=
  rfl
#align upper_central_series_zero upperCentralSeries_zero

@[simp]
theorem upperCentralSeries_one : upperCentralSeries G 1 = center G :=
  by
  ext
  simp only [upperCentralSeries, upperCentralSeriesAux, upperCentralSeriesStep, center, Set.center,
    mem_mk, mem_bot, Set.mem_setOf_eq]
  exact forall_congr' fun y => by rw [mul_inv_eq_one, mul_inv_eq_iff_eq_mul, eq_comm]
#align upper_central_series_one upperCentralSeries_one

/-- The `n+1`st term of the upper central series `H i` has underlying set equal to the `x` such
that `⁅x,G⁆ ⊆ H n`-/
theorem mem_upperCentralSeries_succ_iff (n : ℕ) (x : G) :
    x ∈ upperCentralSeries G (n + 1) ↔ ∀ y : G, x * y * x⁻¹ * y⁻¹ ∈ upperCentralSeries G n :=
  Iff.rfl
#align mem_upper_central_series_succ_iff mem_upperCentralSeries_succ_iff

/- ./././Mathport/Syntax/Translate/Command.lean:388:30: infer kinds are unsupported in Lean 4: #[`nilpotent] [] -/
-- is_nilpotent is already defined in the root namespace (for elements of rings).
/-- A group `G` is nilpotent if its upper central series is eventually `G`. -/
class Group.IsNilpotent (G : Type _) [Group G] : Prop where
  nilpotent : ∃ n : ℕ, upperCentralSeries G n = ⊤
#align group.is_nilpotent Group.IsNilpotent

open Group

variable {G}

/-- A sequence of subgroups of `G` is an ascending central series if `H 0` is trivial and
  `⁅H (n + 1), G⁆ ⊆ H n` for all `n`. Note that we do not require that `H n = G` for some `n`. -/
def IsAscendingCentralSeries (H : ℕ → Subgroup G) : Prop :=
  H 0 = ⊥ ∧ ∀ (x : G) (n : ℕ), x ∈ H (n + 1) → ∀ g, x * g * x⁻¹ * g⁻¹ ∈ H n
#align is_ascending_central_series IsAscendingCentralSeries

/-- A sequence of subgroups of `G` is a descending central series if `H 0` is `G` and
  `⁅H n, G⁆ ⊆ H (n + 1)` for all `n`. Note that we do not requre that `H n = {1}` for some `n`. -/
def IsDescendingCentralSeries (H : ℕ → Subgroup G) :=
  H 0 = ⊤ ∧ ∀ (x : G) (n : ℕ), x ∈ H n → ∀ g, x * g * x⁻¹ * g⁻¹ ∈ H (n + 1)
#align is_descending_central_series IsDescendingCentralSeries

/-- Any ascending central series for a group is bounded above by the upper central series. -/
theorem ascending_central_series_le_upper (H : ℕ → Subgroup G) (hH : IsAscendingCentralSeries H) :
    ∀ n : ℕ, H n ≤ upperCentralSeries G n
  | 0 => hH.1.symm ▸ le_refl ⊥
  | n + 1 => by
    intro x hx
    rw [mem_upperCentralSeries_succ_iff]
    exact fun y => ascending_central_series_le_upper n (hH.2 x n hx y)
#align ascending_central_series_le_upper ascending_central_series_le_upper

variable (G)

/-- The upper central series of a group is an ascending central series. -/
theorem upperCentralSeries_isAscendingCentralSeries :
    IsAscendingCentralSeries (upperCentralSeries G) :=
  ⟨rfl, fun x n h => h⟩
#align upper_central_series_is_ascending_central_series upperCentralSeries_isAscendingCentralSeries

theorem upperCentralSeries_mono : Monotone (upperCentralSeries G) :=
  by
  refine' monotone_nat_of_le_succ _
  intro n x hx y
  rw [mul_assoc, mul_assoc, ← mul_assoc y x⁻¹ y⁻¹]
  exact mul_mem hx (normal.conj_mem (upperCentralSeries.Subgroup.normal G n) x⁻¹ (inv_mem hx) y)
#align upper_central_series_mono upperCentralSeries_mono

/-- A group `G` is nilpotent iff there exists an ascending central series which reaches `G` in
  finitely many steps. -/
theorem nilpotent_iff_finite_ascending_central_series :
    IsNilpotent G ↔ ∃ n : ℕ, ∃ H : ℕ → Subgroup G, IsAscendingCentralSeries H ∧ H n = ⊤ :=
  by
  constructor
  · rintro ⟨n, nH⟩
    refine' ⟨_, _, upperCentralSeries_isAscendingCentralSeries G, nH⟩
  · rintro ⟨n, H, hH, hn⟩
    use n
    rw [eq_top_iff, ← hn]
    exact ascending_central_series_le_upper H hH n
#align nilpotent_iff_finite_ascending_central_series nilpotent_iff_finite_ascending_central_series

theorem is_decending_rev_series_of_is_ascending {H : ℕ → Subgroup G} {n : ℕ} (hn : H n = ⊤)
    (hasc : IsAscendingCentralSeries H) : IsDescendingCentralSeries fun m : ℕ => H (n - m) :=
  by
  cases' hasc with h0 hH
  refine' ⟨hn, fun x m hx g => _⟩
  dsimp at hx
  by_cases hm : n ≤ m
  · rw [tsub_eq_zero_of_le hm, h0, Subgroup.mem_bot] at hx
    subst hx
    convert Subgroup.one_mem _
    group
  · push_neg  at hm
    apply hH
    convert hx
    rw [tsub_add_eq_add_tsub (Nat.succ_le_of_lt hm), Nat.succ_sub_succ]
#align is_decending_rev_series_of_is_ascending is_decending_rev_series_of_is_ascending

theorem is_ascending_rev_series_of_is_descending {H : ℕ → Subgroup G} {n : ℕ} (hn : H n = ⊥)
    (hdesc : IsDescendingCentralSeries H) : IsAscendingCentralSeries fun m : ℕ => H (n - m) :=
  by
  cases' hdesc with h0 hH
  refine' ⟨hn, fun x m hx g => _⟩
  dsimp only at hx⊢
  by_cases hm : n ≤ m
  · have hnm : n - m = 0 := tsub_eq_zero_iff_le.mpr hm
    rw [hnm, h0]
    exact mem_top _
  · push_neg  at hm
    convert hH x _ hx g
    rw [tsub_add_eq_add_tsub (Nat.succ_le_of_lt hm), Nat.succ_sub_succ]
#align is_ascending_rev_series_of_is_descending is_ascending_rev_series_of_is_descending

/-- A group `G` is nilpotent iff there exists a descending central series which reaches the
  trivial group in a finite time. -/
theorem nilpotent_iff_finite_descending_central_series :
    IsNilpotent G ↔ ∃ n : ℕ, ∃ H : ℕ → Subgroup G, IsDescendingCentralSeries H ∧ H n = ⊥ :=
  by
  rw [nilpotent_iff_finite_ascending_central_series]
  constructor
  · rintro ⟨n, H, hH, hn⟩
    refine' ⟨n, fun m => H (n - m), is_decending_rev_series_of_is_ascending G hn hH, _⟩
    rw [tsub_self]
    exact hH.1
  · rintro ⟨n, H, hH, hn⟩
    refine' ⟨n, fun m => H (n - m), is_ascending_rev_series_of_is_descending G hn hH, _⟩
    rw [tsub_self]
    exact hH.1
#align nilpotent_iff_finite_descending_central_series nilpotent_iff_finite_descending_central_series

/-- The lower central series of a group `G` is a sequence `H n` of subgroups of `G`, defined
  by `H 0` is all of `G` and for `n≥1`, `H (n + 1) = ⁅H n, G⁆` -/
def lowerCentralSeries (G : Type _) [Group G] : ℕ → Subgroup G
  | 0 => ⊤
  | n + 1 => ⁅lowerCentralSeries n, ⊤⁆
#align lower_central_series lowerCentralSeries

variable {G}

@[simp]
theorem lowerCentralSeries_zero : lowerCentralSeries G 0 = ⊤ :=
  rfl
#align lower_central_series_zero lowerCentralSeries_zero

@[simp]
theorem lowerCentralSeries_one : lowerCentralSeries G 1 = commutator G :=
  rfl
#align lower_central_series_one lowerCentralSeries_one

theorem mem_lowerCentralSeries_succ_iff (n : ℕ) (q : G) :
    q ∈ lowerCentralSeries G (n + 1) ↔
      q ∈
        closure
          { x | ∃ p ∈ lowerCentralSeries G n, ∃ q ∈ (⊤ : Subgroup G), p * q * p⁻¹ * q⁻¹ = x } :=
  Iff.rfl
#align mem_lower_central_series_succ_iff mem_lowerCentralSeries_succ_iff

theorem lowerCentralSeries_succ (n : ℕ) :
    lowerCentralSeries G (n + 1) =
      closure { x | ∃ p ∈ lowerCentralSeries G n, ∃ q ∈ (⊤ : Subgroup G), p * q * p⁻¹ * q⁻¹ = x } :=
  rfl
#align lower_central_series_succ lowerCentralSeries_succ

instance (n : ℕ) : Normal (lowerCentralSeries G n) :=
  by
  induction' n with d hd
  · exact (⊤ : Subgroup G).normal_of_characteristic
  · exact Subgroup.commutator_normal (lowerCentralSeries G d) ⊤

theorem lowerCentralSeries_antitone : Antitone (lowerCentralSeries G) :=
  by
  refine' antitone_nat_of_succ_le fun n x hx => _
  simp only [mem_lowerCentralSeries_succ_iff, exists_prop, mem_top, exists_true_left,
    true_and_iff] at hx
  refine'
    closure_induction hx _ (Subgroup.one_mem _) (@Subgroup.mul_mem _ _ _) (@Subgroup.inv_mem _ _ _)
  rintro y ⟨z, hz, a, ha⟩
  rw [← ha, mul_assoc, mul_assoc, ← mul_assoc a z⁻¹ a⁻¹]
  exact mul_mem hz (normal.conj_mem (lowerCentralSeries.Subgroup.normal n) z⁻¹ (inv_mem hz) a)
#align lower_central_series_antitone lowerCentralSeries_antitone

/-- The lower central series of a group is a descending central series. -/
theorem lowerCentralSeries_isDescendingCentralSeries :
    IsDescendingCentralSeries (lowerCentralSeries G) :=
  by
  constructor; rfl
  intro x n hxn g
  exact commutator_mem_commutator hxn (mem_top g)
#align lower_central_series_is_descending_central_series lowerCentralSeries_isDescendingCentralSeries

/-- Any descending central series for a group is bounded below by the lower central series. -/
theorem descending_central_series_ge_lower (H : ℕ → Subgroup G) (hH : IsDescendingCentralSeries H) :
    ∀ n : ℕ, lowerCentralSeries G n ≤ H n
  | 0 => hH.1.symm ▸ le_refl ⊤
  | n + 1 => commutator_le.mpr fun x hx q _ => hH.2 x n (descending_central_series_ge_lower n hx) q
#align descending_central_series_ge_lower descending_central_series_ge_lower

/-- A group is nilpotent if and only if its lower central series eventually reaches
  the trivial subgroup. -/
theorem nilpotent_iff_lowerCentralSeries : IsNilpotent G ↔ ∃ n, lowerCentralSeries G n = ⊥ :=
  by
  rw [nilpotent_iff_finite_descending_central_series]
  constructor
  · rintro ⟨n, H, ⟨h0, hs⟩, hn⟩
    use n
    rw [eq_bot_iff, ← hn]
    exact descending_central_series_ge_lower H ⟨h0, hs⟩ n
  · rintro ⟨n, hn⟩
    exact ⟨n, lowerCentralSeries G, lowerCentralSeries_isDescendingCentralSeries, hn⟩
#align nilpotent_iff_lower_central_series nilpotent_iff_lowerCentralSeries

section Classical

open Classical

variable [hG : IsNilpotent G]

include hG

variable (G)

/-- The nilpotency class of a nilpotent group is the smallest natural `n` such that
the `n`'th term of the upper central series is `G`. -/
noncomputable def Group.nilpotencyClass : ℕ :=
  Nat.find (IsNilpotent.nilpotent G)
#align group.nilpotency_class Group.nilpotencyClass

variable {G}

@[simp]
theorem upperCentralSeries_nilpotencyClass : upperCentralSeries G (Group.nilpotencyClass G) = ⊤ :=
  Nat.find_spec (IsNilpotent.nilpotent G)
#align upper_central_series_nilpotency_class upperCentralSeries_nilpotencyClass

theorem upperCentralSeries_eq_top_iff_nilpotencyClass_le {n : ℕ} :
    upperCentralSeries G n = ⊤ ↔ Group.nilpotencyClass G ≤ n :=
  by
  constructor
  · intro h
    exact Nat.find_le h
  · intro h
    apply eq_top_iff.mpr
    rw [← upperCentralSeries_nilpotencyClass]
    exact upperCentralSeries_mono _ h
#align upper_central_series_eq_top_iff_nilpotency_class_le upperCentralSeries_eq_top_iff_nilpotencyClass_le

/-- The nilpotency class of a nilpotent `G` is equal to the smallest `n` for which an ascending
central series reaches `G` in its `n`'th term. -/
theorem least_ascending_central_series_length_eq_nilpotencyClass :
    Nat.find ((nilpotent_iff_finite_ascending_central_series G).mp hG) = Group.nilpotencyClass G :=
  by
  refine' le_antisymm (Nat.find_mono _) (Nat.find_mono _)
  · intro n hn
    exact ⟨upperCentralSeries G, upperCentralSeries_isAscendingCentralSeries G, hn⟩
  · rintro n ⟨H, ⟨hH, hn⟩⟩
    rw [← top_le_iff, ← hn]
    exact ascending_central_series_le_upper H hH n
#align least_ascending_central_series_length_eq_nilpotency_class least_ascending_central_series_length_eq_nilpotencyClass

/-- The nilpotency class of a nilpotent `G` is equal to the smallest `n` for which the descending
central series reaches `⊥` in its `n`'th term. -/
theorem least_descending_central_series_length_eq_nilpotencyClass :
    Nat.find ((nilpotent_iff_finite_descending_central_series G).mp hG) = Group.nilpotencyClass G :=
  by
  rw [← least_ascending_central_series_length_eq_nilpotencyClass]
  refine' le_antisymm (Nat.find_mono _) (Nat.find_mono _)
  · rintro n ⟨H, ⟨hH, hn⟩⟩
    refine' ⟨fun m => H (n - m), is_decending_rev_series_of_is_ascending G hn hH, _⟩
    rw [tsub_self]
    exact hH.1
  · rintro n ⟨H, ⟨hH, hn⟩⟩
    refine' ⟨fun m => H (n - m), is_ascending_rev_series_of_is_descending G hn hH, _⟩
    rw [tsub_self]
    exact hH.1
#align least_descending_central_series_length_eq_nilpotency_class least_descending_central_series_length_eq_nilpotencyClass

/-- The nilpotency class of a nilpotent `G` is equal to the length of the lower central series. -/
theorem lowerCentralSeries_length_eq_nilpotencyClass :
    Nat.find (nilpotent_iff_lowerCentralSeries.mp hG) = @Group.nilpotencyClass G _ _ :=
  by
  rw [← least_descending_central_series_length_eq_nilpotencyClass]
  refine' le_antisymm (Nat.find_mono _) (Nat.find_mono _)
  · rintro n ⟨H, ⟨hH, hn⟩⟩
    rw [← le_bot_iff, ← hn]
    exact descending_central_series_ge_lower H hH n
  · rintro n h
    exact ⟨lowerCentralSeries G, ⟨lowerCentralSeries_isDescendingCentralSeries, h⟩⟩
#align lower_central_series_length_eq_nilpotency_class lowerCentralSeries_length_eq_nilpotencyClass

@[simp]
theorem lowerCentralSeries_nilpotencyClass : lowerCentralSeries G (Group.nilpotencyClass G) = ⊥ :=
  by
  rw [← lowerCentralSeries_length_eq_nilpotencyClass]
  exact Nat.find_spec (nilpotent_iff_lower_central_series.mp _)
#align lower_central_series_nilpotency_class lowerCentralSeries_nilpotencyClass

theorem lowerCentralSeries_eq_bot_iff_nilpotencyClass_le {n : ℕ} :
    lowerCentralSeries G n = ⊥ ↔ Group.nilpotencyClass G ≤ n :=
  by
  constructor
  · intro h
    rw [← lowerCentralSeries_length_eq_nilpotencyClass]
    exact Nat.find_le h
  · intro h
    apply eq_bot_iff.mpr
    rw [← lowerCentralSeries_nilpotencyClass]
    exact lowerCentralSeries_antitone h
#align lower_central_series_eq_bot_iff_nilpotency_class_le lowerCentralSeries_eq_bot_iff_nilpotencyClass_le

end Classical

theorem lowerCentralSeries_map_subtype_le (H : Subgroup G) (n : ℕ) :
    (lowerCentralSeries H n).map H.Subtype ≤ lowerCentralSeries G n :=
  by
  induction' n with d hd
  · simp
  · rw [lowerCentralSeries_succ, lowerCentralSeries_succ, MonoidHom.map_closure]
    apply Subgroup.closure_mono
    rintro x1 ⟨x2, ⟨x3, hx3, x4, hx4, rfl⟩, rfl⟩
    exact ⟨x3, hd (mem_map.mpr ⟨x3, hx3, rfl⟩), x4, by simp⟩
#align lower_central_series_map_subtype_le lowerCentralSeries_map_subtype_le

/-- A subgroup of a nilpotent group is nilpotent -/
instance Subgroup.isNilpotent (H : Subgroup G) [hG : IsNilpotent G] : IsNilpotent H :=
  by
  rw [nilpotent_iff_lowerCentralSeries] at *
  rcases hG with ⟨n, hG⟩
  use n
  have := lowerCentralSeries_map_subtype_le H n
  simp only [hG, SetLike.le_def, mem_map, forall_apply_eq_imp_iff₂, exists_imp] at this
  exact eq_bot_iff.mpr fun x hx => Subtype.ext (this x hx)
#align subgroup.is_nilpotent Subgroup.isNilpotent

/-- A the nilpotency class of a subgroup is less or equal the the nilpotency class of the group -/
theorem Subgroup.nilpotencyClass_le (H : Subgroup G) [hG : IsNilpotent G] :
    Group.nilpotencyClass H ≤ Group.nilpotencyClass G :=
  by
  repeat' rw [← lowerCentralSeries_length_eq_nilpotencyClass]
  apply Nat.find_mono
  intro n hG
  have := lowerCentralSeries_map_subtype_le H n
  simp only [hG, SetLike.le_def, mem_map, forall_apply_eq_imp_iff₂, exists_imp] at this
  exact eq_bot_iff.mpr fun x hx => Subtype.ext (this x hx)
#align subgroup.nilpotency_class_le Subgroup.nilpotencyClass_le

instance (priority := 100) isNilpotent_of_subsingleton [Subsingleton G] : IsNilpotent G :=
  nilpotent_iff_lowerCentralSeries.2 ⟨0, Subsingleton.elim ⊤ ⊥⟩
#align is_nilpotent_of_subsingleton isNilpotent_of_subsingleton

theorem upperCentralSeries.map {H : Type _} [Group H] {f : G →* H} (h : Function.Surjective f)
    (n : ℕ) : Subgroup.map f (upperCentralSeries G n) ≤ upperCentralSeries H n :=
  by
  induction' n with d hd
  · simp
  · rintro _ ⟨x, hx : x ∈ upperCentralSeries G d.succ, rfl⟩ y'
    rcases h y' with ⟨y, rfl⟩
    simpa using hd (mem_map_of_mem f (hx y))
#align upper_central_series.map upperCentralSeries.map

theorem lowerCentralSeries.map {H : Type _} [Group H] (f : G →* H) (n : ℕ) :
    Subgroup.map f (lowerCentralSeries G n) ≤ lowerCentralSeries H n :=
  by
  induction' n with d hd
  · simp [Nat.zero_eq]
  · rintro a ⟨x, hx : x ∈ lowerCentralSeries G d.succ, rfl⟩
    refine'
      closure_induction hx _ (by simp [f.map_one, Subgroup.one_mem _])
        (fun y z hy hz => by simp [MonoidHom.map_mul, Subgroup.mul_mem _ hy hz]) fun y hy => by
        simp [f.map_inv, Subgroup.inv_mem _ hy]
    rintro a ⟨y, hy, z, ⟨-, rfl⟩⟩
    apply mem_closure.mpr
    exact fun K hK => hK ⟨f y, hd (mem_map_of_mem f hy), by simp [commutatorElement_def]⟩
#align lower_central_series.map lowerCentralSeries.map

theorem lowerCentralSeries_succ_eq_bot {n : ℕ} (h : lowerCentralSeries G n ≤ center G) :
    lowerCentralSeries G (n + 1) = ⊥ :=
  by
  rw [lowerCentralSeries_succ, closure_eq_bot_iff, Set.subset_singleton_iff]
  rintro x ⟨y, hy1, z, ⟨⟩, rfl⟩
  rw [mul_assoc, ← mul_inv_rev, mul_inv_eq_one, eq_comm]
  exact mem_center_iff.mp (h hy1) z
#align lower_central_series_succ_eq_bot lowerCentralSeries_succ_eq_bot

/-- The preimage of a nilpotent group is nilpotent if the kernel of the homomorphism is contained
in the center -/
theorem isNilpotent_of_ker_le_center {H : Type _} [Group H] (f : G →* H) (hf1 : f.ker ≤ center G)
    (hH : IsNilpotent H) : IsNilpotent G :=
  by
  rw [nilpotent_iff_lowerCentralSeries] at *
  rcases hH with ⟨n, hn⟩
  use n + 1
  refine' lowerCentralSeries_succ_eq_bot (le_trans ((Subgroup.map_eq_bot_iff _).mp _) hf1)
  exact eq_bot_iff.mpr (hn ▸ lowerCentralSeries.map f n)
#align is_nilpotent_of_ker_le_center isNilpotent_of_ker_le_center

theorem nilpotencyClass_le_of_ker_le_center {H : Type _} [Group H] (f : G →* H)
    (hf1 : f.ker ≤ center G) (hH : IsNilpotent H) :
    @Group.nilpotencyClass G _ (isNilpotent_of_ker_le_center f hf1 hH) ≤
      Group.nilpotencyClass H + 1 :=
  by
  rw [← lowerCentralSeries_length_eq_nilpotencyClass]
  apply Nat.find_min'
  refine' lowerCentralSeries_succ_eq_bot (le_trans ((Subgroup.map_eq_bot_iff _).mp _) hf1)
  apply eq_bot_iff.mpr
  apply le_trans (lowerCentralSeries.map f _)
  simp only [lowerCentralSeries_nilpotencyClass, le_bot_iff]
#align nilpotency_class_le_of_ker_le_center nilpotencyClass_le_of_ker_le_center

/-- The range of a surjective homomorphism from a nilpotent group is nilpotent -/
theorem nilpotent_of_surjective {G' : Type _} [Group G'] [h : IsNilpotent G] (f : G →* G')
    (hf : Function.Surjective f) : IsNilpotent G' :=
  by
  rcases h with ⟨n, hn⟩
  use n
  apply eq_top_iff.mpr
  calc
    ⊤ = f.range := symm (f.range_top_of_surjective hf)
    _ = Subgroup.map f ⊤ := (MonoidHom.range_eq_map _)
    _ = Subgroup.map f (upperCentralSeries G n) := by rw [hn]
    _ ≤ upperCentralSeries G' n := upperCentralSeries.map hf n

#align nilpotent_of_surjective nilpotent_of_surjective

/-- The nilpotency class of the range of a surejctive homomorphism from a
nilpotent group is less or equal the nilpotency class of the domain -/
theorem nilpotencyClass_le_of_surjective {G' : Type _} [Group G'] (f : G →* G')
    (hf : Function.Surjective f) [h : IsNilpotent G] :
    @Group.nilpotencyClass G' _ (nilpotent_of_surjective _ hf) ≤ Group.nilpotencyClass G :=
  by
  apply Nat.find_mono
  intro n hn
  apply eq_top_iff.mpr
  calc
    ⊤ = f.range := symm (f.range_top_of_surjective hf)
    _ = Subgroup.map f ⊤ := (MonoidHom.range_eq_map _)
    _ = Subgroup.map f (upperCentralSeries G n) := by rw [hn]
    _ ≤ upperCentralSeries G' n := upperCentralSeries.map hf n

#align nilpotency_class_le_of_surjective nilpotencyClass_le_of_surjective

/-- Nilpotency respects isomorphisms -/
theorem nilpotent_of_mulEquiv {G' : Type _} [Group G'] [h : IsNilpotent G] (f : G ≃* G') :
    IsNilpotent G' :=
  nilpotent_of_surjective f.toMonoidHom (MulEquiv.surjective f)
#align nilpotent_of_mul_equiv nilpotent_of_mulEquiv

/-- A quotient of a nilpotent group is nilpotent -/
instance nilpotent_quotient_of_nilpotent (H : Subgroup G) [H.Normal] [h : IsNilpotent G] :
    IsNilpotent (G ⧸ H) :=
  nilpotent_of_surjective _ (show Function.Surjective (QuotientGroup.mk' H) by tidy)
#align nilpotent_quotient_of_nilpotent nilpotent_quotient_of_nilpotent

/-- The nilpotency class of a quotient of `G` is less or equal the nilpotency class of `G` -/
theorem nilpotencyClass_quotient_le (H : Subgroup G) [H.Normal] [h : IsNilpotent G] :
    Group.nilpotencyClass (G ⧸ H) ≤ Group.nilpotencyClass G :=
  nilpotencyClass_le_of_surjective _ _
#align nilpotency_class_quotient_le nilpotencyClass_quotient_le

-- This technical lemma helps with rewriting the subgroup, which occurs in indices
private theorem comap_center_subst {H₁ H₂ : Subgroup G} [Normal H₁] [Normal H₂] (h : H₁ = H₂) :
    comap (mk' H₁) (center (G ⧸ H₁)) = comap (mk' H₂) (center (G ⧸ H₂)) := by subst h
#align comap_center_subst comap_center_subst

theorem comap_upperCentralSeries_quotient_center (n : ℕ) :
    comap (mk' (center G)) (upperCentralSeries (G ⧸ center G) n) = upperCentralSeries G n.succ :=
  by
  induction' n with n ih
  · simp
  · let Hn := upperCentralSeries (G ⧸ center G) n
    calc
      comap (mk' (center G)) (upperCentralSeriesStep Hn) =
          comap (mk' (center G)) (comap (mk' Hn) (center ((G ⧸ center G) ⧸ Hn))) :=
        by rw [upperCentralSeriesStep_eq_comap_center]
      _ = comap (mk' (comap (mk' (center G)) Hn)) (center (G ⧸ comap (mk' (center G)) Hn)) :=
        QuotientGroup.comap_comap_center
      _ = comap (mk' (upperCentralSeries G n.succ)) (center (G ⧸ upperCentralSeries G n.succ)) :=
        (comap_center_subst ih)
      _ = upperCentralSeriesStep (upperCentralSeries G n.succ) :=
        symm (upperCentralSeriesStep_eq_comap_center _)

#align comap_upper_central_series_quotient_center comap_upperCentralSeries_quotient_center

theorem nilpotencyClass_zero_iff_subsingleton [IsNilpotent G] :
    Group.nilpotencyClass G = 0 ↔ Subsingleton G := by
  simp [Group.nilpotencyClass, Nat.find_eq_zero, subsingleton_iff_bot_eq_top]
#align nilpotency_class_zero_iff_subsingleton nilpotencyClass_zero_iff_subsingleton

/-- Quotienting the `center G` reduces the nilpotency class by 1 -/
theorem nilpotencyClass_quotient_center [hH : IsNilpotent G] :
    Group.nilpotencyClass (G ⧸ center G) = Group.nilpotencyClass G - 1 :=
  by
  generalize hn : Group.nilpotencyClass G = n
  rcases n with (rfl | n)
  · simp [nilpotencyClass_zero_iff_subsingleton] at *
    haveI := hn
    infer_instance
  · suffices Group.nilpotencyClass (G ⧸ center G) = n by simpa
    apply le_antisymm
    · apply upper_central_series_eq_top_iff_nilpotency_class_le.mp
      apply @comap_injective G _ _ _ (mk' (center G)) (surjective_quot_mk _)
      rw [comap_upperCentralSeries_quotient_center, comap_top, ← hn]
      exact upperCentralSeries_nilpotencyClass
    · apply le_of_add_le_add_right
      calc
        n + 1 = n.succ := rfl
        _ = Group.nilpotencyClass G := (symm hn)
        _ ≤ Group.nilpotencyClass (G ⧸ center G) + 1 :=
          nilpotencyClass_le_of_ker_le_center _ (le_of_eq (ker_mk _)) _

#align nilpotency_class_quotient_center nilpotencyClass_quotient_center

/-- The nilpotency class of a non-trivial group is one more than its quotient by the center -/
theorem nilpotencyClass_eq_quotient_center_plus_one [hH : IsNilpotent G] [Nontrivial G] :
    Group.nilpotencyClass G = Group.nilpotencyClass (G ⧸ center G) + 1 :=
  by
  rw [nilpotencyClass_quotient_center]
  rcases h : Group.nilpotencyClass G with ⟨⟩
  · exfalso
    rw [nilpotencyClass_zero_iff_subsingleton] at h
    skip
    apply false_of_nontrivial_of_subsingleton G
  · simp
#align nilpotency_class_eq_quotient_center_plus_one nilpotencyClass_eq_quotient_center_plus_one

/-- If the quotient by `center G` is nilpotent, then so is G. -/
theorem of_quotient_center_nilpotent (h : IsNilpotent (G ⧸ center G)) : IsNilpotent G :=
  by
  obtain ⟨n, hn⟩ := h.nilpotent
  use n.succ
  simp [← comap_upperCentralSeries_quotient_center, hn]
#align of_quotient_center_nilpotent of_quotient_center_nilpotent

/-- A custom induction principle for nilpotent groups. The base case is a trivial group
(`subsingleton G`), and in the induction step, one can assume the hypothesis for
the group quotiented by its center. -/
@[elab_as_elim]
theorem nilpotent_center_quotient_ind {P : ∀ (G) [Group G], ∀ [IsNilpotent G], Prop} (G : Type _)
    [Group G] [IsNilpotent G] (hbase : ∀ (G) [Group G] [Subsingleton G], P G)
    (hstep : ∀ (G) [Group G], ∀ [IsNilpotent G], ∀ ih : P (G ⧸ center G), P G) : P G :=
  by
  obtain ⟨n, h⟩ : ∃ n, Group.nilpotencyClass G = n := ⟨_, rfl⟩
  induction' n with n ih generalizing G
  · haveI := nilpotency_class_zero_iff_subsingleton.mp h
    exact hbase _
  · have hn : Group.nilpotencyClass (G ⧸ center G) = n := by
      simp [nilpotencyClass_quotient_center, h]
    exact hstep _ (ih _ hn)
#align nilpotent_center_quotient_ind nilpotent_center_quotient_ind

theorem derived_le_lower_central (n : ℕ) : derivedSeries G n ≤ lowerCentralSeries G n :=
  by
  induction' n with i ih
  · simp
  · apply commutator_mono ih
    simp
#align derived_le_lower_central derived_le_lower_central

/-- Abelian groups are nilpotent -/
instance (priority := 100) CommGroup.isNilpotent {G : Type _} [CommGroup G] : IsNilpotent G :=
  by
  use 1
  rw [upperCentralSeries_one]
  apply CommGroup.center_eq_top
#align comm_group.is_nilpotent CommGroup.isNilpotent

/-- Abelian groups have nilpotency class at most one -/
theorem CommGroup.nilpotencyClass_le_one {G : Type _} [CommGroup G] : Group.nilpotencyClass G ≤ 1 :=
  by
  apply upper_central_series_eq_top_iff_nilpotency_class_le.mp
  rw [upperCentralSeries_one]
  apply CommGroup.center_eq_top
#align comm_group.nilpotency_class_le_one CommGroup.nilpotencyClass_le_one

/-- Groups with nilpotency class at most one are abelian -/
def commGroupOfNilpotencyClass [IsNilpotent G] (h : Group.nilpotencyClass G ≤ 1) : CommGroup G :=
  Group.commGroupOfCenterEqTop <| by
    rw [← upperCentralSeries_one]
    exact upper_central_series_eq_top_iff_nilpotency_class_le.mpr h
#align comm_group_of_nilpotency_class commGroupOfNilpotencyClass

section Prod

variable {G₁ G₂ : Type _} [Group G₁] [Group G₂]

theorem lowerCentralSeries_prod (n : ℕ) :
    lowerCentralSeries (G₁ × G₂) n = (lowerCentralSeries G₁ n).Prod (lowerCentralSeries G₂ n) :=
  by
  induction' n with n ih
  · simp
  ·
    calc
      lowerCentralSeries (G₁ × G₂) n.succ = ⁅lowerCentralSeries (G₁ × G₂) n, ⊤⁆ := rfl
      _ = ⁅(lowerCentralSeries G₁ n).Prod (lowerCentralSeries G₂ n), ⊤⁆ := by rw [ih]
      _ = ⁅(lowerCentralSeries G₁ n).Prod (lowerCentralSeries G₂ n), (⊤ : Subgroup G₁).Prod ⊤⁆ := by
        simp
      _ = ⁅lowerCentralSeries G₁ n, (⊤ : Subgroup G₁)⁆.Prod ⁅lowerCentralSeries G₂ n, ⊤⁆ :=
        (commutator_prod_prod _ _ _ _)
      _ = (lowerCentralSeries G₁ n.succ).Prod (lowerCentralSeries G₂ n.succ) := rfl

#align lower_central_series_prod lowerCentralSeries_prod

/-- Products of nilpotent groups are nilpotent -/
instance isNilpotent_prod [IsNilpotent G₁] [IsNilpotent G₂] : IsNilpotent (G₁ × G₂) :=
  by
  rw [nilpotent_iff_lowerCentralSeries]
  refine' ⟨max (Group.nilpotencyClass G₁) (Group.nilpotencyClass G₂), _⟩
  rw [lowerCentralSeries_prod,
    lower_central_series_eq_bot_iff_nilpotency_class_le.mpr (le_max_left _ _),
    lower_central_series_eq_bot_iff_nilpotency_class_le.mpr (le_max_right _ _), bot_prod_bot]
#align is_nilpotent_prod isNilpotent_prod

/-- The nilpotency class of a product is the max of the nilpotency classes of the factors -/
theorem nilpotencyClass_prod [IsNilpotent G₁] [IsNilpotent G₂] :
    Group.nilpotencyClass (G₁ × G₂) = max (Group.nilpotencyClass G₁) (Group.nilpotencyClass G₂) :=
  by
  refine' eq_of_forall_ge_iff fun k => _
  simp only [max_le_iff, ← lowerCentralSeries_eq_bot_iff_nilpotencyClass_le,
    lowerCentralSeries_prod, prod_eq_bot_iff]
#align nilpotency_class_prod nilpotencyClass_prod

end Prod

section BoundedPi

-- First the case of infinite products with bounded nilpotency class
variable {η : Type _} {Gs : η → Type _} [∀ i, Group (Gs i)]

theorem lowerCentralSeries_pi_le (n : ℕ) :
    lowerCentralSeries (∀ i, Gs i) n ≤ Subgroup.pi Set.univ fun i => lowerCentralSeries (Gs i) n :=
  by
  let pi := fun f : ∀ i, Subgroup (Gs i) => Subgroup.pi Set.univ f
  induction' n with n ih
  · simp [pi_top]
  ·
    calc
      lowerCentralSeries (∀ i, Gs i) n.succ = ⁅lowerCentralSeries (∀ i, Gs i) n, ⊤⁆ := rfl
      _ ≤ ⁅pi fun i => lowerCentralSeries (Gs i) n, ⊤⁆ := (commutator_mono ih (le_refl _))
      _ = ⁅pi fun i => lowerCentralSeries (Gs i) n, pi fun i => ⊤⁆ := by simp [pi, pi_top]
      _ ≤ pi fun i => ⁅lowerCentralSeries (Gs i) n, ⊤⁆ := (commutator_pi_pi_le _ _)
      _ = pi fun i => lowerCentralSeries (Gs i) n.succ := rfl

#align lower_central_series_pi_le lowerCentralSeries_pi_le

/-- products of nilpotent groups are nilpotent if their nipotency class is bounded -/
theorem isNilpotent_pi_of_bounded_class [∀ i, IsNilpotent (Gs i)] (n : ℕ)
    (h : ∀ i, Group.nilpotencyClass (Gs i) ≤ n) : IsNilpotent (∀ i, Gs i) :=
  by
  rw [nilpotent_iff_lowerCentralSeries]
  refine' ⟨n, _⟩
  rw [eq_bot_iff]
  apply le_trans (lowerCentralSeries_pi_le _)
  rw [← eq_bot_iff, pi_eq_bot_iff]
  intro i
  apply lower_central_series_eq_bot_iff_nilpotency_class_le.mpr (h i)
#align is_nilpotent_pi_of_bounded_class isNilpotent_pi_of_bounded_class

end BoundedPi

section FinitePi

-- Now for finite products
variable {η : Type _} {Gs : η → Type _} [∀ i, Group (Gs i)]

theorem lowerCentralSeries_pi_of_finite [Finite η] (n : ℕ) :
    lowerCentralSeries (∀ i, Gs i) n = Subgroup.pi Set.univ fun i => lowerCentralSeries (Gs i) n :=
  by
  let pi := fun f : ∀ i, Subgroup (Gs i) => Subgroup.pi Set.univ f
  induction' n with n ih
  · simp [pi_top]
  ·
    calc
      lowerCentralSeries (∀ i, Gs i) n.succ = ⁅lowerCentralSeries (∀ i, Gs i) n, ⊤⁆ := rfl
      _ = ⁅pi fun i => lowerCentralSeries (Gs i) n, ⊤⁆ := by rw [ih]
      _ = ⁅pi fun i => lowerCentralSeries (Gs i) n, pi fun i => ⊤⁆ := by simp [pi, pi_top]
      _ = pi fun i => ⁅lowerCentralSeries (Gs i) n, ⊤⁆ := (commutator_pi_pi_of_finite _ _)
      _ = pi fun i => lowerCentralSeries (Gs i) n.succ := rfl

#align lower_central_series_pi_of_finite lowerCentralSeries_pi_of_finite

/-- n-ary products of nilpotent groups are nilpotent -/
instance isNilpotent_pi [Finite η] [∀ i, IsNilpotent (Gs i)] : IsNilpotent (∀ i, Gs i) :=
  by
  cases nonempty_fintype η
  rw [nilpotent_iff_lowerCentralSeries]
  refine' ⟨finset.univ.sup fun i => Group.nilpotencyClass (Gs i), _⟩
  rw [lowerCentralSeries_pi_of_finite, pi_eq_bot_iff]
  intro i
  apply lower_central_series_eq_bot_iff_nilpotency_class_le.mpr
  exact
    @Finset.le_sup _ _ _ _ Finset.univ (fun i => Group.nilpotencyClass (Gs i)) _ (Finset.mem_univ i)
#align is_nilpotent_pi isNilpotent_pi

/-- The nilpotency class of an n-ary product is the sup of the nilpotency classes of the factors -/
theorem nilpotencyClass_pi [Fintype η] [∀ i, IsNilpotent (Gs i)] :
    Group.nilpotencyClass (∀ i, Gs i) = Finset.univ.sup fun i => Group.nilpotencyClass (Gs i) :=
  by
  apply eq_of_forall_ge_iff
  intro k
  simp only [Finset.sup_le_iff, ← lowerCentralSeries_eq_bot_iff_nilpotencyClass_le,
    lowerCentralSeries_pi_of_finite, pi_eq_bot_iff, Finset.mem_univ, true_imp_iff]
#align nilpotency_class_pi nilpotencyClass_pi

end FinitePi

/-- A nilpotent subgroup is solvable -/
instance (priority := 100) IsNilpotent.to_isSolvable [h : IsNilpotent G] : IsSolvable G :=
  by
  obtain ⟨n, hn⟩ := nilpotent_iff_lowerCentralSeries.1 h
  use n
  rw [eq_bot_iff, ← hn]
  exact derived_le_lower_central n
#align is_nilpotent.to_is_solvable IsNilpotent.to_isSolvable

theorem normalizerCondition_of_isNilpotent [h : IsNilpotent G] : NormalizerCondition G :=
  by
  -- roughly based on https://groupprops.subwiki.org/wiki/Nilpotent_implies_normalizer_condition
  rw [normalizerCondition_iff_only_full_group_self_normalizing]
  apply nilpotent_center_quotient_ind G <;> clear! G
  · intro G _ _ H _
    apply Subsingleton.elim
  · intro G _ _ ih H hH
    have hch : center G ≤ H := subgroup.center_le_normalizer.trans (le_of_eq hH)
    have hkh : (mk' (center G)).ker ≤ H := by simpa using hch
    have hsur : Function.Surjective (mk' (center G)) := surjective_quot_mk _
    let H' := H.map (mk' (center G))
    have hH' : H'.normalizer = H' := by
      apply comap_injective hsur
      rw [comap_normalizer_eq_of_surjective _ hsur, comap_map_eq_self hkh]
      exact hH
    apply map_injective_of_ker_le (mk' (center G)) hkh le_top
    exact (ih H' hH').trans (symm (map_top_of_surjective _ hsur))
#align normalizer_condition_of_is_nilpotent normalizerCondition_of_isNilpotent

end WithGroup

section WithFiniteGroup

open Group Fintype

variable {G : Type _} [hG : Group G]

include hG

/-- A p-group is nilpotent -/
theorem IsPGroup.isNilpotent [Finite G] {p : ℕ} [hp : Fact (Nat.Prime p)] (h : IsPGroup p G) :
    IsNilpotent G := by
  cases nonempty_fintype G
  classical
    revert hG
    induction' val using Fintype.induction_subsingleton_or_nontrivial with G hG hS G hG hN ih
    · infer_instance
    · intro
      intro h
      have hcq : Fintype.card (G ⧸ center G) < Fintype.card G :=
        by
        rw [card_eq_card_quotient_mul_card_subgroup (center G)]
        apply lt_mul_of_one_lt_right
        exact fintype.card_pos_iff.mpr One.nonempty
        exact (Subgroup.one_lt_card_iff_ne_bot _).mpr (ne_of_gt h.bot_lt_center)
      have hnq : IsNilpotent (G ⧸ center G) := ih _ hcq (h.to_quotient (center G))
      exact of_quotient_center_nilpotent hnq
#align is_p_group.is_nilpotent IsPGroup.isNilpotent

variable [Fintype G]

/-- If a finite group is the direct product of its Sylow groups, it is nilpotent -/
theorem isNilpotent_of_product_of_sylow_group
    (e : (∀ p : (Fintype.card G).factorization.support, ∀ P : Sylow p G, (↑P : Subgroup G)) ≃* G) :
    IsNilpotent G := by
  classical
    let ps := (Fintype.card G).factorization.support
    have : ∀ (p : ps) (P : Sylow p G), IsNilpotent (↑P : Subgroup G) :=
      by
      intro p P
      haveI : Fact (Nat.Prime ↑p) := Fact.mk (Nat.prime_of_mem_factorization (Finset.coe_mem p))
      exact P.is_p_group'.is_nilpotent
    exact nilpotent_of_mulEquiv e
#align is_nilpotent_of_product_of_sylow_group isNilpotent_of_product_of_sylow_group

/-- A finite group is nilpotent iff the normalizer condition holds, and iff all maximal groups are
normal and iff all sylow groups are normal and iff the group is the direct product of its sylow
groups. -/
theorem isNilpotent_of_finite_tFAE :
    TFAE
      [IsNilpotent G, NormalizerCondition G, ∀ H : Subgroup G, IsCoatom H → H.Normal,
        ∀ (p : ℕ) (hp : Fact p.Prime) (P : Sylow p G), (↑P : Subgroup G).Normal,
        Nonempty
          ((∀ p : (card G).factorization.support, ∀ P : Sylow p G, (↑P : Subgroup G)) ≃* G)] :=
  by
  tfae_have 1 → 2; · exact @normalizerCondition_of_isNilpotent _ _
  tfae_have 2 → 3; · exact fun h H => normalizer_condition.normal_of_coatom H h
  tfae_have 3 → 4;
  · intro h p _ P
    exact Sylow.normal_of_all_max_subgroups_normal h _
  tfae_have 4 → 5; · exact fun h => Nonempty.intro (Sylow.directProductOfNormal h)
  tfae_have 5 → 1;
  · rintro ⟨e⟩
    exact isNilpotent_of_product_of_sylow_group e
  tfae_finish
#align is_nilpotent_of_finite_tfae isNilpotent_of_finite_tFAE

end WithFiniteGroup


-- Cota_del_valor_absoluto_del_producto.lean
-- Si x,y,ε ∈ ℝ tales que 0 < ε ≤ 1, |x| < ε y |y| < ε, entonces |x*y| < ε
-- José A. Alonso Jiménez <https://jaalonso.github.io>
-- Sevilla, 1-noviembre-2022
-- ---------------------------------------------------------------------

-- ---------------------------------------------------------------------
-- Demostrar que si x,y,ε ∈ ℝ tales que 0 < ε ≤ 1, |x| < ε y |y| < ε,
-- entonces |x*y| < ε.
-- ---------------------------------------------------------------------

import data.real.basic tactic

-- 1ª demostración
-- ===============

example :
  ∀ {x y ε : ℝ}, 0 < ε → ε ≤ 1 → |x| < ε → |y| < ε → |x * y| < ε :=
begin
  intros x y ε he1 he2 hx hy,
  by_cases h : (|x| = 0),
  { calc |x * y|
         = |x| * |y| : abs_mul x y
     ... = 0 * |y|   : by rw h
     ... = 0         : zero_mul (abs y)
     ... < ε         : he1 },
  { have h1 : 0 < |x|,
    { have h2 : 0 ≤ |x| := abs_nonneg x,
      show 0 < |x|,
        exact lt_of_le_of_ne h2 (ne.symm h) },
    calc |x * y|
         = |x| * |y| : abs_mul x y
     ... < |x| * ε   : (mul_lt_mul_left h1).mpr hy
     ... < ε * ε     : (mul_lt_mul_right he1).mpr hx
     ... ≤ 1 * ε     : (mul_le_mul_right he1).mpr he2
     ... = ε         : one_mul ε },
end

-- 2ª demostración
-- ===============

example :
  ∀ {x y ε : ℝ}, 0 < ε → ε ≤ 1 → |x| < ε → |y| < ε → |x * y| < ε :=
begin
  intros x y ε he1 he2 hx hy,
  by_cases h : (|x| = 0),
  { calc |x * y|
         = |x| * |y| : by apply abs_mul
     ... = 0 * |y|   : by rw h
     ... = 0         : by apply zero_mul
     ... < ε         : by apply he1 },
  { have h1 : 0 < |x|,
      { have h2 : 0 ≤ |x|,
          apply abs_nonneg,
        exact lt_of_le_of_ne h2 (ne.symm h) },
    calc |x * y|
         = |x| * |y| : by rw abs_mul
     ... < |x| * ε   : by apply (mul_lt_mul_left h1).mpr hy
     ... < ε * ε     : by apply (mul_lt_mul_right he1).mpr hx
     ... ≤ 1 * ε     : by apply (mul_le_mul_right he1).mpr he2
     ... = ε         : by rw [one_mul] },
end

-- 3ª demostración
-- ===============

example :
  ∀ {x y ε : ℝ}, 0 < ε → ε ≤ 1 → |x| < ε → |y| < ε → |x * y| < ε :=
begin
  intros x y ε he1 he2 hx hy,
  by_cases (|x| = 0),
  { by finish },
  { have : 0 < |x|, by finish,
    calc |x * y|
         = |x| * |y| : by rw abs_mul
     ... < |x| * ε   : by finish
     ... ≤ 1 * ε     : by nlinarith
     ... = ε         : by finish },
end


/-
  The goal of this assignment is to formalize a proof that there are infinitely many
  primes.
-/
import tools.super   -- provides some automation

/-
   The statement
-/

namespace nat

protected def dvd (m n : ℕ) : Prop := ∃ k, n = m * k

instance : has_dvd nat := ⟨nat.dvd⟩

-- remember, type "dvd" with \|
def prime (n : ℕ) : Prop := n ≥ 2 ∧ ∀ m, m ∣ n → m = 1 ∨ m = n

-- this is what you need to prove:
check ∀ n, ∃ p, prime p ∧ p > n

end nat

/-
   Some useful things

   (You can use these also as examples.)
-/

namespace nat

theorem dvd_refl (m : ℕ) : m ∣ m :=
⟨1, by rw mul_one⟩

theorem dvd_trans : ∀ {m n k : ℕ}, m ∣ n → n ∣ k → m ∣ k
| m ._ ._ ⟨u, rfl⟩ ⟨v, rfl⟩ := ⟨u * v, by simp⟩

theorem dvd_mul_left (m n : ℕ) : m ∣ m * n :=
⟨n, rfl⟩

theorem dvd_mul_right (m n : ℕ) : m ∣ n * m :=
⟨n, by simp⟩

theorem dvd_add : ∀ {m n k : ℕ}, m ∣ n → m ∣ k → m ∣ (n + k)
| m ._ ._ ⟨u, rfl⟩ ⟨v, rfl⟩ := ⟨u + v, by simp [mul_add]⟩

theorem eq_sub_of_add_eq {m n k : ℕ} (h : m + n = k) : m = k - n :=
calc
  m = m + n - n : by rw nat.add_sub_cancel
  ... = k - n   : by rw h

theorem mul_sub (n m k : ℕ) : n * (m - k) = n * m - n * k :=
or.elim (le_or_gt m k)
  (assume h : m ≤ k,
    have n * m ≤ n * k, from mul_le_mul_left _ h,
    by simp [sub_eq_zero_of_le h, sub_eq_zero_of_le this])
  (suppose m > k,
    have m ≥ k, from le_of_lt this,
    eq_sub_of_add_eq begin rw [-mul_add, nat.sub_add_cancel this] end)

theorem dvd_sub : ∀ {m n k : ℕ}, m ∣ n → m ∣ k → m ∣ (n - k)
| m ._ ._ ⟨u, rfl⟩ ⟨v, rfl⟩ := begin rw -nat.mul_sub, apply dvd_mul_left end

theorem eq_or_ge_succ_of_ge {n m : ℕ} (h : n ≥ m) : n = m ∨ n ≥ succ m :=
begin
  cases (lt_or_eq_of_le h) with h₁ h₂,
  { right, assumption },
  left, symmetry, assumption
end

theorem eq_zero_or_eq_one_or_ge_2 (n : ℕ) : n = 0 ∨ n = 1 ∨ n ≥ 2 :=
begin
  cases (nat.eq_or_lt_of_le (zero_le n)) with h₀ h₁,
  { left, symmetry, assumption},
  cases (nat.eq_or_lt_of_le h₁) with h₁ h₂,
  { right, left, symmetry, assumption},
  right, right, exact h₂
end

def ge_2_of_prime {p : ℕ} (h : prime p) : p ≥ 2 := h^.left

-- a nice bit of automation!
theorem has_divisor_of_not_prime {n : ℕ} (h₁ : n ≥ 2) (h₂ : ¬ prime n) :
  ∃ m, m ∣ n ∧ m ≠ 1 ∧ m ≠ n :=
by super

def complete_induction_on (n : ℕ) {p : ℕ → Prop} (h : ∀ n, (∀ m < n, p m) → p n) : p n :=
suffices ∀ n, ∀ m < n, p m, from this (succ n) _ (lt_succ_self n),
take n,
nat.induction_on n
  (take m, suppose m < 0, absurd this (not_lt_zero m))
  (take n,
    assume ih : ∀ m < n, p m,
    take m,
    suppose m < succ n,
    or.elim (lt_or_eq_of_le (le_of_lt_succ this))
      (ih m)
      (suppose m = n,
        begin rw this, apply h, exact ih end))

theorem le_of_dvd {m n : ℕ} (h : m ∣ n) (h' : n ≠ 0) : m ≤ n :=
let ⟨k, hk⟩ := h in
have k ≠ 0, begin intro h, rw [h, mul_zero] at hk, contradiction end,
begin
  rw [-(mul_one m), hk], apply mul_le_mul_left,
  cases eq_or_ge_succ_of_ge (zero_le k),
  { contradiction },
  assumption
end

theorem le_or_eq_succ_of_le_succ {m n : ℕ} (h : m ≤ succ n) : m ≤ n ∨ m = succ n :=
begin
  cases m,
  { left, apply zero_le },
  cases lt_or_eq_of_le (le_of_succ_le_succ h),
  { left, assumption },
  right, apply congr_arg, assumption
end

end nat

/-
  Here is an outline of my solution. You do not need to follow it.

  If you succeed and are looking for more to do, try (stating and) proving that there are
  infinitely many primes congruent to 3 modulo 4.
-/

open nat

-- This requires a fair bit of work. I used complete induction on n.
theorem has_prime_divisor (n : ℕ) : n ≥ 2 → ∃ p, prime p ∧ p ∣ n := sorry

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

theorem fact_ge_1 : ∀ n, fact n ≥ 1 := sorry

theorem fact_ge_self {n : ℕ} (nnz : n ≠ 0) : fact n ≥ n := sorry

theorem dvd_fact_of_le : ∀ n, ∀ m ≤ n, m > 0 → m ∣ fact n := sorry

-- lots of intermediate steps are deleted!
theorem infinitely_many_primes : ∀ n, ∃ p, prime p ∧ p > n :=
suffices h : ∀ n, n ≥ 2 → ∃ p, prime p ∧ p > n, from sorry,
take n,
assume nge2 : n ≥ 2,
have fact n + 1 ≥ 2, from succ_le_succ (fact_ge_1 n),
match has_prime_divisor _ this with
| ⟨p, primep, pdvd⟩ :=
  have p > n, from lt_of_not_ge
    (suppose p ≤ n,
      have p ≤ fact n, from sorry,
      have p > 0, from lt_of_lt_of_le dec_trivial (ge_2_of_prime primep),
      have p ∣ fact n, from sorry,
      have p ∣ fact n + 1 - fact n, from sorry,
      have p ∣ 1, from sorry,
      have p ≤ 1, from sorry,
      have 2 ≤ 1, from sorry,
      show false, from absurd this dec_trivial),
  ⟨p, primep, this⟩
end


/-
Copyright (c) 2023 María Inés de Frutos-Fernández. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author : María Inés de Frutos-Fernández
-/

import tactic
import data.real.basic

/-
# Ejercicios sobre números reales

Los siguientes ejercicios aparecen como pasos intermedios en algunos
de los problemas de `limites.lean`. Intenta resolverlos utilizando
las tácticas `library_search` y `linarith`.

NOTA: adaptado de los cursos de formalización de Kevin Buzzard.
-/

example (x : ℝ) : |(-x)| = |x| :=
begin
  exact abs_neg x,
end

example (x y : ℝ) : |x - y| = |y - x| :=
begin
  exact abs_sub_comm x y,
end

example (A B C : ℕ) : max A B ≤ C ↔ A ≤ C ∧ B ≤ C :=
begin
  exact max_le_iff,
end

example (x y : ℝ) : |x| < y ↔ -y < x ∧ x < y :=
begin
  exact abs_lt,
end

example (ε : ℝ) (hε : 0 < ε) : 0 < ε / 2 :=
begin
  linarith,
end

example (a b x y : ℝ) (h1 : a < x) (h2 : b < y) : a + b < x + y :=
begin
  linarith,
end

example (ε : ℝ) (hε : 0 < ε) : 0 < ε / 3 :=
begin
  linarith,
end

example (a b c d x y : ℝ) (h1 : a + c < x) (h2 : b + d < y) :
  a + b + c + d < x + y :=
begin
  linarith,
end


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

! This file was ported from Lean 3 source module analysis.complex.circle
! leanprover-community/mathlib commit ad3dfaca9ea2465198bcf58aa114401c324e29d1
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Analysis.SpecialFunctions.Exp
import Mathbin.Topology.ContinuousFunction.Basic
import Mathbin.Analysis.Normed.Field.UnitBall

/-!
# The circle

This file defines `circle` to be the metric sphere (`metric.sphere`) in `ℂ` centred at `0` of
radius `1`.  We equip it with the following structure:

* a submonoid of `ℂ`
* a group
* a topological group

We furthermore define `exp_map_circle` to be the natural map `λ t, exp (t * I)` from `ℝ` to
`circle`, and show that this map is a group homomorphism.

## Implementation notes

Because later (in `geometry.manifold.instances.sphere`) one wants to equip the circle with a smooth
manifold structure borrowed from `metric.sphere`, the underlying set is
`{z : ℂ | abs (z - 0) = 1}`.  This prevents certain algebraic facts from working definitionally --
for example, the circle is not defeq to `{z : ℂ | abs z = 1}`, which is the kernel of `complex.abs`
considered as a homomorphism from `ℂ` to `ℝ`, nor is it defeq to `{z : ℂ | norm_sq z = 1}`, which
is the kernel of the homomorphism `complex.norm_sq` from `ℂ` to `ℝ`.

-/

noncomputable section

open Complex Metric

open ComplexConjugate

/-- The unit circle in `ℂ`, here given the structure of a submonoid of `ℂ`. -/
def circle : Submonoid ℂ :=
  Submonoid.unitSphere ℂ
#align circle circle

@[simp]
theorem mem_circle_iff_abs {z : ℂ} : z ∈ circle ↔ abs z = 1 :=
  mem_sphere_zero_iff_norm
#align mem_circle_iff_abs mem_circle_iff_abs

theorem circle_def : ↑circle = { z : ℂ | abs z = 1 } :=
  Set.ext fun z => mem_circle_iff_abs
#align circle_def circle_def

@[simp]
theorem abs_coe_circle (z : circle) : abs z = 1 :=
  mem_circle_iff_abs.mp z.2
#align abs_coe_circle abs_coe_circle

theorem mem_circle_iff_normSq {z : ℂ} : z ∈ circle ↔ normSq z = 1 := by simp [Complex.abs]
#align mem_circle_iff_norm_sq mem_circle_iff_normSq

@[simp]
theorem normSq_eq_of_mem_circle (z : circle) : normSq z = 1 := by simp [norm_sq_eq_abs]
#align norm_sq_eq_of_mem_circle normSq_eq_of_mem_circle

theorem ne_zero_of_mem_circle (z : circle) : (z : ℂ) ≠ 0 :=
  ne_zero_of_mem_unit_sphere z
#align ne_zero_of_mem_circle ne_zero_of_mem_circle

instance : CommGroup circle :=
  Metric.sphere.commGroup

@[simp]
theorem coe_inv_circle (z : circle) : ↑z⁻¹ = (z : ℂ)⁻¹ :=
  rfl
#align coe_inv_circle coe_inv_circle

theorem coe_inv_circle_eq_conj (z : circle) : ↑z⁻¹ = conj (z : ℂ) := by
  rw [coe_inv_circle, inv_def, normSq_eq_of_mem_circle, inv_one, of_real_one, mul_one]
#align coe_inv_circle_eq_conj coe_inv_circle_eq_conj

@[simp]
theorem coe_div_circle (z w : circle) : ↑(z / w) = (z : ℂ) / w :=
  circle.Subtype.map_div z w
#align coe_div_circle coe_div_circle

/-- The elements of the circle embed into the units. -/
def circle.toUnits : circle →* Units ℂ :=
  unitSphereToUnits ℂ
#align circle.to_units circle.toUnits

-- written manually because `@[simps]` was slow and generated the wrong lemma
@[simp]
theorem circle.toUnits_apply (z : circle) :
    circle.toUnits z = Units.mk0 z (ne_zero_of_mem_circle z) :=
  rfl
#align circle.to_units_apply circle.toUnits_apply

instance : CompactSpace circle :=
  Metric.sphere.compactSpace _ _

instance : TopologicalGroup circle :=
  Metric.sphere.topologicalGroup

/-- If `z` is a nonzero complex number, then `conj z / z` belongs to the unit circle. -/
@[simps]
def circle.ofConjDivSelf (z : ℂ) (hz : z ≠ 0) : circle :=
  ⟨conj z / z,
    mem_circle_iff_abs.2 <| by rw [map_div₀, abs_conj, div_self (complex.abs.ne_zero hz)]⟩
#align circle.of_conj_div_self circle.ofConjDivSelf

/-- The map `λ t, exp (t * I)` from `ℝ` to the unit circle in `ℂ`. -/
def expMapCircle : C(ℝ, circle)
    where toFun t := ⟨exp (t * I), by simp [exp_mul_I, abs_cos_add_sin_mul_I]⟩
#align exp_map_circle expMapCircle

@[simp]
theorem expMapCircle_apply (t : ℝ) : ↑(expMapCircle t) = Complex.exp (t * Complex.I) :=
  rfl
#align exp_map_circle_apply expMapCircle_apply

@[simp]
theorem expMapCircle_zero : expMapCircle 0 = 1 :=
  Subtype.ext <| by
    rw [expMapCircle_apply, of_real_zero, MulZeroClass.zero_mul, exp_zero, Submonoid.coe_one]
#align exp_map_circle_zero expMapCircle_zero

@[simp]
theorem expMapCircle_add (x y : ℝ) : expMapCircle (x + y) = expMapCircle x * expMapCircle y :=
  Subtype.ext <| by
    simp only [expMapCircle_apply, Submonoid.coe_mul, of_real_add, add_mul, Complex.exp_add]
#align exp_map_circle_add expMapCircle_add

/-- The map `λ t, exp (t * I)` from `ℝ` to the unit circle in `ℂ`, considered as a homomorphism of
groups. -/
@[simps]
def expMapCircleHom : ℝ →+ Additive circle
    where
  toFun := Additive.ofMul ∘ expMapCircle
  map_zero' := expMapCircle_zero
  map_add' := expMapCircle_add
#align exp_map_circle_hom expMapCircleHom

@[simp]
theorem expMapCircle_sub (x y : ℝ) : expMapCircle (x - y) = expMapCircle x / expMapCircle y :=
  expMapCircleHom.map_sub x y
#align exp_map_circle_sub expMapCircle_sub

@[simp]
theorem expMapCircle_neg (x : ℝ) : expMapCircle (-x) = (expMapCircle x)⁻¹ :=
  expMapCircleHom.map_neg x
#align exp_map_circle_neg expMapCircle_neg


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

! This file was ported from Lean 3 source module algebra.char_p.char_and_card
! leanprover-community/mathlib commit 2fae5fd7f90711febdadf19c44dc60fae8834d1b
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Algebra.CharP.Basic
import Mathbin.GroupTheory.Perm.Cycle.Type

/-!
# Characteristic and cardinality

We prove some results relating characteristic and cardinality of finite rings

## Tags
characterstic, cardinality, ring
-/

/-- A prime `p` is a unit in a commutative ring `R` of nonzero characterstic iff it does not divide
the characteristic. -/
theorem isUnit_iff_not_dvd_char_of_ringChar_ne_zero (R : Type _) [CommRing R] (p : ℕ) [Fact p.Prime]
    (hR : ringChar R ≠ 0) : IsUnit (p : R) ↔ ¬p ∣ ringChar R :=
  by
  have hch := CharP.cast_eq_zero R (ringChar R)
  have hp : p.prime := Fact.out p.prime
  constructor
  · rintro h₁ ⟨q, hq⟩
    rcases IsUnit.exists_left_inv h₁ with ⟨a, ha⟩
    have h₃ : ¬ringChar R ∣ q := by
      rintro ⟨r, hr⟩
      rw [hr, ← mul_assoc, mul_comm p, mul_assoc] at hq
      nth_rw 1 [← mul_one (ringChar R)] at hq
      exact Nat.Prime.not_dvd_one hp ⟨r, mul_left_cancel₀ hR hq⟩
    have h₄ := mt (CharP.int_cast_eq_zero_iff R (ringChar R) q).mp
    apply_fun (coe : ℕ → R)  at hq
    apply_fun (· * ·) a  at hq
    rw [Nat.cast_mul, hch, MulZeroClass.mul_zero, ← mul_assoc, ha, one_mul] at hq
    norm_cast  at h₄
    exact h₄ h₃ hq.symm
  · intro h
    rcases(hp.coprime_iff_not_dvd.mpr h).IsCoprime with ⟨a, b, hab⟩
    apply_fun (coe : ℤ → R)  at hab
    push_cast at hab
    rw [hch, MulZeroClass.mul_zero, add_zero, mul_comm] at hab
    exact isUnit_of_mul_eq_one (p : R) a hab
#align is_unit_iff_not_dvd_char_of_ring_char_ne_zero isUnit_iff_not_dvd_char_of_ringChar_ne_zero

/-- A prime `p` is a unit in a finite commutative ring `R`
iff it does not divide the characteristic. -/
theorem isUnit_iff_not_dvd_char (R : Type _) [CommRing R] (p : ℕ) [Fact p.Prime] [Finite R] :
    IsUnit (p : R) ↔ ¬p ∣ ringChar R :=
  isUnit_iff_not_dvd_char_of_ringChar_ne_zero R p <| CharP.char_ne_zero_of_finite R (ringChar R)
#align is_unit_iff_not_dvd_char isUnit_iff_not_dvd_char

/-- The prime divisors of the characteristic of a finite commutative ring are exactly
the prime divisors of its cardinality. -/
theorem prime_dvd_char_iff_dvd_card {R : Type _} [CommRing R] [Fintype R] (p : ℕ) [Fact p.Prime] :
    p ∣ ringChar R ↔ p ∣ Fintype.card R :=
  by
  refine'
    ⟨fun h =>
      h.trans <|
        int.coe_nat_dvd.mp <|
          (CharP.int_cast_eq_zero_iff R (ringChar R) (Fintype.card R)).mp <| by
            exact_mod_cast CharP.cast_card_eq_zero R,
      fun h => _⟩
  by_contra h₀
  rcases exists_prime_addOrderOf_dvd_card p h with ⟨r, hr⟩
  have hr₁ := addOrderOf_nsmul_eq_zero r
  rw [hr, nsmul_eq_mul] at hr₁
  rcases IsUnit.exists_left_inv ((isUnit_iff_not_dvd_char R p).mpr h₀) with ⟨u, hu⟩
  apply_fun (· * ·) u  at hr₁
  rw [MulZeroClass.mul_zero, ← mul_assoc, hu, one_mul] at hr₁
  exact
    mt add_monoid.order_of_eq_one_iff.mpr (ne_of_eq_of_ne hr (Nat.Prime.ne_one (Fact.out p.prime)))
      hr₁
#align prime_dvd_char_iff_dvd_card prime_dvd_char_iff_dvd_card

/-- A prime that does not divide the cardinality of a finite commutative ring `R`
is a unit in `R`. -/
theorem not_isUnit_prime_of_dvd_card {R : Type _} [CommRing R] [Fintype R] (p : ℕ) [Fact p.Prime]
    (hp : p ∣ Fintype.card R) : ¬IsUnit (p : R) :=
  mt (isUnit_iff_not_dvd_char R p).mp
    (Classical.not_not.mpr ((prime_dvd_char_iff_dvd_card p).mpr hp))
#align not_is_unit_prime_of_dvd_card not_isUnit_prime_of_dvd_card


import ..lectures.love12_basic_mathematical_structures_demo
import tactic

/-! # LoVe Homework 7: Basic Mathematical Structures (7 points)

Homework must be done individually.-/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

/-! ### Question 1 : Graph Definitions

There are many definitions of *graphs* in both the math and CS literature.
Graphs have *vertices* (or *nodes*) which are connected by *edges*.
Some definitions specify edges to be *directed*, i.e.
an edge that connects vertex `a` to vertex `b` does not connect `b` to `a`.
Others are undirected.
Some allow *loops*, i.e. edges from a vertex `a` to itself.
Some don't.

A (undirected, loopless) graph is usually defined as a list of vertices
along with a list of tuples of vertices, representing edges:-/

structure list_graph (V : Type) :=
(edges : list (V × V))

/-! For large graphs, this data structure can get cumbersome. Instead, we
can define graphs based on an adjacency function. To ensure undirectedness
and looplessness, we need to prove our adjacency function is symmetric and
irreflexive, respectively. -/

structure func_graph (V : Type) :=
(adj : V → V → Prop)
(symm : symmetric adj)
(loopless : irreflexive adj )

/- 1.1 (3 points).
A complete graph is a graph in which each vertex is adjacent to each other vertex.
Define the complete graph on four vertices, K₄, as a `list_graph` and as a `func_graph`.
You can use a predefined function for the adjacency function, or you can make your own! -/

inductive quad : Type
| one | two | three | four

namespace quad

def K₄' : list_graph quad := sorry

def K₄ : func_graph quad := sorry

end quad

/- 1.2 (4 points)
If we choose the right type for a graph, it can be easier to define, manipulate, and manage
the graph. A `path graph` is a graph with vertices v₁,...,vₙ whose only edges are connecting
vᵢ and vᵢ₊₁ for i < n. Define a path graph `path₅` on the type `fin 5` by designing a symmetric
and irreflexive function. You'll need to prove symmetry and irreflexivity, too!
-/
#check fin
/-
Be careful with addition on `fin`. You might recognize this type mathematically as `ℤ/5ℤ`.
-/
#eval (4 : fin 5) + 1
/-
You also might be surprised that tactics like `linarith` don't work on `fin 5`.
Turns out that algorithm doesn't like it when `5 = 0`!
If you end up with a hypothesis `h : 0 = 1` or something like that,
`cases h` may come in handy.
-/

def path₅ : func_graph (fin 5) := sorry

/- 1.3 (1 point)
Using this functional definition of a graph, we can easily generalize the above path graph
to make an arbitrarily large path. Fill in the following definition, utilizing your previous
answer. -/

def pathBIG : func_graph (fin 100) := sorry

/- Extra Challenge: define `pathBig` as a list_graph. (just kidding don't do that)-/

/- 1.4 (Bonus)
We lied a bit: the definitions `func_graph` and `list_graph` are actually capturing
slightly different graph concepts!

To make that more precise: we can state the *equivalence* of a `list_graph` and a `func_graph`.
-/
def graph_equiv {α : Type} (lg : list_graph α) (fg : func_graph α) : Prop :=
∀ v₁ v₂ : α, fg.adj v₁ v₂ ↔ ((v₁, v₂) ∈ lg.edges ∨ (v₂, v₁) ∈ lg.edges)

/-
I claim that I can find two non-equal `func_graphs` that are equivalent to the same `list_graph`.

Can you give an example? What graph theory concept is appearing here?
-/

/- For question 2, you may chose to do **one** of Question 2α or Question 2β. You may do both,
but you will only be graded for one. Please indicate which one you want to be graded.
Question 2α will be more in the realm of math and logic.
Question 2β will be more about CS, PL, and functional programming. -/

/- ### Question 2α : Automorphism Groups of Graphs (6 points) -/
/- An automorphism of a graph is a function on (or permutation of) its vertices that preserves
the edge structure of the graph. In other words: -/
def is_graph_automorphism {α : Type} (G : func_graph α) (A : α → α) : Prop :=
  ∀(v₁ v₂ : α), G.adj v₁ v₂ ↔ G.adj (A v₁) (A v₂)

/- We can define a structure like this to define an automorphism of a graph. -/
structure graph_automorphism (α : Type) (G : func_graph α) :=
(f : α → α)
(is_aut : is_graph_automorphism G f)

/- Let's focus on a particular graph, say, the graph `path₅` defined above, to define and study
its automorphisms. It has two automorphisms: the identity, which fixes all vertices, and a 'flip',
which fixes the middle vertex, maps terminal vertices to terminal vertices, and the two remaining
vertices to each other. -/

/- 2α.1 (1 point)
Define the two functions corresponding to the `flip` and the `identity` on the `path₅` graph.
-/

def path₅_flip : fin 5 → fin 5 := sorry

def path₅_id : fin 5 → fin 5 := sorry

/- 2α.2 (1 point)
Now prove that these functions are indeed automorphisms.
-/

lemma path₅_flip_is_aut : is_graph_automorphism path₅ path₅_flip := sorry

lemma path₅_id_is_aut : is_graph_automorphism path₅ path₅_id := sorry

/- Now we can define the two elements of our structure! -/
def path₅_flip_aut : graph_automorphism (fin 5) path₅ :=
{
  f := path₅_flip,
  is_aut := path₅_flip_is_aut
}

def path₅_id_aut : graph_automorphism (fin 5) path₅ :=
{
  f := path₅_id,
  is_aut := path₅_id_is_aut
}

/- Graph automorphisms under the operation of function composition form a group! Let's work
towards building that group for our path₅ graph.
-/

/- Question 2α.3 (2 points)
First, let's prove that automorphisms are closed under composition (`aut_comp_aut`).
Then we'll define the composition function as `aut_comp`.

Hint for the lemma: `iff.trans` might be handy!
-/

lemma aut_comp_aut {α : Type} (G : func_graph α) (f g : α → α) :
  is_graph_automorphism G f → is_graph_automorphism G g → is_graph_automorphism G (f ∘ g) :=
sorry

def aut_comp {α : Type} (G : func_graph α) :
 graph_automorphism α G → graph_automorphism α G → graph_automorphism α G := sorry

infix ` ∘₅ ` : 90 := aut_comp path₅

/- Question 2α.4 (2 points)
Now prove that this operation is associative and define an inverse function. (Note that the
inverse function is defined only on the path₅ graph, so don't overthink it! What are the inverses
of each element?) -/

lemma graph_automorphism.assoc :
  ∀ (a b c : graph_automorphism (fin 5) path₅), a ∘₅ b ∘₅ c = a ∘₅ (b ∘₅ c) := sorry

def graph_automorphism.inv : graph_automorphism (fin 5) path₅ → graph_automorphism (fin 5) path₅ := sorry

/- You don't have to prove the rest, but if you did, you'd have a group! Notice also how easy it would
be to generalize this group to an automorphism group of any path graph. -/

axiom graph_automorphism.one_mul : ∀ (a : graph_automorphism (fin 5) path₅), path₅_id_aut ∘₅ a = a
axiom graph_automorphism.mul_one : ∀ (a : graph_automorphism (fin 5) path₅), a ∘₅ path₅_id_aut = a
axiom graph_automorphism.mul_left_inv : ∀ (a : graph_automorphism (fin 5) path₅), graph_automorphism.inv a ∘₅ a = path₅_id_aut

@[instance] def automorphism_group_path₅ : group (graph_automorphism (fin 5) path₅) :=
{
  mul := aut_comp path₅,
  one := path₅_id_aut,
  mul_assoc := graph_automorphism.assoc,
  one_mul := graph_automorphism.one_mul,
  inv := graph_automorphism.inv,
  mul_one := graph_automorphism.mul_one,
  mul_left_inv := graph_automorphism.mul_left_inv,
}

/- ### Question 2β : Computer Networks (6 points)-/
/- Computer networks can be modeled by graphs. Suppose we have a system of routers, and
we want any router to be able to send and receive information from any other router. Graph
theoretically, we want our graph to be connected.
-/

/- Question 2β.1 (2 points)
We'll say a path is valid if it
* Starts with the starting vertex `start_v`
* Ends with the ending vertex `end_v`
* For each element in the list vᵢ, vᵢ₊₁ is adjacent to vᵢ

Fill in the predicate `is_path` that holds when an input list is a valid path.
-/

def is_path {α : Type} (G : func_graph α) (start_v end_v: α) : list α → Prop := sorry

def is_connected {α : Type} (G : func_graph α) : Prop :=
  ∀(v₁ v₂ : α), v₁ ≠ v₂ → ∃p, is_path G v₁ v₂ p

/- Suppose now that Ian, infamous for hating routers, destroys one of the routers. It's very
much possible that the destruction of that router disconnected the rest of the network, i.e., after that
router is destroyed, there are some routers that can no longer communicate with others. We would
call that router a **separation vertex**. We might consider employing the stricter condition of
**biconnectivity** to avoid this problem. Biconnectivity is when there are no separation vertices
in the graph, or, alternatively, for every two vertices there are two disjoint paths connecting them. -/

/- Question 2β.2 (1 point)
Write an inductive predicate that holds when two lists are totally disjoint (their intersection is empty). -/

inductive list_disj {α : Type} : list α → list α → Prop

/- Question 2β.3 (1 point)
Use the predicate above to write a definition for biconnectivity. -/

def is_biconnected {α : Type} (G : func_graph α) : Prop := sorry

/-  Question 2β.4 (2 points)

Define a biconnected graph of your choice (with at least 4 vertices) and prove that it is biconnected!
-/

def bcgraph : func_graph sorry := sorry

lemma bc_graph_is_biconnected : is_biconnected bcgraph :=
sorry

end LoVe


/-
Copyright (c) 2023 Kevin Buzzard. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author : Kevin Buzzard
-/

import tactic -- imports all the Lean tactics
import linear_algebra.finite_dimensional -- finite-dimensional vector spaces

/-!

# Vector spaces

Ok so groups in Lean are called `group` and fields are called `field`, so
I think I owe you an explanation for why vector spaces are called `module`.

The lie is that `module` is French for vector space, and we used a French source.
If you're happy with that explanation, then you can skip the actual explanation below.

The truth is the following. The definition of a vector space `V` over a field `k` is:

1) `V` is an abelian group
2) If `r : k` and `v : V` there's an element `r • v : V`
3) Axioms for `•`: `(r + s) • v = r • v + s • v`, `r • (v + w) = r • v = r • w`,
  `1 • v = v` and `(r * s) • v = r • (s • v)`.

Recall that `k` was a field. Fields have division and inverses (except for 0),
but look at those axioms: there is no mention of inverses or division for `k` in the axioms
of a vector space. The only things we use on `k` are `1`, `+` and `*`.

This means that we can make the *definition* of a vector space just under
the assumption that `k` is a `ring`, rather than a `field`, although of course
fewer things will be true (for example, over a general ring it is not true that
every vector space has a basis). However, when `k` is a ring, mathematicians don't
call these things vector spaces; they call them *modules*. So the way we say
"let `V` be a vector space over `k`" in Lean is "let `V` be a module over `k`".

Note to self: we should be able to add notation `vector_space` for `module` in a locale.
But let's just tell them it's French for vector space.
-/

-- Let `k` be a field and let `V` be a vector space over `k`
variables (k : Type) [field k] (V : Type) [add_comm_group V] [module k V]

-- The field `k` acts on the vector space `V` and the notation for this is `•`,
-- which is notation for `smul`. We don't use `mul` because for `a * b` to make
-- sense in Lean we need `a` and `b` to have the same type. Here `a : k` and `v : V`
-- so this isn't satisfied.

example (a : k) (v : V) : V := a • v

-- Axioms for a vector space

variables (a b : k) (v w : V)

example : a • (v + w) = a • v + a • w := smul_add a v w
example : (a + b) • v = a • v + b • v := add_smul a b v
example : (1 : k) • v = v := one_smul k v
example : a • (b • v) = (a * b) • v := smul_smul a b v

-- Other standard facts about vector spaces:
example : (a - b) • v = a • v - b • v := sub_smul a b v
example : (0 : k) • v = 0 := zero_smul k v

/-

## Subspaces

The type of subspaces of a vector space is called `subspace k V`. You
have to mention `k` because there are real world examples like `ℂⁿ` which
are vector spaces over both the reals and the complexes, so they have
more real subspaces than complex subspaces.

Subspaces of a vector space form a complete lattice, so Lean uses lattice notation for them.

-/

-- let `X` and `Y` be subspaces of `V`
variables (X Y : subspace k V)

-- Note that `X` and `Y` are terms not types.

-- How do we say `X ⊆ Y`?

-- #check X ⊆ Y -- doesn't work! `⊆` only works for terms of type `set V`.

-- We use *lattice notation* and it works fine.

example : Prop := X ≤ Y -- `X ≤ Y` is a true-false statement

example : subspace k V := X ⊓ Y -- intersection of `X` and `Y`, as a subspace

example : subspace k V := X ⊔ Y -- `X` + `Y`, as a subspace. It's the smallest vector subspace of
                                -- `V` containing `X` and `Y`, so it's the sup of `X` and `Y`
                                -- in the lattice of subspaces.

example : subspace k V := ⊥ -- the 0-dimensional subspace

example : subspace k V := ⊤  -- V considered as a subspace of itself; note that
                             -- we can't use V to represent this subspace because
                             -- V is a type, and a subspace of V is a term

-- For elements of subspaces it's just like sets:

example : Prop := v ∈ X -- the type of `v` is `V`, and the true-false statement is `v ∈ X`

-- Let `W` be another `k`-vector space
variables (W : Type) [add_comm_group W] [module k W]

-- `k`-linear maps from `V` to `W` are terms of type `V →ₗ[k] W`. This is notation
-- for `linear_map (ring_hom.id k) V W`.

-- let `φ : V → W` be a linear map
variable (φ : V →ₗ[k] W)

-- Axioms for a linear map:
example : φ (a • v) = a • φ v := φ.map_smul a v
example : φ (v + w) = φ v + φ w := φ.map_add v w

-- quotients work just like in group theory
example := V ⧸ X

-- The linear map from `V` to `V ⧸ X` is called `submodule.mkq X`

example : V →ₗ[k] V ⧸ X := submodule.mkq X

-- ...which is inconsistent with the group theory quotient conventions, something I only
-- just spotted when preparing this course sheet.

-- You can take the image and preimage of subspaces along a linear map.

example (X : subspace k V) : subspace k W := X.map φ
example (Y : subspace k W) : subspace k V := Y.comap φ

-- Here's an actual question at long last. If φ : V → W is a linear map,
-- if X is a subspace of V and Y a subspace of W, prove that φ(X) ⊆ Y iff X ⊆ φ⁻¹(Y)
example (X : subspace k V) (Y : subspace k W) : X.map φ ≤ Y ↔ X ≤ Y.comap φ :=
begin
  sorry,
end