/-
Copyright (c) 2020 Jeremy Avigad. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jeremy Avigad, Mario Carneiro, Yury G. Kudryashov
-/
import order.basic

/-!
# Unbundled relation classes

In this file we prove some properties of `is_*` classes defined in `init.algebra.classes`. The main
difference between these classes and the usual order classes (`preorder` etc) is that usual classes
extend `has_le` and/or `has_lt` while these classes take a relation as an explicit argument.

-/

universes u v

variables {α : Type u} {β : Type v} {r : α → α → Prop} {s : β → β → Prop}

open function

lemma comm [is_symm α r] {a b : α} : r a b ↔ r b a := ⟨symm, symm⟩
lemma antisymm' {r : α → α → Prop} [is_antisymm α r] {a b : α} : r a b → r b a → b = a :=
λ h h', antisymm h' h

/-- A version of `antisymm` with `r` explicit.

This lemma matches the lemmas from lean core in `init.algebra.classes`, but is missing there.  -/
@[elab_simple]
lemma antisymm_of (r : α → α → Prop) [is_antisymm α r] {a b : α} : r a b → r b a → a = b := antisymm

/-- A version of `antisymm'` with `r` explicit.

This lemma matches the lemmas from lean core in `init.algebra.classes`, but is missing there.  -/
@[elab_simple]
lemma antisymm_of' (r : α → α → Prop) [is_antisymm α r] {a b : α} : r a b → r b a → b = a :=
antisymm'

/-- A version of `comm` with `r` explicit.

This lemma matches the lemmas from lean core in `init.algebra.classes`, but is missing there.  -/
lemma comm_of (r : α → α → Prop) [is_symm α r] {a b : α} : r a b ↔ r b a := comm

theorem is_refl.swap (r) [is_refl α r] : is_refl α (swap r) := ⟨refl_of r⟩
theorem is_irrefl.swap (r) [is_irrefl α r] : is_irrefl α (swap r) := ⟨irrefl_of r⟩
theorem is_trans.swap (r) [is_trans α r] : is_trans α (swap r) :=
⟨λ a b c h₁ h₂, trans_of r h₂ h₁⟩
theorem is_antisymm.swap (r) [is_antisymm α r] : is_antisymm α (swap r) :=
⟨λ a b h₁ h₂, antisymm h₂ h₁⟩
theorem is_asymm.swap (r) [is_asymm α r] : is_asymm α (swap r) :=
⟨λ a b h₁ h₂, asymm_of r h₂ h₁⟩
theorem is_total.swap (r) [is_total α r] : is_total α (swap r) :=
⟨λ a b, (total_of r a b).swap⟩
theorem is_trichotomous.swap (r) [is_trichotomous α r] : is_trichotomous α (swap r) :=
⟨λ a b, by simpa [swap, or.comm, or.left_comm] using trichotomous_of r a b⟩
theorem is_preorder.swap (r) [is_preorder α r] : is_preorder α (swap r) :=
{..@is_refl.swap α r _, ..@is_trans.swap α r _}
theorem is_strict_order.swap (r) [is_strict_order α r] : is_strict_order α (swap r) :=
{..@is_irrefl.swap α r _, ..@is_trans.swap α r _}
theorem is_partial_order.swap (r) [is_partial_order α r] : is_partial_order α (swap r) :=
{..@is_preorder.swap α r _, ..@is_antisymm.swap α r _}
theorem is_total_preorder.swap (r) [is_total_preorder α r] : is_total_preorder α (swap r) :=
{..@is_preorder.swap α r _, ..@is_total.swap α r _}
theorem is_linear_order.swap (r) [is_linear_order α r] : is_linear_order α (swap r) :=
{..@is_partial_order.swap α r _, ..@is_total.swap α r _}

protected theorem is_asymm.is_antisymm (r) [is_asymm α r] : is_antisymm α r :=
⟨λ x y h₁ h₂, (asymm h₁ h₂).elim⟩
protected theorem is_asymm.is_irrefl [is_asymm α r] : is_irrefl α r :=
⟨λ a h, asymm h h⟩
protected theorem is_total.is_trichotomous (r) [is_total α r] : is_trichotomous α r :=
⟨λ a b, or.left_comm.1 (or.inr $ total_of r a b)⟩

@[priority 100]  -- see Note [lower instance priority]
instance is_total.to_is_refl (r) [is_total α r] : is_refl α r :=
⟨λ a, (or_self _).1 $ total_of r a a⟩

lemma ne_of_irrefl {r} [is_irrefl α r] : ∀ {x y : α}, r x y → x ≠ y | _ _ h rfl := irrefl _ h
lemma ne_of_irrefl' {r} [is_irrefl α r] : ∀ {x y : α}, r x y → y ≠ x | _ _ h rfl := irrefl _ h

lemma trans_trichotomous_left [is_trans α r] [is_trichotomous α r] {a b c : α} :
  ¬r b a → r b c → r a c :=
begin
  intros h₁ h₂, rcases trichotomous_of r a b with h₃|h₃|h₃,
  exact trans h₃ h₂, rw h₃, exact h₂, exfalso, exact h₁ h₃
end

lemma trans_trichotomous_right [is_trans α r] [is_trichotomous α r] {a b c : α} :
  r a b → ¬r c b → r a c :=
begin
  intros h₁ h₂, rcases trichotomous_of r b c with h₃|h₃|h₃,
  exact trans h₁ h₃, rw ←h₃, exact h₁, exfalso, exact h₂ h₃
end

/-- Construct a partial order from a `is_strict_order` relation.

See note [reducible non-instances]. -/
@[reducible] def partial_order_of_SO (r) [is_strict_order α r] : partial_order α :=
{ le := λ x y, x = y ∨ r x y,
  lt := r,
  le_refl := λ x, or.inl rfl,
  le_trans := λ x y z h₁ h₂,
    match y, z, h₁, h₂ with
    | _, _, or.inl rfl, h₂ := h₂
    | _, _, h₁, or.inl rfl := h₁
    | _, _, or.inr h₁, or.inr h₂ := or.inr (trans h₁ h₂)
    end,
  le_antisymm := λ x y h₁ h₂,
    match y, h₁, h₂ with
    | _, or.inl rfl, h₂ := rfl
    | _, h₁, or.inl rfl := rfl
    | _, or.inr h₁, or.inr h₂ := (asymm h₁ h₂).elim
    end,
  lt_iff_le_not_le := λ x y,
    ⟨λ h, ⟨or.inr h, not_or
      (λ e, by rw e at h; exact irrefl _ h)
      (asymm h)⟩,
    λ ⟨h₁, h₂⟩, h₁.resolve_left (λ e, h₂ $ e ▸ or.inl rfl)⟩ }

/-- This is basically the same as `is_strict_total_order`, but that definition has a redundant
assumption `is_incomp_trans α lt`. -/
@[algebra] class is_strict_total_order' (α : Type u) (lt : α → α → Prop)
  extends is_trichotomous α lt, is_strict_order α lt : Prop.

/-- Construct a linear order from an `is_strict_total_order'` relation.

See note [reducible non-instances]. -/
@[reducible]
def linear_order_of_STO' (r) [is_strict_total_order' α r] [Π x y, decidable (¬ r x y)] :
  linear_order α :=
{ le_total := λ x y,
    match y, trichotomous_of r x y with
    | y, or.inl h := or.inl (or.inr h)
    | _, or.inr (or.inl rfl) := or.inl (or.inl rfl)
    | _, or.inr (or.inr h) := or.inr (or.inr h)
    end,
  decidable_le := λ x y, decidable_of_iff (¬ r y x)
    ⟨λ h, ((trichotomous_of r y x).resolve_left h).imp eq.symm id,
      λ h, h.elim (λ h, h ▸ irrefl_of _ _) (asymm_of r)⟩,
  ..partial_order_of_SO r }

theorem is_strict_total_order'.swap (r) [is_strict_total_order' α r] :
  is_strict_total_order' α (swap r) :=
{..is_trichotomous.swap r, ..is_strict_order.swap r}

/-! ### Order connection -/

/-- A connected order is one satisfying the condition `a < c → a < b ∨ b < c`.
  This is recognizable as an intuitionistic substitute for `a ≤ b ∨ b ≤ a` on
  the constructive reals, and is also known as negative transitivity,
  since the contrapositive asserts transitivity of the relation `¬ a < b`.  -/
@[algebra] class is_order_connected (α : Type u) (lt : α → α → Prop) : Prop :=
(conn : ∀ a b c, lt a c → lt a b ∨ lt b c)

theorem is_order_connected.neg_trans {r : α → α → Prop} [is_order_connected α r]
  {a b c} (h₁ : ¬ r a b) (h₂ : ¬ r b c) : ¬ r a c :=
mt (is_order_connected.conn a b c) $ by simp [h₁, h₂]

theorem is_strict_weak_order_of_is_order_connected [is_asymm α r]
  [is_order_connected α r] : is_strict_weak_order α r :=
{ trans := λ a b c h₁ h₂, (is_order_connected.conn _ c _ h₁).resolve_right (asymm h₂),
  incomp_trans := λ a b c ⟨h₁, h₂⟩ ⟨h₃, h₄⟩,
    ⟨is_order_connected.neg_trans h₁ h₃, is_order_connected.neg_trans h₄ h₂⟩,
  ..@is_asymm.is_irrefl α r _ }

@[priority 100] -- see Note [lower instance priority]
instance is_order_connected_of_is_strict_total_order'
  [is_strict_total_order' α r] : is_order_connected α r :=
⟨λ a b c h, (trichotomous _ _).imp_right (λ o,
  o.elim (λ e, e ▸ h) (λ h', trans h' h))⟩

@[priority 100] -- see Note [lower instance priority]
instance is_strict_total_order_of_is_strict_total_order'
  [is_strict_total_order' α r] : is_strict_total_order α r :=
{..is_strict_weak_order_of_is_order_connected}

/-! ### Extensional relation -/

/-- An extensional relation is one in which an element is determined by its set
  of predecessors. It is named for the `x ∈ y` relation in set theory, whose
  extensionality is one of the first axioms of ZFC. -/
@[algebra] class is_extensional (α : Type u) (r : α → α → Prop) : Prop :=
(ext : ∀ a b, (∀ x, r x a ↔ r x b) → a = b)

@[priority 100] -- see Note [lower instance priority]
instance is_extensional_of_is_strict_total_order'
  [is_strict_total_order' α r] : is_extensional α r :=
⟨λ a b H, ((@trichotomous _ r _ a b)
  .resolve_left $ mt (H _).2 (irrefl a))
  .resolve_right $ mt (H _).1 (irrefl b)⟩

/-! ### Well-order -/

/-- A well order is a well-founded linear order. -/
@[algebra] class is_well_order (α : Type u) (r : α → α → Prop)
  extends is_strict_total_order' α r : Prop :=
(wf : well_founded r)

@[priority 100] -- see Note [lower instance priority]
instance is_well_order.is_strict_total_order {α} (r : α → α → Prop) [is_well_order α r] :
  is_strict_total_order α r := by apply_instance
@[priority 100] -- see Note [lower instance priority]
instance is_well_order.is_extensional {α} (r : α → α → Prop) [is_well_order α r] :
  is_extensional α r := by apply_instance
@[priority 100] -- see Note [lower instance priority]
instance is_well_order.is_trichotomous {α} (r : α → α → Prop) [is_well_order α r] :
  is_trichotomous α r := by apply_instance
@[priority 100] -- see Note [lower instance priority]
instance is_well_order.is_trans {α} (r : α → α → Prop) [is_well_order α r] :
  is_trans α r := by apply_instance
@[priority 100] -- see Note [lower instance priority]
instance is_well_order.is_irrefl {α} (r : α → α → Prop) [is_well_order α r] :
  is_irrefl α r := by apply_instance
@[priority 100] -- see Note [lower instance priority]
instance is_well_order.is_asymm {α} (r : α → α → Prop) [is_well_order α r] :
  is_asymm α r := by apply_instance

/-- Construct a decidable linear order from a well-founded linear order. -/
noncomputable def is_well_order.linear_order (r : α → α → Prop) [is_well_order α r] :
  linear_order α :=
by { letI := λ x y, classical.dec (¬r x y), exact linear_order_of_STO' r }

instance empty_relation.is_well_order [subsingleton α] : is_well_order α empty_relation :=
{ trichotomous := λ a b, or.inr $ or.inl $ subsingleton.elim _ _,
  irrefl       := λ a, id,
  trans        := λ a b c, false.elim,
  wf           := ⟨λ a, ⟨_, λ y, false.elim⟩⟩ }

instance prod.lex.is_well_order [is_well_order α r] [is_well_order β s] :
  is_well_order (α × β) (prod.lex r s) :=
{ trichotomous := λ ⟨a₁, a₂⟩ ⟨b₁, b₂⟩,
    match @trichotomous _ r _ a₁ b₁ with
    | or.inl h₁ := or.inl $ prod.lex.left _ _ h₁
    | or.inr (or.inr h₁) := or.inr $ or.inr $ prod.lex.left _ _ h₁
    | or.inr (or.inl e) := e ▸  match @trichotomous _ s _ a₂ b₂ with
      | or.inl h := or.inl $ prod.lex.right _ h
      | or.inr (or.inr h) := or.inr $ or.inr $ prod.lex.right _ h
      | or.inr (or.inl e) := e ▸ or.inr $ or.inl rfl
      end
    end,
  irrefl := λ ⟨a₁, a₂⟩ h, by cases h with _ _ _ _ h _ _ _ h;
     [exact irrefl _ h, exact irrefl _ h],
  trans := λ a b c h₁ h₂, begin
    cases h₁ with a₁ a₂ b₁ b₂ ab a₁ b₁ b₂ ab;
    cases h₂ with _ _ c₁ c₂ bc _ _ c₂ bc,
    { exact prod.lex.left _ _ (trans ab bc) },
    { exact prod.lex.left _ _ ab },
    { exact prod.lex.left _ _ bc },
    { exact prod.lex.right _ (trans ab bc) }
  end,
  wf := prod.lex_wf is_well_order.wf is_well_order.wf }

namespace set

/-- An unbounded or cofinal set. -/
def unbounded (r : α → α → Prop) (s : set α) : Prop := ∀ a, ∃ b ∈ s, ¬ r b a
/-- A bounded or final set. Not to be confused with `metric.bounded`. -/
def bounded (r : α → α → Prop) (s : set α) : Prop := ∃ a, ∀ b ∈ s, r b a

@[simp] lemma not_bounded_iff {r : α → α → Prop} (s : set α) : ¬bounded r s ↔ unbounded r s :=
by simp only [bounded, unbounded, not_forall, not_exists, exists_prop, not_and, not_not]

@[simp] lemma not_unbounded_iff {r : α → α → Prop} (s : set α) : ¬unbounded r s ↔ bounded r s :=
by rw [not_iff_comm, not_bounded_iff]

end set

namespace prod

instance is_refl_preimage_fst {r : α → α → Prop} [h : is_refl α r] :
  is_refl (α × α) (prod.fst ⁻¹'o r) := ⟨λ a, refl_of r a.1⟩

instance is_refl_preimage_snd {r : α → α → Prop} [h : is_refl α r] :
  is_refl (α × α) (prod.snd ⁻¹'o r) := ⟨λ a, refl_of r a.2⟩

instance is_trans_preimage_fst {r : α → α → Prop} [h : is_trans α r] :
  is_trans (α × α) (prod.fst ⁻¹'o r) := ⟨λ _ _ _, trans_of r⟩

instance is_trans_preimage_snd {r : α → α → Prop} [h : is_trans α r] :
  is_trans (α × α) (prod.snd ⁻¹'o r) := ⟨λ _ _ _, trans_of r⟩

end prod

/-! ### Strict-non strict relations -/

/-- An unbundled relation class stating that `r` is the nonstrict relation corresponding to the
strict relation `s`. Compare `preorder.lt_iff_le_not_le`. This is mostly meant to provide dot
notation on `(⊆)` and `(⊂)`. -/
class is_nonstrict_strict_order (α : Type*) (r s : α → α → Prop) :=
(right_iff_left_not_left (a b : α) : s a b ↔ r a b ∧ ¬ r b a)

lemma right_iff_left_not_left {r s : α → α → Prop} [is_nonstrict_strict_order α r s] {a b : α} :
  s a b ↔ r a b ∧ ¬ r b a :=
is_nonstrict_strict_order.right_iff_left_not_left _ _

/-- A version of `right_iff_left_not_left` with explicit `r` and `s`. -/
lemma right_iff_left_not_left_of (r s : α → α → Prop) [is_nonstrict_strict_order α r s] {a b : α} :
  s a b ↔ r a b ∧ ¬ r b a :=
right_iff_left_not_left

-- The free parameter `r` is strictly speaking not uniquely determined by `s`, but in practice it
-- always has a unique instance, so this is not dangerous.
@[priority 100, nolint dangerous_instance] -- see Note [lower instance priority]
instance is_nonstrict_strict_order.to_is_irrefl {r : α → α → Prop} {s : α → α → Prop}
  [is_nonstrict_strict_order α r s] :
  is_irrefl α s :=
⟨λ a h, ((right_iff_left_not_left_of r s).1 h).2 ((right_iff_left_not_left_of r s).1 h).1⟩

/-! #### `⊆` and `⊂` -/

section subset
variables [has_subset α] {a b c : α}

@[refl] lemma subset_refl [is_refl α (⊆)] (a : α) : a ⊆ a := refl _
lemma subset_rfl [is_refl α (⊆)] : a ⊆ a := refl _
lemma subset_of_eq [is_refl α (⊆)] : a = b → a ⊆ b := λ h, h ▸ subset_rfl
lemma superset_of_eq [is_refl α (⊆)] : a = b → b ⊆ a := λ h, h ▸ subset_rfl
lemma ne_of_not_subset [is_refl α (⊆)] : ¬ a ⊆ b → a ≠ b := mt subset_of_eq
lemma ne_of_not_superset [is_refl α (⊆)] : ¬ a ⊆ b → b ≠ a := mt superset_of_eq
@[trans] lemma subset_trans [is_trans α (⊆)] (h : a ⊆ b) (h' : b ⊆ c) : a ⊆ c := trans h h'

lemma subset_antisymm [is_antisymm α (⊆)] (h : a ⊆ b) (h' : b ⊆ a) : a = b :=
antisymm h h'

lemma superset_antisymm [is_antisymm α (⊆)] (h : a ⊆ b) (h' : b ⊆ a) : b = a :=
antisymm' h h'

alias subset_of_eq ← eq.subset' --TODO: Fix it and kill `eq.subset`
alias superset_of_eq ← eq.superset
alias subset_trans      ← has_subset.subset.trans
alias subset_antisymm   ← has_subset.subset.antisymm
alias superset_antisymm ← has_subset.subset.antisymm'

lemma subset_antisymm_iff [is_refl α (⊆)] [is_antisymm α (⊆)] : a = b ↔ a ⊆ b ∧ b ⊆ a :=
⟨λ h, ⟨h.subset', h.superset⟩, λ h, h.1.antisymm h.2⟩

lemma superset_antisymm_iff [is_refl α (⊆)] [is_antisymm α (⊆)] : a = b ↔ b ⊆ a ∧ a ⊆ b :=
⟨λ h, ⟨h.superset, h.subset'⟩, λ h, h.1.antisymm' h.2⟩

end subset

section ssubset
variables [has_ssubset α]

lemma ssubset_irrefl [is_irrefl α (⊂)] (a : α) : ¬ a ⊂ a := irrefl _
lemma ssubset_irrfl [is_irrefl α (⊂)] {a : α} : ¬ a ⊂ a := irrefl _
lemma ne_of_ssubset [is_irrefl α (⊂)] {a b : α} : a ⊂ b → a ≠ b := ne_of_irrefl
lemma ne_of_ssuperset [is_irrefl α (⊂)] {a b : α} : a ⊂ b → b ≠ a := ne_of_irrefl'
@[trans] lemma ssubset_trans [is_trans α (⊂)] {a b c : α} (h : a ⊂ b) (h' : b ⊂ c) : a ⊂ c :=
trans h h'
lemma ssubset_asymm [is_asymm α (⊂)] {a b : α} (h : a ⊂ b) : ¬ b ⊂ a := asymm h

alias ssubset_irrfl   ← has_ssubset.ssubset.false
alias ne_of_ssubset   ← has_ssubset.ssubset.ne
alias ne_of_ssuperset ← has_ssubset.ssubset.ne'
alias ssubset_trans   ← has_ssubset.ssubset.trans
alias ssubset_asymm   ← has_ssubset.ssubset.asymm

end ssubset

section subset_ssubset
variables [has_subset α] [has_ssubset α] [is_nonstrict_strict_order α (⊆) (⊂)] {a b c : α}

lemma ssubset_iff_subset_not_subset : a ⊂ b ↔ a ⊆ b ∧ ¬ b ⊆ a := right_iff_left_not_left
lemma subset_of_ssubset (h : a ⊂ b) : a ⊆ b := (ssubset_iff_subset_not_subset.1 h).1
lemma not_subset_of_ssubset (h : a ⊂ b) : ¬ b ⊆ a := (ssubset_iff_subset_not_subset.1 h).2
lemma not_ssubset_of_subset (h : a ⊆ b) : ¬ b ⊂ a := λ h', not_subset_of_ssubset h' h

lemma ssubset_of_subset_not_subset (h₁ : a ⊆ b) (h₂ : ¬ b ⊆ a) : a ⊂ b :=
ssubset_iff_subset_not_subset.2 ⟨h₁, h₂⟩

alias subset_of_ssubset            ← has_ssubset.ssubset.subset
alias not_subset_of_ssubset        ← has_ssubset.ssubset.not_subset
alias not_ssubset_of_subset        ← has_subset.subset.not_ssubset
alias ssubset_of_subset_not_subset ← has_subset.subset.ssubset_of_not_subset

lemma ssubset_of_subset_of_ssubset [is_trans α (⊆)] (h₁ : a ⊆ b) (h₂ : b ⊂ c) : a ⊂ c :=
(h₁.trans h₂.subset).ssubset_of_not_subset $ λ h, h₂.not_subset $ h.trans h₁

lemma ssubset_of_ssubset_of_subset [is_trans α (⊆)] (h₁ : a ⊂ b) (h₂ : b ⊆ c) : a ⊂ c :=
(h₁.subset.trans h₂).ssubset_of_not_subset $ λ h, h₁.not_subset $ h₂.trans h

lemma ssubset_of_subset_of_ne [is_antisymm α (⊆)] (h₁ : a ⊆ b) (h₂ : a ≠ b) : a ⊂ b :=
h₁.ssubset_of_not_subset $ mt h₁.antisymm h₂

lemma ssubset_of_ne_of_subset [is_antisymm α (⊆)] (h₁ : a ≠ b) (h₂ : a ⊆ b) : a ⊂ b :=
ssubset_of_subset_of_ne h₂ h₁

lemma eq_or_ssubset_of_subset [is_antisymm α (⊆)] (h : a ⊆ b) : a = b ∨ a ⊂ b :=
(em (b ⊆ a)).imp h.antisymm h.ssubset_of_not_subset

lemma ssubset_or_eq_of_subset [is_antisymm α (⊆)] (h : a ⊆ b) : a ⊂ b ∨ a = b :=
(eq_or_ssubset_of_subset h).swap

alias ssubset_of_subset_of_ssubset ← has_subset.subset.trans_ssubset
alias ssubset_of_ssubset_of_subset ← has_ssubset.ssubset.trans_subset
alias ssubset_of_subset_of_ne      ← has_subset.subset.ssubset_of_ne
alias ssubset_of_ne_of_subset      ← ne.ssubset_of_subset
alias eq_or_ssubset_of_subset      ← has_subset.subset.eq_or_ssubset
alias ssubset_or_eq_of_subset      ← has_subset.subset.ssubset_or_eq

lemma ssubset_iff_subset_ne [is_antisymm α (⊆)] : a ⊂ b ↔ a ⊆ b ∧ a ≠ b :=
⟨λ h, ⟨h.subset, h.ne⟩, λ h, h.1.ssubset_of_ne h.2⟩

lemma subset_iff_ssubset_or_eq [is_refl α (⊆)] [is_antisymm α (⊆)] : a ⊆ b ↔ a ⊂ b ∨ a = b :=
⟨λ h, h.ssubset_or_eq, λ h, h.elim subset_of_ssubset subset_of_eq⟩

end subset_ssubset

/-! ### Conversion of bundled order typeclasses to unbundled relation typeclasses -/

instance [preorder α] : is_refl α (≤) := ⟨le_refl⟩
instance [preorder α] : is_refl α (≥) := is_refl.swap _
instance [preorder α] : is_trans α (≤) := ⟨@le_trans _ _⟩
instance [preorder α] : is_trans α (≥) := is_trans.swap _
instance [preorder α] : is_preorder α (≤) := {}
instance [preorder α] : is_preorder α (≥) := {}
instance [preorder α] : is_irrefl α (<) := ⟨lt_irrefl⟩
instance [preorder α] : is_irrefl α (>) := is_irrefl.swap _
instance [preorder α] : is_trans α (<) := ⟨@lt_trans _ _⟩
instance [preorder α] : is_trans α (>) := is_trans.swap _
instance [preorder α] : is_asymm α (<) := ⟨@lt_asymm _ _⟩
instance [preorder α] : is_asymm α (>) := is_asymm.swap _
instance [preorder α] : is_antisymm α (<) := is_asymm.is_antisymm _
instance [preorder α] : is_antisymm α (>) := is_asymm.is_antisymm _
instance [preorder α] : is_strict_order α (<) := {}
instance [preorder α] : is_strict_order α (>) := {}
instance [preorder α] : is_nonstrict_strict_order α (≤) (<) := ⟨@lt_iff_le_not_le _ _⟩
instance [partial_order α] : is_antisymm α (≤) := ⟨@le_antisymm _ _⟩
instance [partial_order α] : is_antisymm α (≥) := is_antisymm.swap _
instance [partial_order α] : is_partial_order α (≤) := {}
instance [partial_order α] : is_partial_order α (≥) := {}
instance [linear_order α] : is_total α (≤) := ⟨le_total⟩
instance [linear_order α] : is_total α (≥) := is_total.swap _
instance linear_order.is_total_preorder [linear_order α] : is_total_preorder α (≤) :=
  by apply_instance
instance [linear_order α] : is_total_preorder α (≥) := {}
instance [linear_order α] : is_linear_order α (≤) := {}
instance [linear_order α] : is_linear_order α (≥) := {}
instance [linear_order α] : is_trichotomous α (<) := ⟨lt_trichotomy⟩
instance [linear_order α] : is_trichotomous α (>) := is_trichotomous.swap _
instance [linear_order α] : is_trichotomous α (≤) := is_total.is_trichotomous _
instance [linear_order α] : is_trichotomous α (≥) := is_total.is_trichotomous _
instance [linear_order α] : is_strict_total_order α (<) := by apply_instance
instance [linear_order α] : is_strict_total_order' α (<) := {}
instance [linear_order α] : is_order_connected α (<) := by apply_instance
instance [linear_order α] : is_incomp_trans α (<) := by apply_instance
instance [linear_order α] : is_strict_weak_order α (<) := by apply_instance

instance order_dual.is_total_le [has_le α] [is_total α (≤)] : is_total (order_dual α) (≤) :=
@is_total.swap α _ _

instance nat.lt.is_well_order : is_well_order ℕ (<) := ⟨nat.lt_wf⟩


/-
Copyright (c) 2022 James Gallicchio.

Authors: James Gallicchio
-/

import LeanColls.List
import LeanColls.AuxLemmas

namespace LeanColls

/-! # Range

Represents the first `n` natural numbers, e.g. [0, n).
-/
structure Range where
  n : Nat

namespace Range

instance : Membership Nat Range where
  mem x r := x < r.n

/-! ## Range folds

We define folds over ranges in both directions. Noting that
`toList n = [0, 1, ..., n]`, the directions correspond to
`List.foldl` and `List.foldr`.
-/

def foldr (f : Nat → β → β) (acc : β) : Range → β
| ⟨0⟩ => acc
| ⟨n+1⟩ => foldr f (f n acc) ⟨n⟩

def foldl (f : β → Nat → β) (acc : β) : Range → β
| ⟨0⟩ => acc
| ⟨n+1⟩ => f (foldl f acc ⟨n⟩) n

def foldr' (r : Range) (f : (i : Nat) → i ∈ r → β → β) (acc : β) : β :=
  match r with
  | ⟨0⟩ => acc
  | ⟨n+1⟩ =>
    have hn : n < n+1 := Nat.lt_succ_self _
    have hi : ∀ {i}, i < n → i < n+1 := Nat.le_step
    foldr' ⟨n⟩ (λ i h acc => f i (hi h) acc) (f n hn acc)

def foldl' (r : Range) (f : β → (i : Nat) → i ∈ r → β) (acc : β) : β :=
  match r with
  | ⟨0⟩ => acc
  | ⟨n+1⟩ =>
    have hn : n < n+1 := Nat.lt_succ_self _
    have hi : ∀ {i}, i < n → i < n+1 := Nat.le_step
    f (foldl' ⟨n⟩ (λ acc i h => f acc i (hi h)) acc) n hn

def foldl'' (r : Range) (motive : (i : Nat) → Sort u)
  (f : (i : Nat) → i ∈ r → motive i → motive i.succ)
  (init : motive 0)
  : motive r.n
  :=
  let rec aux (n : Nat) (motive : (i : Nat) → Sort u)
    (f : (i : Nat) → i ∈ Range.mk n → motive i → motive i.succ)
    (i : Nat)
    (h_i : i ≤ n)
    (acc : motive i)
    : motive n
    :=
    if h : i = n then
      h ▸ acc
    else
      have : i < n := Nat.lt_of_le_of_ne h_i h
      aux n motive f i.succ this (f i this acc)
  aux r.n motive f 0 (Nat.zero_le _) init
  termination_by aux n _ _ i _ _ => n - i

/- ## Simp lemmas -/

@[simp]
theorem foldl'_zero (f : β → (i : _) → _) (acc)
  : foldl' ⟨0⟩ f acc = acc
  := by unfold foldl'; simp

/- ## Utility functions -/

def toList (r : Range) : List Nat :=
  foldr (· :: ·) [] r

/- ## Fold correctness proofs -/

theorem foldr_cons_eq_append
  : foldr (· :: ·) acc r = foldr (· :: ·) [] r ++ acc
  := by
  cases r; case mk n =>
  induction n generalizing acc with
  | zero => simp [foldr]
  | succ n ih =>
    simp [foldr]
    rw [@ih [n], @ih (n :: acc), List.append_assoc]
    simp

@[simp]
theorem toList_succ (n)
  : toList ⟨n+1⟩ = toList ⟨n⟩ ++ [n]
  := by
  simp [toList, foldr]
  apply foldr_cons_eq_append

theorem foldr_correct (r : Range)
  : r.foldr f acc = (toList r).foldr f acc
  := by
  cases r; case mk n =>
  induction n generalizing acc with
  | zero => simp [foldr]
  | succ n ih => simp [foldr, List.foldr_append, ih]

theorem toList_eq_canonicalToList (r : Range)
  : toList r = canonicalToList r.foldl
  := by
  cases r; case mk n =>
  simp [toList, canonicalToList]
  induction n with
  | zero =>
    simp [foldr, foldl]
  | succ n ih =>
    simp [foldr, foldl, ←ih]
    apply foldr_cons_eq_append

theorem foldl_correct (r : Range)
  : r.foldl f acc = (toList r).foldl f acc
  := by
  rw [toList_eq_canonicalToList]
  simp [canonicalToList]
  cases r; case mk n =>
  induction n with
  | zero => simp [foldl, List.foldl]
  | succ n ih =>
    simp [foldl, List.foldl_append, List.foldl, ih]

theorem foldl_step (n)
  : foldl f init ⟨n+1⟩ = foldl (fun acc x => f acc x.succ) (f init 0) ⟨n⟩
  := by
  induction n generalizing f init with
  | zero      => simp [foldl]
  | succ n ih =>
    simp [foldl]
    congr
    apply ih

theorem foldl'_step (n) {f : (x : _) → _}
  : foldl' ⟨n+1⟩ f init = foldl' ⟨n⟩ (fun acc x h => f acc x.succ (Nat.succ_le_succ h)) (f init 0 (Nat.zero_lt_succ _))
  := by
  induction n generalizing init with
  | zero      =>
    unfold foldl'
    simp
    unfold foldl'
    simp
  | succ n ih =>
    unfold foldl'
    simp [ih]

theorem foldr'_eq_foldl'_mapped (r) {f : (x : _) → _}
  : foldr' r f init = foldl' r (fun acc x h =>
    f (r.n - x - 1) (by
      simp [Membership.mem, Nat.sub_sub]
      apply Nat.sub_lt (Nat.lt_of_le_of_lt (Nat.zero_le _) h)
      apply Nat.succ_le_succ (Nat.zero_le _)
      )
      acc) init
  := by
  cases r; case mk n =>
  simp
  induction n generalizing init with
  | zero =>
    unfold foldr'
    unfold foldl'
    simp
  | succ n ih =>
    unfold foldr'
    simp
    rw [foldl'_step]
    rw [ih]
    simp
    congr
    funext acc x
    simp [Nat.succ_sub_succ]

theorem foldr_eq_foldl_mapped (r)
  : foldr f init r = foldl (fun acc x => f (r.n - x - 1) acc) init r
  := by
  cases r; case mk n =>
  simp
  induction n generalizing init with
  | zero =>
    simp [foldr, foldl]
  | succ n ih =>
    rw [foldl_step]
    simp [foldr, foldl, ih]
    congr
    funext acc x
    simp [Nat.succ_sub_succ]

theorem reverse_toList_eq_map_toList (r : Range)
  : r.toList.reverse = r.toList.map (fun i => r.n - i - 1)
  := by
  conv =>
    rhs
    simp [toList]
    rw [foldr_eq_foldl_mapped, foldl_correct]
  match r with
  | ⟨n⟩ =>
  simp
  suffices ∀ k, k ≤ n → List.reverse (toList ⟨k⟩) =
    List.map (fun i => n - i - 1)
      (List.foldl (fun acc x => (n - x - 1) :: acc) []
        (toList ⟨k⟩))
    from this n (Nat.le_refl _)
  intro k h_k
  induction k with
  | zero => simp
  | succ n ih =>
  simp [List.foldl_append]
  constructor
  case left =>
    rw [←Nat.sub_dist (y := n), Nat.sub_sub_self h_k, Nat.succ_sub_one]
  case right =>
    apply ih
    exact Nat.le_of_lt h_k

theorem memCorrect (x : Nat) (c : Range)
  : x ∈ c ↔ x ∈ canonicalToList (fun {β} => c.foldl)
  := by
  cases c; case mk n =>
  simp [Foldable.fold]
  induction n with
  | zero =>
    simp [canonicalToList, foldl]
    apply Nat.not_lt_zero
  | succ n ih =>
    conv => lhs; simp [Membership.mem]
    conv => rhs; simp [canonicalToList, foldl]
    constructor <;> intro h
    case mp =>
      cases Nat.eq_or_lt_of_le <| Nat.le_of_succ_le_succ h
      case inl h =>
        simp [h]
      case inr h =>
        apply Or.inl <| ih.mp h
    case mpr =>
      cases h
      case inl h =>
        apply Nat.le_step <| ih.mpr h
      case inr h =>
        simp [h]

theorem foldl'_correct {β : Type u} (r : Range) {f : β → (i : Nat) → i ∈ r → β} {acc : β}
    {L : List Nat} (hL : L = canonicalToList r.foldl)
  : r.foldl' f acc = L.foldl' (fun acc x h => f acc x ((memCorrect _ _).mpr (hL.subst h))) acc
  := by
  rw [←toList_eq_canonicalToList] at hL
  cases r; case mk n hL =>
  induction n generalizing L with
  | zero =>
    simp [toList, foldr] at hL
    cases hL
    unfold foldl'
    simp [List.foldl']
  | succ n ih =>
    simp at hL
    simp [foldl']
    rw [ih (L := toList ⟨n⟩) _ rfl]
    conv => rhs; rw [List.foldl'_eq_subtypeByMem_foldl]
    cases hL
    rw [List.subtypeByMem_append]
    rw [List.foldl'_eq_subtypeByMem_foldl]
    simp [foldl, List.foldl_map, List.foldl, List.foldl_append]
    simp [toList_eq_canonicalToList]

theorem foldr'_correct {β : Type u} (r : Range) {f} {acc : β}
  {L} (hL : L = canonicalToList r.foldl)
  : r.foldr' f acc =
    L.foldr'
      (fun x h acc => f x
        ((memCorrect _ _ ).mpr (hL.subst h))
        acc)
      acc
  := set_option pp.all false in by
  cases r; case mk n =>
  cases hL
  rw [List.foldr'_rw _ _ _ _ (toList_eq_canonicalToList _).symm]
  induction n generalizing β acc with
  | zero => unfold foldr'; simp [List.foldr']
  | succ n ih =>
    rw [List.foldr'_rw _ _ _ _ (toList_succ _)]
    rw [List.foldr'_eq_subtypeByMem_foldr]
    rw [List.subtypeByMem_append]
    simp [List.map_append, List.foldr_append]
    rw [List.map_of_subtypeByMem_eq_map']
    rw [List.foldr_of_map']
    simp
    unfold foldr'
    simp [List.foldr']
    rw [ih]

/- ## csimp lemmas -/

theorem foldl''.aux_step {n} {f : (_ : _) → _ → _ → _} {i : Nat} (h : i ≤ n) {acc : motive i}
  : foldl''.aux (n+1) motive f i (Nat.le_step h) acc =
    f n (Nat.lt_succ_self _) (
      foldl''.aux (n) motive (fun i h acc => f i (Nat.le_step h) acc) i h acc)
  := by
  suffices ∀ j (_ : j ≤ n) i (h_i : i = n - j) acc,
    foldl''.aux (n+1) motive f i (h_i ▸ Nat.le_step (Nat.sub_le _ _)) acc =
    f n (Nat.lt_succ_self _) (
      foldl''.aux (n) motive (fun i h acc => f i (Nat.le_step h) acc) i (h_i ▸ Nat.sub_le _ _) acc)
    from this (n-i) (Nat.sub_le _ _) i (Nat.sub_sub_self h).symm acc
  intro j h i h_i acc
  induction j generalizing i acc with
  | zero =>
    simp at h_i
    cases h_i
    unfold aux
    simp
    split
    case inl h => cases h
    unfold aux
    simp
  | succ j ih =>
    conv => lhs; unfold aux
    conv => rhs; unfold aux
    have : i < n := by
      cases h_i
      apply Nat.sub_lt_of_pos_le _ _ (Nat.zero_lt_succ _) h
    have i_nsucc : i ≠ n+1 :=
      Nat.ne_of_lt (Nat.le_step this)
    have i_n : i ≠ n :=
      Nat.ne_of_lt this
    simp [i_nsucc, i_n]
    apply ih
    exact Nat.le_of_lt h
    cases h_i
    rw [Nat.sub_succ]
    rw [Nat.succ_pred]
    apply Ne.symm; apply Nat.ne_of_lt
    apply Nat.lt_sub_of_add_lt
    simp
    exact h

@[simp]
theorem foldl''_zero {f : (_ : _) → _ → _ → _}
  : foldl'' ⟨0⟩ motive f init = init
  := by
  unfold foldl''
  unfold foldl''.aux
  simp

@[simp]
theorem foldl''_step {n} {f : (_ : _) → _ → _ → _}
  : foldl'' ⟨n+1⟩ motive f init =
    f n (Nat.lt_succ_self _) (
      foldl'' ⟨n⟩
        motive
        (f := fun i h acc => f i (Nat.le_step h) acc)
        init)
  := by
  simp [foldl'']
  apply foldl''.aux_step

def foldlImpl (f : β → Nat → β) (acc : β) (r : Range) : β :=
  foldl'' r (motive := λ _ => β) (fun i _ acc => f acc i) acc

@[csimp]
theorem foldl_eq_foldlImpl
  : @foldl = @foldlImpl
  := by
  funext β f acc r
  cases r; case mk n =>
  simp [foldlImpl]
  induction n with
  | zero =>
    simp [foldl, foldl'']
    unfold foldl''.aux
    simp
  | succ n ih =>
    simp [foldl]
    rw [ih]

def foldl'Impl (r : Range) (f : β → (i : Nat) → i ∈ r → β) (init : β) : β :=
  foldl'' r (fun _ => β) (fun i h acc => f acc i h) init

@[csimp]
theorem foldl'_eq_foldl'Impl
  : @foldl' = @foldl'Impl
  := by
  funext β r f acc
  cases r; case mk n =>
  simp [foldl'Impl]
  induction n with
  | zero =>
    simp [foldl'']
    unfold foldl''.aux
    simp
  | succ n ih =>
    simp [foldl']
    rw [ih]

/- ## Class instances -/

instance : Foldable'.Correct Range Nat inferInstance where
  fold r := r.foldl
  fold' := foldl'
  memCorrect := memCorrect
  foldCorrect := by simp [Foldable.fold, foldl_correct]
  fold'Correct := by
    intro _ c f acc; simp [Foldable'.fold']; rw [foldl'_correct]; rfl

instance : FoldableOps Range Nat := {
  (default : FoldableOps Range Nat) with
  contains := λ r _ i => i < r.n
  toList := toList
}

instance : Iterable Range Nat where
  ρ := Nat × Nat
  step := λ (i,stop) => if i < stop then some (i, (i.succ, stop)) else none
  toIterator := λ r => (0,r.n)

instance : Iterable' Range Nat inferInstance where
  ρ c := Σ' (i : Nat) (stop : Nat), i ≤ stop ∧ stop = c.n
  step := λ ⟨i,stop,_,h_stop⟩ =>
    if h : i < stop then
      some (⟨i, by rw [h_stop] at h; exact h⟩, ⟨i.succ, stop, h, h_stop⟩)
    else none
  toIterator := λ r => ⟨0,r.n,Nat.zero_le _,rfl⟩

/- ## Lemmas -/

@[simp]
theorem mem_toList_iff {r : Range} {x}
  : x ∈ toList r ↔ x < r.n
  := by rw [toList_eq_canonicalToList, ←memCorrect]; simp [Membership.mem]

theorem size_pos_of_mem {r : Range} {x}
  : x ∈ r → 0 < r.n
  := by
  intro h; apply Nat.lt_of_le_of_lt (Nat.zero_le _) h

@[simp]
theorem length_toList {r : Range}
  : r.toList.length = r.n
  := by cases r; case mk n => induction n; simp; simp; assumption

@[simp]
theorem get_toList {r : Range} {i : Fin r.toList.length}
  : r.toList.get i = i
  := by cases r; case mk n =>
  induction n with
  | zero => simp at i; exact i.elim0
  | succ n ih =>
    suffices ∀ L (_ : L = toList ⟨n.succ⟩) i,
      List.get L i = i
      from this _ rfl i
    intro L hL i
    rw [toList_succ] at hL
    cases i; case mk i hi =>
    simp
    simp [hL] at hi
    cases Nat.eq_or_lt_of_le (Nat.le_of_succ_le_succ hi)
    case inl h =>
      cases h
      cases hL
      rw [List.get_append_right]
      simp
      simp
      simp
    case inr h =>
      cases hL
      rw [List.get_append_left]
      apply ih
      simp
      assumption

theorem map_set_toList {r : Range} {f : Nat → τ}
  : (r.toList.set i j).map f = (r.toList.map f).set i (f j)
  := by
  cases r; case mk n =>
  cases Nat.decLt i n
  case isFalse h =>
    rw [List.set_of_ge_length (toList ⟨n⟩) _ _]
    rw [List.set_of_ge_length (List.map f (toList ⟨n⟩))]
    simp at h ⊢; exact h
    simp at h ⊢; exact h
  case isTrue hi =>
  induction n with
  | zero => simp
  | succ n ih =>
    simp
    cases Nat.decLt i (toList ⟨n⟩).length
    case isFalse h =>
      have : i = n :=
        Nat.le_antisymm
          (Nat.le_of_succ_le_succ hi)
          (by simp at h; exact h)
      cases this
      rw [List.set_append_right]
      rw [List.set_append_right]
      simp [List.set]
      simp
      simp
    case isTrue h =>
      simp at h
      rw [List.set_append_left]
      rw [List.set_append_left]
      simp
      apply ih
      assumption
      simp; assumption
      simp; assumption

theorem set_map'_toList {r : Range} {i} {x : τ} {f}
  : (r.toList.map' f).set i x
    = r.toList.map' (fun j h' =>
      if j = i
      then x
      else f j h'
      )
  := by
  cases r; case mk n =>
  cases Nat.decLt i n
  case isFalse h =>
    rw [List.set_of_ge_length ((toList ⟨n⟩).map' f)]
    next =>
      congr
      funext j hj
      simp at hj
      simp [Nat.ne_of_lt (Nat.lt_of_lt_of_le hj (Nat.le_of_not_lt h))]
    simp at h ⊢; exact h
  case isTrue hi =>
  simp [List.map']
  suffices ∀ f, List.set (List.map f _) _ _
    = List.map (fun ⟨j,h⟩ => if j = i then x else f ⟨j,h⟩) (List.subtypeByMem (toList ⟨n⟩))
    from this _
  intro f
  induction n with
  | zero =>
    simp
  | succ n ih =>
    rw [List.subtypeByMem_rw _ (toList_succ _)]
    simp [List.subtypeByMem_append]
    cases Nat.decLt i n
    case isFalse h =>
      have : i = n :=
        Nat.le_antisymm
          (Nat.le_of_succ_le_succ hi)
          (by simp at h; exact h)
      cases this
      rw [List.set_append_right]
      next =>
        simp [List.set]
        congr
        funext j'
        cases j'; case mk j' hj' =>
        rw [toList_eq_canonicalToList] at hj'
        rw [←memCorrect] at hj'
        simp [Membership.mem] at hj'
        simp [Nat.ne_of_lt hj']
      simp
    case isTrue h' =>
      rw [List.set_append_left]
      simp [(Nat.ne_of_lt h').symm]
      rw [ih]
      simp
      congr
      assumption
      exact fun x h => f ⟨x,by simp; apply Or.inl; simp at h; assumption⟩
      simp; assumption

end Range

/-! # Range.Complex

A more complicated range, defined by a `start`, `step`, and `stop` value.

The sequence proceeds: `start`, `start + step`, `start + 2 * step`, ...

... until the value passes `stop`. For `step > 0`, the values are upper bounded
by `stop`, while for `step < 0` the values are lower bounded by `stop`.

Similar to `Std.Range`, but allows negative values for start/stop/step.
-/
structure Range.Complex where
  start : Int
  stop : Int
  step : Int
  h_step : step ≠ 0

namespace Range.Complex

def fold : (β → Int → β) → β → Range.Complex → β :=
  let rec loopUp {α} (start stop step : Int) (h_step : step > 0)
    (f : α → Int → α) acc i : α :=
    if h:i < stop then
      have : Int.natAbs (stop + step - (i+step))
              < Int.natAbs (stop + step - i)
        := by
        rw [(by rw [Int.sub_eq_add_neg, Int.sub_eq_add_neg, Int.neg_add, Int.add_comm (-i), Int.add_assoc, ←Int.add_assoc step, Int.add_right_neg]; simp
            : stop + step - (i+step) = stop - i)]
        have h1 : stop - i > 0 := by
          apply Int.lt_sub_right_of_add_lt (a := 0)
          simp [h]
        have h2 : stop + step - i > 0 := by
          rw [Int.add_comm, Int.add_sub_assoc, ←Int.add_zero 0]
          apply Int.add_lt_add_of_le_of_lt (Int.le_of_lt h_step) h1
        suffices stop - i < stop + step - i by
          rw [←Int.ofNat_natAbs_eq_of_nonneg _ (Int.le_of_lt h1)] at this
          rw [←Int.ofNat_natAbs_eq_of_nonneg _ (Int.le_of_lt h2)] at this
          simp at this
          assumption
        rw [Int.sub_eq_add_neg,
            Int.sub_eq_add_neg]
        apply Int.add_lt_add_of_lt_of_le
        apply Int.lt_add_of_pos_right _ h_step
        apply Int.le_refl
      loopUp start stop step h_step f (f acc i) (i+step)
    else
      acc
  let rec loopDown {α} (start stop step : Int) (h_step : step < 0)
    (f : α → Int → α) acc i : α :=
    if h:i > stop then
      have : Int.natAbs (stop + step - (i+step))
              < Int.natAbs (stop + step - i)
        := by
        rw [(by rw [Int.sub_eq_add_neg, Int.sub_eq_add_neg, Int.neg_add, Int.add_comm (-i), Int.add_assoc, ←Int.add_assoc step, Int.add_right_neg]; simp
            : stop + step - (i+step) = stop - i)]
        have h1 : stop - i < 0 := by
          apply Int.sub_left_lt_of_lt_add
          simp [h]
        have h2 : stop + step - i < 0 := by
          rw [Int.add_comm, Int.add_sub_assoc, ←Int.add_zero 0]
          apply Int.add_lt_add_of_le_of_lt (Int.le_of_lt h_step) h1
        suffices stop - i > stop + step - i by
          have := Int.neg_lt_neg this
          rw [←Int.ofNat_natAbs_eq_neg_of_nonpos _ (Int.le_of_lt h1)] at this
          rw [←Int.ofNat_natAbs_eq_neg_of_nonpos _ (Int.le_of_lt h2)] at this
          simp at this
          assumption
        rw [Int.sub_eq_add_neg,
            Int.sub_eq_add_neg]
        apply Int.add_lt_add_of_lt_of_le
        conv =>
          rhs
          rw [←Int.add_zero stop]
        apply Int.add_lt_add_left h_step
        apply Int.le_refl
      loopDown start stop step h_step f (f acc i) (i+step)
    else
      acc

  λ f acc ⟨start,stop,step,h_step⟩ =>
    if h_step:step > 0 then
      loopUp start stop step h_step f acc 0
    else
      have h_step : step < 0 := by
        apply Int.lt_iff_le_and_ne.mpr
        constructor
        simp at h_step
        assumption
        assumption
      loopDown start stop step h_step f acc 0
  termination_by
    loopUp i => Int.natAbs (stop + step - i)
    loopDown i => Int.natAbs (stop + step - i)

/-
instance : Membership Nat Range where
  mem x r := x < r.stop ∧ ∃ k, x = r.start + k * r.step

def fold' : (r : Range) → (β → (i : Nat) → i ∈ r → β) → β → β :=
  let rec @[inline] loop {α} (start stop step h_step)
    (f : α → (i : Nat) → i ∈ (⟨start,stop,step,h_step⟩ : Range) → α) acc
    i (h_i : ∃ k, i = start + k * step) : α :=
    if h:i < stop then
      have : stop - (i + step) < stop - i := by
        rw [Nat.sub_dist]
        apply Nat.sub_lt
        exact Nat.zero_lt_sub_of_lt h
        assumption
      have : i ∈ (⟨start,stop,step,h_step⟩ : Range) := ⟨h_step, h, h_i⟩
      loop start stop step h_step f (f acc i this) (i+step) (by
        cases h_i; case intro k h_i =>
        apply Exists.intro (k+1)
        simp [Nat.succ_mul]
        rw [←Nat.add_assoc, h_i]
      )
    else
      acc
  λ ⟨start,stop,step,h_step⟩ f acc =>
    loop start stop step h_step f acc start ⟨0,by simp⟩
  termination_by loop _ _ i _ => stop - i

def last (r : Range) := r.start + ((r.stop - r.start) / r.step) * r.step

theorem mem_last (r : Range) : r.last ∈ r := by
  simp [last, Membership.mem]
  sorry

theorem fold_ind {start stop step : Nat}
  {f : β → (i : Nat) → i ∈ (⟨start,stop,step⟩ : Range) → β}
  {acc : β} {motive : Nat → β → Prop}
  (base : motive 0 acc)
  (ind_step : ∀ i acc, (h : i ∈ (⟨start,stop,step⟩ : Range)) → motive i acc → motive (i+step) (f acc i h))
  : motive n (fold' (⟨start,stop,step⟩ : Range) f acc)
  :=
  let rec loop i (acc : β) (h_i : i ≤ n) (h_acc : motive i acc)
    : motive i () :=
    if h:i < n then by
      unfold fold.loop
      simp [h]
      exact loop i.succ (f acc ⟨i,h⟩) h (step i acc h h_acc)
    else by
      have : i = n := (Nat.eq_or_lt_of_le h_i).elim (id) (False.elim ∘ h)
      unfold fold.loop
      simp [h]
      rw [this] at h_acc
      exact h_acc
  loop 0 acc (Nat.zero_le _) init
  termination_by loop _ _ _i => n - i

instance : Foldable Range Nat where
  fold := fold

instance : FoldableOps Range Nat := default

instance : Foldable' Range Nat inferInstance where
  fold' := fold'

instance : Iterable Range Nat where
  ρ := Nat
  step := λ i => if h:i < n then some ⟨⟨i,h⟩,i.succ⟩ else none
  toIterator := λ _ => 0
-/

end Range.Complex

end LeanColls


import group_theory.subgroup

theorem cpge_groupe_9_a {G1 : Type*} [group G1] {G2 : Type*} [group G2]
(f : G1 →* G2) : ∀ (a : G1), ( (f a = 1) → (∀ (x : G1), f (x * a * x⁻¹) = 1))  := sorry


import data.real.basic
import data.polynomial.basic
import data.polynomial.eval

theorem exo (f: polynomial real):
  let f_fun (x: real) := polynomial.eval x f in
  (forall (a b c: real), a*b + b*c + c*a = 0 -> f_fun(a - b) + f_fun(b - c) + f_fun(c - a) = 2*f_fun(a + b + c))
  <-> (exists c1 c2, forall x, f_fun x = c1 * x^4 + c2 * x^2)
:=
  sorry


/-
Copyright (c) 2018 Chris Hughes. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Chris Hughes, Abhimanyu Pallavi Sudhir, Jean Lo, Calle Sönne, Sébastien Gouëzel,
  Rémy Degenne
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.analysis.special_functions.trigonometric
import Mathlib.analysis.calculus.extend_deriv
import Mathlib.PostPort

universes u_1

namespace Mathlib

/-!
# Power function on `ℂ`, `ℝ`, `ℝ≥0`, and `ennreal`

We construct the power functions `x ^ y` where
* `x` and `y` are complex numbers,
* or `x` and `y` are real numbers,
* or `x` is a nonnegative real number and `y` is a real number;
* or `x` is a number from `[0, +∞]` (a.k.a. `ennreal`) and `y` is a real number.

We also prove basic properties of these functions.
-/

namespace complex

/-- The complex power function `x^y`, given by `x^y = exp(y log x)` (where `log` is the principal
determination of the logarithm), unless `x = 0` where one sets `0^0 = 1` and `0^y = 0` for
`y ≠ 0`. -/
def cpow (x : ℂ) (y : ℂ) : ℂ := ite (x = 0) (ite (y = 0) 1 0) (exp (log x * y))

protected instance has_pow : has_pow ℂ ℂ := has_pow.mk cpow

@[simp] theorem cpow_eq_pow (x : ℂ) (y : ℂ) : cpow x y = x ^ y := rfl

theorem cpow_def (x : ℂ) (y : ℂ) : x ^ y = ite (x = 0) (ite (y = 0) 1 0) (exp (log x * y)) := rfl

@[simp] theorem cpow_zero (x : ℂ) : x ^ 0 = 1 := sorry

@[simp] theorem cpow_eq_zero_iff (x : ℂ) (y : ℂ) : x ^ y = 0 ↔ x = 0 ∧ y ≠ 0 := sorry

@[simp] theorem zero_cpow {x : ℂ} (h : x ≠ 0) : 0 ^ x = 0 := sorry

@[simp] theorem cpow_one (x : ℂ) : x ^ 1 = x := sorry

@[simp] theorem one_cpow (x : ℂ) : 1 ^ x = 1 := sorry

theorem cpow_add {x : ℂ} (y : ℂ) (z : ℂ) (hx : x ≠ 0) : x ^ (y + z) = x ^ y * x ^ z := sorry

theorem cpow_mul {x : ℂ} {y : ℂ} (z : ℂ) (h₁ : -real.pi < im (log x * y))
    (h₂ : im (log x * y) ≤ real.pi) : x ^ (y * z) = (x ^ y) ^ z :=
  sorry

theorem cpow_neg (x : ℂ) (y : ℂ) : x ^ (-y) = (x ^ y⁻¹) := sorry

theorem cpow_neg_one (x : ℂ) : x ^ (-1) = (x⁻¹) := sorry

@[simp] theorem cpow_nat_cast (x : ℂ) (n : ℕ) : x ^ ↑n = x ^ n := sorry

@[simp] theorem cpow_int_cast (x : ℂ) (n : ℤ) : x ^ ↑n = x ^ n := sorry

theorem cpow_nat_inv_pow (x : ℂ) {n : ℕ} (hn : 0 < n) : (x ^ (↑n⁻¹)) ^ n = x := sorry

end complex

namespace real

/-- The real power function `x^y`, defined as the real part of the complex power function.
For `x > 0`, it is equal to `exp(y log x)`. For `x = 0`, one sets `0^0=1` and `0^y=0` for `y ≠ 0`.
For `x < 0`, the definition is somewhat arbitary as it depends on the choice of a complex
determination of the logarithm. With our conventions, it is equal to `exp (y log x) cos (πy)`. -/
def rpow (x : ℝ) (y : ℝ) : ℝ := complex.re (↑x ^ ↑y)

protected instance has_pow : has_pow ℝ ℝ := has_pow.mk rpow

@[simp] theorem rpow_eq_pow (x : ℝ) (y : ℝ) : rpow x y = x ^ y := rfl

theorem rpow_def (x : ℝ) (y : ℝ) : x ^ y = complex.re (↑x ^ ↑y) := rfl

theorem rpow_def_of_nonneg {x : ℝ} (hx : 0 ≤ x) (y : ℝ) :
    x ^ y = ite (x = 0) (ite (y = 0) 1 0) (exp (log x * y)) :=
  sorry

theorem rpow_def_of_pos {x : ℝ} (hx : 0 < x) (y : ℝ) : x ^ y = exp (log x * y) := sorry

theorem exp_mul (x : ℝ) (y : ℝ) : exp (x * y) = exp x ^ y :=
  eq.mpr (id (Eq._oldrec (Eq.refl (exp (x * y) = exp x ^ y)) (rpow_def_of_pos (exp_pos x) y)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (exp (x * y) = exp (log (exp x) * y))) (log_exp x)))
      (Eq.refl (exp (x * y))))

theorem rpow_eq_zero_iff_of_nonneg {x : ℝ} {y : ℝ} (hx : 0 ≤ x) : x ^ y = 0 ↔ x = 0 ∧ y ≠ 0 := sorry

theorem rpow_def_of_neg {x : ℝ} (hx : x < 0) (y : ℝ) : x ^ y = exp (log x * y) * cos (y * pi) :=
  sorry

theorem rpow_def_of_nonpos {x : ℝ} (hx : x ≤ 0) (y : ℝ) :
    x ^ y = ite (x = 0) (ite (y = 0) 1 0) (exp (log x * y) * cos (y * pi)) :=
  sorry

theorem rpow_pos_of_pos {x : ℝ} (hx : 0 < x) (y : ℝ) : 0 < x ^ y :=
  eq.mpr (id (Eq._oldrec (Eq.refl (0 < x ^ y)) (rpow_def_of_pos hx y))) (exp_pos (log x * y))

@[simp] theorem rpow_zero (x : ℝ) : x ^ 0 = 1 := sorry

@[simp] theorem zero_rpow {x : ℝ} (h : x ≠ 0) : 0 ^ x = 0 := sorry

@[simp] theorem rpow_one (x : ℝ) : x ^ 1 = x := sorry

@[simp] theorem one_rpow (x : ℝ) : 1 ^ x = 1 := sorry

theorem zero_rpow_le_one (x : ℝ) : 0 ^ x ≤ 1 := sorry

theorem zero_rpow_nonneg (x : ℝ) : 0 ≤ 0 ^ x := sorry

theorem rpow_nonneg_of_nonneg {x : ℝ} (hx : 0 ≤ x) (y : ℝ) : 0 ≤ x ^ y := sorry

theorem abs_rpow_le_abs_rpow (x : ℝ) (y : ℝ) : abs (x ^ y) ≤ abs x ^ y := sorry

end real

namespace complex

theorem of_real_cpow {x : ℝ} (hx : 0 ≤ x) (y : ℝ) : ↑(x ^ y) = ↑x ^ ↑y := sorry

@[simp] theorem abs_cpow_real (x : ℂ) (y : ℝ) : abs (x ^ ↑y) = abs x ^ y := sorry

@[simp] theorem abs_cpow_inv_nat (x : ℂ) (n : ℕ) : abs (x ^ (↑n⁻¹)) = abs x ^ (↑n⁻¹) := sorry

end complex

namespace real

theorem rpow_add {x : ℝ} (hx : 0 < x) (y : ℝ) (z : ℝ) : x ^ (y + z) = x ^ y * x ^ z := sorry

theorem rpow_add' {x : ℝ} (hx : 0 ≤ x) {y : ℝ} {z : ℝ} (h : y + z ≠ 0) :
    x ^ (y + z) = x ^ y * x ^ z :=
  sorry

/-- For `0 ≤ x`, the only problematic case in the equality `x ^ y * x ^ z = x ^ (y + z)` is for
`x = 0` and `y + z = 0`, where the right hand side is `1` while the left hand side can vanish.
The inequality is always true, though, and given in this lemma. -/
theorem le_rpow_add {x : ℝ} (hx : 0 ≤ x) (y : ℝ) (z : ℝ) : x ^ y * x ^ z ≤ x ^ (y + z) := sorry

theorem rpow_mul {x : ℝ} (hx : 0 ≤ x) (y : ℝ) (z : ℝ) : x ^ (y * z) = (x ^ y) ^ z := sorry

theorem rpow_neg {x : ℝ} (hx : 0 ≤ x) (y : ℝ) : x ^ (-y) = (x ^ y⁻¹) := sorry

theorem rpow_sub {x : ℝ} (hx : 0 < x) (y : ℝ) (z : ℝ) : x ^ (y - z) = x ^ y / x ^ z := sorry

theorem rpow_sub' {x : ℝ} (hx : 0 ≤ x) {y : ℝ} {z : ℝ} (h : y - z ≠ 0) :
    x ^ (y - z) = x ^ y / x ^ z :=
  sorry

@[simp] theorem rpow_nat_cast (x : ℝ) (n : ℕ) : x ^ ↑n = x ^ n := sorry

@[simp] theorem rpow_int_cast (x : ℝ) (n : ℤ) : x ^ ↑n = x ^ n := sorry

theorem rpow_neg_one (x : ℝ) : x ^ (-1) = (x⁻¹) := sorry

theorem mul_rpow {x : ℝ} {y : ℝ} {z : ℝ} (h : 0 ≤ x) (h₁ : 0 ≤ y) : (x * y) ^ z = x ^ z * y ^ z :=
  sorry

theorem inv_rpow {x : ℝ} (hx : 0 ≤ x) (y : ℝ) : x⁻¹ ^ y = (x ^ y⁻¹) := sorry

theorem div_rpow {x : ℝ} {y : ℝ} (hx : 0 ≤ x) (hy : 0 ≤ y) (z : ℝ) : (x / y) ^ z = x ^ z / y ^ z :=
  sorry

theorem log_rpow {x : ℝ} (hx : 0 < x) (y : ℝ) : log (x ^ y) = y * log x := sorry

theorem rpow_lt_rpow {x : ℝ} {y : ℝ} {z : ℝ} (hx : 0 ≤ x) (hxy : x < y) (hz : 0 < z) :
    x ^ z < y ^ z :=
  sorry

theorem rpow_le_rpow {x : ℝ} {y : ℝ} {z : ℝ} (h : 0 ≤ x) (h₁ : x ≤ y) (h₂ : 0 ≤ z) :
    x ^ z ≤ y ^ z :=
  sorry

theorem rpow_lt_rpow_iff {x : ℝ} {y : ℝ} {z : ℝ} (hx : 0 ≤ x) (hy : 0 ≤ y) (hz : 0 < z) :
    x ^ z < y ^ z ↔ x < y :=
  { mp := lt_imp_lt_of_le_imp_le fun (h : y ≤ x) => rpow_le_rpow hy h (le_of_lt hz),
    mpr := fun (h : x < y) => rpow_lt_rpow hx h hz }

theorem rpow_le_rpow_iff {x : ℝ} {y : ℝ} {z : ℝ} (hx : 0 ≤ x) (hy : 0 ≤ y) (hz : 0 < z) :
    x ^ z ≤ y ^ z ↔ x ≤ y :=
  iff.mpr le_iff_le_iff_lt_iff_lt (rpow_lt_rpow_iff hy hx hz)

theorem rpow_lt_rpow_of_exponent_lt {x : ℝ} {y : ℝ} {z : ℝ} (hx : 1 < x) (hyz : y < z) :
    x ^ y < x ^ z :=
  sorry

theorem rpow_le_rpow_of_exponent_le {x : ℝ} {y : ℝ} {z : ℝ} (hx : 1 ≤ x) (hyz : y ≤ z) :
    x ^ y ≤ x ^ z :=
  sorry

theorem rpow_lt_rpow_of_exponent_gt {x : ℝ} {y : ℝ} {z : ℝ} (hx0 : 0 < x) (hx1 : x < 1)
    (hyz : z < y) : x ^ y < x ^ z :=
  sorry

theorem rpow_le_rpow_of_exponent_ge {x : ℝ} {y : ℝ} {z : ℝ} (hx0 : 0 < x) (hx1 : x ≤ 1)
    (hyz : z ≤ y) : x ^ y ≤ x ^ z :=
  sorry

theorem rpow_lt_one {x : ℝ} {z : ℝ} (hx1 : 0 ≤ x) (hx2 : x < 1) (hz : 0 < z) : x ^ z < 1 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (x ^ z < 1)) (Eq.symm (one_rpow z)))) (rpow_lt_rpow hx1 hx2 hz)

theorem rpow_le_one {x : ℝ} {z : ℝ} (hx1 : 0 ≤ x) (hx2 : x ≤ 1) (hz : 0 ≤ z) : x ^ z ≤ 1 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (x ^ z ≤ 1)) (Eq.symm (one_rpow z)))) (rpow_le_rpow hx1 hx2 hz)

theorem rpow_lt_one_of_one_lt_of_neg {x : ℝ} {z : ℝ} (hx : 1 < x) (hz : z < 0) : x ^ z < 1 := sorry

theorem rpow_le_one_of_one_le_of_nonpos {x : ℝ} {z : ℝ} (hx : 1 ≤ x) (hz : z ≤ 0) : x ^ z ≤ 1 :=
  sorry

theorem one_lt_rpow {x : ℝ} {z : ℝ} (hx : 1 < x) (hz : 0 < z) : 1 < x ^ z :=
  eq.mpr (id (Eq._oldrec (Eq.refl (1 < x ^ z)) (Eq.symm (one_rpow z))))
    (rpow_lt_rpow zero_le_one hx hz)

theorem one_le_rpow {x : ℝ} {z : ℝ} (hx : 1 ≤ x) (hz : 0 ≤ z) : 1 ≤ x ^ z :=
  eq.mpr (id (Eq._oldrec (Eq.refl (1 ≤ x ^ z)) (Eq.symm (one_rpow z))))
    (rpow_le_rpow zero_le_one hx hz)

theorem one_lt_rpow_of_pos_of_lt_one_of_neg {x : ℝ} {z : ℝ} (hx1 : 0 < x) (hx2 : x < 1)
    (hz : z < 0) : 1 < x ^ z :=
  sorry

theorem one_le_rpow_of_pos_of_le_one_of_nonpos {x : ℝ} {z : ℝ} (hx1 : 0 < x) (hx2 : x ≤ 1)
    (hz : z ≤ 0) : 1 ≤ x ^ z :=
  sorry

theorem rpow_lt_one_iff_of_pos {x : ℝ} {y : ℝ} (hx : 0 < x) :
    x ^ y < 1 ↔ 1 < x ∧ y < 0 ∨ x < 1 ∧ 0 < y :=
  sorry

theorem rpow_lt_one_iff {x : ℝ} {y : ℝ} (hx : 0 ≤ x) :
    x ^ y < 1 ↔ x = 0 ∧ y ≠ 0 ∨ 1 < x ∧ y < 0 ∨ x < 1 ∧ 0 < y :=
  sorry

theorem one_lt_rpow_iff_of_pos {x : ℝ} {y : ℝ} (hx : 0 < x) :
    1 < x ^ y ↔ 1 < x ∧ 0 < y ∨ x < 1 ∧ y < 0 :=
  sorry

theorem one_lt_rpow_iff {x : ℝ} {y : ℝ} (hx : 0 ≤ x) :
    1 < x ^ y ↔ 1 < x ∧ 0 < y ∨ 0 < x ∧ x < 1 ∧ y < 0 :=
  sorry

theorem rpow_le_one_iff_of_pos {x : ℝ} {y : ℝ} (hx : 0 < x) :
    x ^ y ≤ 1 ↔ 1 ≤ x ∧ y ≤ 0 ∨ x ≤ 1 ∧ 0 ≤ y :=
  sorry

theorem pow_nat_rpow_nat_inv {x : ℝ} (hx : 0 ≤ x) {n : ℕ} (hn : 0 < n) : (x ^ n) ^ (↑n⁻¹) = x :=
  sorry

theorem rpow_nat_inv_pow_nat {x : ℝ} (hx : 0 ≤ x) {n : ℕ} (hn : 0 < n) : (x ^ (↑n⁻¹)) ^ n = x :=
  sorry

theorem continuous_rpow_aux1 :
    continuous
        fun (p : Subtype fun (p : ℝ × ℝ) => 0 < prod.fst p) =>
          prod.fst (subtype.val p) ^ prod.snd (subtype.val p) :=
  sorry

theorem continuous_rpow_aux2 :
    continuous
        fun (p : Subtype fun (p : ℝ × ℝ) => prod.fst p < 0) =>
          prod.fst (subtype.val p) ^ prod.snd (subtype.val p) :=
  sorry

theorem continuous_at_rpow_of_ne_zero {x : ℝ} (hx : x ≠ 0) (y : ℝ) :
    continuous_at (fun (p : ℝ × ℝ) => prod.fst p ^ prod.snd p) (x, y) :=
  sorry

theorem continuous_rpow_aux3 :
    continuous
        fun (p : Subtype fun (p : ℝ × ℝ) => 0 < prod.snd p) =>
          prod.fst (subtype.val p) ^ prod.snd (subtype.val p) :=
  sorry

theorem continuous_at_rpow_of_pos {y : ℝ} (hy : 0 < y) (x : ℝ) :
    continuous_at (fun (p : ℝ × ℝ) => prod.fst p ^ prod.snd p) (x, y) :=
  sorry

theorem continuous_at_rpow {x : ℝ} {y : ℝ} (h : x ≠ 0 ∨ 0 < y) :
    continuous_at (fun (p : ℝ × ℝ) => prod.fst p ^ prod.snd p) (x, y) :=
  or.dcases_on h (fun (h : x ≠ 0) => continuous_at_rpow_of_ne_zero h y)
    fun (h : 0 < y) => continuous_at_rpow_of_pos h x

/--
`real.rpow` is continuous at all points except for the lower half of the y-axis.
In other words, the function `λp:ℝ×ℝ, p.1^p.2` is continuous at `(x, y)` if `x ≠ 0` or `y > 0`.

Multiple forms of the claim is provided in the current section.
-/
theorem continuous_rpow {α : Type u_1} [topological_space α] {f : α → ℝ} {g : α → ℝ}
    (h : ∀ (a : α), f a ≠ 0 ∨ 0 < g a) (hf : continuous f) (hg : continuous g) :
    continuous fun (a : α) => f a ^ g a :=
  sorry

theorem continuous_rpow_of_ne_zero {α : Type u_1} [topological_space α] {f : α → ℝ} {g : α → ℝ}
    (h : ∀ (a : α), f a ≠ 0) (hf : continuous f) (hg : continuous g) :
    continuous fun (a : α) => f a ^ g a :=
  continuous_rpow (fun (a : α) => Or.inl (h a)) hf hg

theorem continuous_rpow_of_pos {α : Type u_1} [topological_space α] {f : α → ℝ} {g : α → ℝ}
    (h : ∀ (a : α), 0 < g a) (hf : continuous f) (hg : continuous g) :
    continuous fun (a : α) => f a ^ g a :=
  continuous_rpow (fun (a : α) => Or.inr (h a)) hf hg

theorem has_deriv_at_rpow_of_pos {x : ℝ} (h : 0 < x) (p : ℝ) :
    has_deriv_at (fun (x : ℝ) => x ^ p) (p * x ^ (p - 1)) x :=
  sorry

theorem has_deriv_at_rpow_of_neg {x : ℝ} (h : x < 0) (p : ℝ) :
    has_deriv_at (fun (x : ℝ) => x ^ p) (p * x ^ (p - 1)) x :=
  sorry

theorem has_deriv_at_rpow {x : ℝ} (h : x ≠ 0) (p : ℝ) :
    has_deriv_at (fun (x : ℝ) => x ^ p) (p * x ^ (p - 1)) x :=
  or.dcases_on (lt_trichotomy x 0) (fun (H : x < 0) => has_deriv_at_rpow_of_neg H p)
    fun (h_1 : x = 0 ∨ 0 < x) =>
      or.dcases_on h_1 (fun (H : x = 0) => false.elim (h H))
        fun (H : 0 < x) => has_deriv_at_rpow_of_pos H p

theorem has_deriv_at_rpow_zero_of_one_le {p : ℝ} (h : 1 ≤ p) :
    has_deriv_at (fun (x : ℝ) => x ^ p) (p * 0 ^ (p - 1)) 0 :=
  sorry

theorem has_deriv_at_rpow_of_one_le (x : ℝ) {p : ℝ} (h : 1 ≤ p) :
    has_deriv_at (fun (x : ℝ) => x ^ p) (p * x ^ (p - 1)) x :=
  sorry

theorem sqrt_eq_rpow : sqrt = fun (x : ℝ) => x ^ (1 / bit0 1) := sorry

end real

theorem real.measurable_rpow : measurable fun (p : ℝ × ℝ) => prod.fst p ^ prod.snd p := sorry

theorem measurable.rpow {α : Type u_1} [measurable_space α] {f : α → ℝ} {g : α → ℝ}
    (hf : measurable f) (hg : measurable g) : measurable fun (a : α) => f a ^ g a :=
  id (measurable.comp real.measurable_rpow (measurable.prod hf hg))

theorem real.measurable_rpow_const {y : ℝ} : measurable fun (x : ℝ) => x ^ y :=
  id (measurable.comp real.measurable_rpow (measurable.prod measurable_id (id measurable_const)))

theorem measurable.rpow_const {α : Type u_1} [measurable_space α] {f : α → ℝ} (hf : measurable f)
    {y : ℝ} : measurable fun (a : α) => f a ^ y :=
  measurable.rpow hf measurable_const

/- Differentiability statements for the power of a function, when the function does not vanish
and the exponent is arbitrary-/

theorem has_deriv_within_at.rpow {f : ℝ → ℝ} {x : ℝ} {f' : ℝ} {s : set ℝ} (p : ℝ)
    (hf : has_deriv_within_at f f' s x) (hx : f x ≠ 0) :
    has_deriv_within_at (fun (y : ℝ) => f y ^ p) (f' * p * f x ^ (p - 1)) s x :=
  sorry

theorem has_deriv_at.rpow {f : ℝ → ℝ} {x : ℝ} {f' : ℝ} (p : ℝ) (hf : has_deriv_at f f' x)
    (hx : f x ≠ 0) : has_deriv_at (fun (y : ℝ) => f y ^ p) (f' * p * f x ^ (p - 1)) x :=
  sorry

theorem differentiable_within_at.rpow {f : ℝ → ℝ} {x : ℝ} {s : set ℝ} (p : ℝ)
    (hf : differentiable_within_at ℝ f s x) (hx : f x ≠ 0) :
    differentiable_within_at ℝ (fun (x : ℝ) => f x ^ p) s x :=
  has_deriv_within_at.differentiable_within_at
    (has_deriv_within_at.rpow p (differentiable_within_at.has_deriv_within_at hf) hx)

@[simp] theorem differentiable_at.rpow {f : ℝ → ℝ} {x : ℝ} (p : ℝ) (hf : differentiable_at ℝ f x)
    (hx : f x ≠ 0) : differentiable_at ℝ (fun (x : ℝ) => f x ^ p) x :=
  has_deriv_at.differentiable_at (has_deriv_at.rpow p (differentiable_at.has_deriv_at hf) hx)

theorem differentiable_on.rpow {f : ℝ → ℝ} {s : set ℝ} (p : ℝ) (hf : differentiable_on ℝ f s)
    (hx : ∀ (x : ℝ), x ∈ s → f x ≠ 0) : differentiable_on ℝ (fun (x : ℝ) => f x ^ p) s :=
  fun (x : ℝ) (h : x ∈ s) => differentiable_within_at.rpow p (hf x h) (hx x h)

@[simp] theorem differentiable.rpow {f : ℝ → ℝ} (p : ℝ) (hf : differentiable ℝ f)
    (hx : ∀ (x : ℝ), f x ≠ 0) : differentiable ℝ fun (x : ℝ) => f x ^ p :=
  fun (x : ℝ) => differentiable_at.rpow p (hf x) (hx x)

theorem deriv_within_rpow {f : ℝ → ℝ} {x : ℝ} {s : set ℝ} (p : ℝ)
    (hf : differentiable_within_at ℝ f s x) (hx : f x ≠ 0) (hxs : unique_diff_within_at ℝ s x) :
    deriv_within (fun (x : ℝ) => f x ^ p) s x = deriv_within f s x * p * f x ^ (p - 1) :=
  has_deriv_within_at.deriv_within
    (has_deriv_within_at.rpow p (differentiable_within_at.has_deriv_within_at hf) hx) hxs

@[simp] theorem deriv_rpow {f : ℝ → ℝ} {x : ℝ} (p : ℝ) (hf : differentiable_at ℝ f x)
    (hx : f x ≠ 0) : deriv (fun (x : ℝ) => f x ^ p) x = deriv f x * p * f x ^ (p - 1) :=
  has_deriv_at.deriv (has_deriv_at.rpow p (differentiable_at.has_deriv_at hf) hx)

/- Differentiability statements for the power of a function, when the function may vanish
but the exponent is at least one. -/

theorem has_deriv_within_at.rpow_of_one_le {f : ℝ → ℝ} {x : ℝ} {f' : ℝ} {s : set ℝ} {p : ℝ}
    (hf : has_deriv_within_at f f' s x) (hp : 1 ≤ p) :
    has_deriv_within_at (fun (y : ℝ) => f y ^ p) (f' * p * f x ^ (p - 1)) s x :=
  sorry

theorem has_deriv_at.rpow_of_one_le {f : ℝ → ℝ} {x : ℝ} {f' : ℝ} {p : ℝ} (hf : has_deriv_at f f' x)
    (hp : 1 ≤ p) : has_deriv_at (fun (y : ℝ) => f y ^ p) (f' * p * f x ^ (p - 1)) x :=
  sorry

theorem differentiable_within_at.rpow_of_one_le {f : ℝ → ℝ} {x : ℝ} {s : set ℝ} {p : ℝ}
    (hf : differentiable_within_at ℝ f s x) (hp : 1 ≤ p) :
    differentiable_within_at ℝ (fun (x : ℝ) => f x ^ p) s x :=
  has_deriv_within_at.differentiable_within_at
    (has_deriv_within_at.rpow_of_one_le (differentiable_within_at.has_deriv_within_at hf) hp)

@[simp] theorem differentiable_at.rpow_of_one_le {f : ℝ → ℝ} {x : ℝ} {p : ℝ}
    (hf : differentiable_at ℝ f x) (hp : 1 ≤ p) : differentiable_at ℝ (fun (x : ℝ) => f x ^ p) x :=
  has_deriv_at.differentiable_at
    (has_deriv_at.rpow_of_one_le (differentiable_at.has_deriv_at hf) hp)

theorem differentiable_on.rpow_of_one_le {f : ℝ → ℝ} {s : set ℝ} {p : ℝ}
    (hf : differentiable_on ℝ f s) (hp : 1 ≤ p) : differentiable_on ℝ (fun (x : ℝ) => f x ^ p) s :=
  fun (x : ℝ) (h : x ∈ s) => differentiable_within_at.rpow_of_one_le (hf x h) hp

@[simp] theorem differentiable.rpow_of_one_le {f : ℝ → ℝ} {p : ℝ} (hf : differentiable ℝ f)
    (hp : 1 ≤ p) : differentiable ℝ fun (x : ℝ) => f x ^ p :=
  fun (x : ℝ) => differentiable_at.rpow_of_one_le (hf x) hp

theorem deriv_within_rpow_of_one_le {f : ℝ → ℝ} {x : ℝ} {s : set ℝ} {p : ℝ}
    (hf : differentiable_within_at ℝ f s x) (hp : 1 ≤ p) (hxs : unique_diff_within_at ℝ s x) :
    deriv_within (fun (x : ℝ) => f x ^ p) s x = deriv_within f s x * p * f x ^ (p - 1) :=
  has_deriv_within_at.deriv_within
    (has_deriv_within_at.rpow_of_one_le (differentiable_within_at.has_deriv_within_at hf) hp) hxs

@[simp] theorem deriv_rpow_of_one_le {f : ℝ → ℝ} {x : ℝ} {p : ℝ} (hf : differentiable_at ℝ f x)
    (hp : 1 ≤ p) : deriv (fun (x : ℝ) => f x ^ p) x = deriv f x * p * f x ^ (p - 1) :=
  has_deriv_at.deriv (has_deriv_at.rpow_of_one_le (differentiable_at.has_deriv_at hf) hp)

/- Differentiability statements for the square root of a function, when the function does not
vanish -/

theorem has_deriv_within_at.sqrt {f : ℝ → ℝ} {x : ℝ} {f' : ℝ} {s : set ℝ}
    (hf : has_deriv_within_at f f' s x) (hx : f x ≠ 0) :
    has_deriv_within_at (fun (y : ℝ) => real.sqrt (f y)) (f' / (bit0 1 * real.sqrt (f x))) s x :=
  sorry

theorem has_deriv_at.sqrt {f : ℝ → ℝ} {x : ℝ} {f' : ℝ} (hf : has_deriv_at f f' x) (hx : f x ≠ 0) :
    has_deriv_at (fun (y : ℝ) => real.sqrt (f y)) (f' / (bit0 1 * real.sqrt (f x))) x :=
  sorry

theorem differentiable_within_at.sqrt {f : ℝ → ℝ} {x : ℝ} {s : set ℝ}
    (hf : differentiable_within_at ℝ f s x) (hx : f x ≠ 0) :
    differentiable_within_at ℝ (fun (x : ℝ) => real.sqrt (f x)) s x :=
  has_deriv_within_at.differentiable_within_at
    (has_deriv_within_at.sqrt (differentiable_within_at.has_deriv_within_at hf) hx)

@[simp] theorem differentiable_at.sqrt {f : ℝ → ℝ} {x : ℝ} (hf : differentiable_at ℝ f x)
    (hx : f x ≠ 0) : differentiable_at ℝ (fun (x : ℝ) => real.sqrt (f x)) x :=
  has_deriv_at.differentiable_at (has_deriv_at.sqrt (differentiable_at.has_deriv_at hf) hx)

theorem differentiable_on.sqrt {f : ℝ → ℝ} {s : set ℝ} (hf : differentiable_on ℝ f s)
    (hx : ∀ (x : ℝ), x ∈ s → f x ≠ 0) : differentiable_on ℝ (fun (x : ℝ) => real.sqrt (f x)) s :=
  fun (x : ℝ) (h : x ∈ s) => differentiable_within_at.sqrt (hf x h) (hx x h)

@[simp] theorem differentiable.sqrt {f : ℝ → ℝ} (hf : differentiable ℝ f)
    (hx : ∀ (x : ℝ), f x ≠ 0) : differentiable ℝ fun (x : ℝ) => real.sqrt (f x) :=
  fun (x : ℝ) => differentiable_at.sqrt (hf x) (hx x)

theorem deriv_within_sqrt {f : ℝ → ℝ} {x : ℝ} {s : set ℝ} (hf : differentiable_within_at ℝ f s x)
    (hx : f x ≠ 0) (hxs : unique_diff_within_at ℝ s x) :
    deriv_within (fun (x : ℝ) => real.sqrt (f x)) s x =
        deriv_within f s x / (bit0 1 * real.sqrt (f x)) :=
  has_deriv_within_at.deriv_within
    (has_deriv_within_at.sqrt (differentiable_within_at.has_deriv_within_at hf) hx) hxs

@[simp] theorem deriv_sqrt {f : ℝ → ℝ} {x : ℝ} (hf : differentiable_at ℝ f x) (hx : f x ≠ 0) :
    deriv (fun (x : ℝ) => real.sqrt (f x)) x = deriv f x / (bit0 1 * real.sqrt (f x)) :=
  has_deriv_at.deriv (has_deriv_at.sqrt (differentiable_at.has_deriv_at hf) hx)

/-- The function `x ^ y` tends to `+∞` at `+∞` for any positive real `y`. -/
theorem tendsto_rpow_at_top {y : ℝ} (hy : 0 < y) :
    filter.tendsto (fun (x : ℝ) => x ^ y) filter.at_top filter.at_top :=
  sorry

/-- The function `x ^ (-y)` tends to `0` at `+∞` for any positive real `y`. -/
theorem tendsto_rpow_neg_at_top {y : ℝ} (hy : 0 < y) :
    filter.tendsto (fun (x : ℝ) => x ^ (-y)) filter.at_top (nhds 0) :=
  sorry

/-- The function `x ^ (a / (b * x + c))` tends to `1` at `+∞`, for any real numbers `a`, `b`, and
`c` such that `b` is nonzero. -/
theorem tendsto_rpow_div_mul_add (a : ℝ) (b : ℝ) (c : ℝ) (hb : 0 ≠ b) :
    filter.tendsto (fun (x : ℝ) => x ^ (a / (b * x + c))) filter.at_top (nhds 1) :=
  sorry

/-- The function `x ^ (1 / x)` tends to `1` at `+∞`. -/
theorem tendsto_rpow_div : filter.tendsto (fun (x : ℝ) => x ^ (1 / x)) filter.at_top (nhds 1) :=
  sorry

/-- The function `x ^ (-1 / x)` tends to `1` at `+∞`. -/
theorem tendsto_rpow_neg_div :
    filter.tendsto (fun (x : ℝ) => x ^ (-1 / x)) filter.at_top (nhds 1) :=
  sorry

namespace nnreal

/-- The nonnegative real power function `x^y`, defined for `x : ℝ≥0` and `y : ℝ ` as the
restriction of the real power function. For `x > 0`, it is equal to `exp (y log x)`. For `x = 0`,
one sets `0 ^ 0 = 1` and `0 ^ y = 0` for `y ≠ 0`. -/
def rpow (x : nnreal) (y : ℝ) : nnreal := { val := ↑x ^ y, property := sorry }

protected instance real.has_pow : has_pow nnreal ℝ := has_pow.mk rpow

@[simp] theorem rpow_eq_pow (x : nnreal) (y : ℝ) : rpow x y = x ^ y := rfl

@[simp] theorem coe_rpow (x : nnreal) (y : ℝ) : ↑(x ^ y) = ↑x ^ y := rfl

@[simp] theorem rpow_zero (x : nnreal) : x ^ 0 = 1 := nnreal.eq (real.rpow_zero ↑x)

@[simp] theorem rpow_eq_zero_iff {x : nnreal} {y : ℝ} : x ^ y = 0 ↔ x = 0 ∧ y ≠ 0 := sorry

@[simp] theorem zero_rpow {x : ℝ} (h : x ≠ 0) : 0 ^ x = 0 := nnreal.eq (real.zero_rpow h)

@[simp] theorem rpow_one (x : nnreal) : x ^ 1 = x := nnreal.eq (real.rpow_one ↑x)

@[simp] theorem one_rpow (x : ℝ) : 1 ^ x = 1 := nnreal.eq (real.one_rpow x)

theorem rpow_add {x : nnreal} (hx : x ≠ 0) (y : ℝ) (z : ℝ) : x ^ (y + z) = x ^ y * x ^ z :=
  nnreal.eq (real.rpow_add (iff.mpr pos_iff_ne_zero hx) y z)

theorem rpow_add' (x : nnreal) {y : ℝ} {z : ℝ} (h : y + z ≠ 0) : x ^ (y + z) = x ^ y * x ^ z :=
  nnreal.eq (real.rpow_add' (subtype.property x) h)

theorem rpow_mul (x : nnreal) (y : ℝ) (z : ℝ) : x ^ (y * z) = (x ^ y) ^ z :=
  nnreal.eq (real.rpow_mul (subtype.property x) y z)

theorem rpow_neg (x : nnreal) (y : ℝ) : x ^ (-y) = (x ^ y⁻¹) :=
  nnreal.eq (real.rpow_neg (subtype.property x) y)

theorem rpow_neg_one (x : nnreal) : x ^ (-1) = (x⁻¹) := sorry

theorem rpow_sub {x : nnreal} (hx : x ≠ 0) (y : ℝ) (z : ℝ) : x ^ (y - z) = x ^ y / x ^ z :=
  nnreal.eq (real.rpow_sub (iff.mpr pos_iff_ne_zero hx) y z)

theorem rpow_sub' (x : nnreal) {y : ℝ} {z : ℝ} (h : y - z ≠ 0) : x ^ (y - z) = x ^ y / x ^ z :=
  nnreal.eq (real.rpow_sub' (subtype.property x) h)

theorem inv_rpow (x : nnreal) (y : ℝ) : x⁻¹ ^ y = (x ^ y⁻¹) :=
  nnreal.eq (real.inv_rpow (subtype.property x) y)

theorem div_rpow (x : nnreal) (y : nnreal) (z : ℝ) : (x / y) ^ z = x ^ z / y ^ z :=
  nnreal.eq (real.div_rpow (subtype.property x) (subtype.property y) z)

@[simp] theorem rpow_nat_cast (x : nnreal) (n : ℕ) : x ^ ↑n = x ^ n := sorry

theorem mul_rpow {x : nnreal} {y : nnreal} {z : ℝ} : (x * y) ^ z = x ^ z * y ^ z :=
  nnreal.eq (real.mul_rpow (subtype.property x) (subtype.property y))

theorem rpow_le_rpow {x : nnreal} {y : nnreal} {z : ℝ} (h₁ : x ≤ y) (h₂ : 0 ≤ z) : x ^ z ≤ y ^ z :=
  real.rpow_le_rpow (subtype.property x) h₁ h₂

theorem rpow_lt_rpow {x : nnreal} {y : nnreal} {z : ℝ} (h₁ : x < y) (h₂ : 0 < z) : x ^ z < y ^ z :=
  real.rpow_lt_rpow (subtype.property x) h₁ h₂

theorem rpow_lt_rpow_iff {x : nnreal} {y : nnreal} {z : ℝ} (hz : 0 < z) : x ^ z < y ^ z ↔ x < y :=
  real.rpow_lt_rpow_iff (subtype.property x) (subtype.property y) hz

theorem rpow_le_rpow_iff {x : nnreal} {y : nnreal} {z : ℝ} (hz : 0 < z) : x ^ z ≤ y ^ z ↔ x ≤ y :=
  real.rpow_le_rpow_iff (subtype.property x) (subtype.property y) hz

theorem rpow_lt_rpow_of_exponent_lt {x : nnreal} {y : ℝ} {z : ℝ} (hx : 1 < x) (hyz : y < z) :
    x ^ y < x ^ z :=
  real.rpow_lt_rpow_of_exponent_lt hx hyz

theorem rpow_le_rpow_of_exponent_le {x : nnreal} {y : ℝ} {z : ℝ} (hx : 1 ≤ x) (hyz : y ≤ z) :
    x ^ y ≤ x ^ z :=
  real.rpow_le_rpow_of_exponent_le hx hyz

theorem rpow_lt_rpow_of_exponent_gt {x : nnreal} {y : ℝ} {z : ℝ} (hx0 : 0 < x) (hx1 : x < 1)
    (hyz : z < y) : x ^ y < x ^ z :=
  real.rpow_lt_rpow_of_exponent_gt hx0 hx1 hyz

theorem rpow_le_rpow_of_exponent_ge {x : nnreal} {y : ℝ} {z : ℝ} (hx0 : 0 < x) (hx1 : x ≤ 1)
    (hyz : z ≤ y) : x ^ y ≤ x ^ z :=
  real.rpow_le_rpow_of_exponent_ge hx0 hx1 hyz

theorem rpow_lt_one {x : nnreal} {z : ℝ} (hx : 0 ≤ x) (hx1 : x < 1) (hz : 0 < z) : x ^ z < 1 :=
  real.rpow_lt_one hx hx1 hz

theorem rpow_le_one {x : nnreal} {z : ℝ} (hx2 : x ≤ 1) (hz : 0 ≤ z) : x ^ z ≤ 1 :=
  real.rpow_le_one (subtype.property x) hx2 hz

theorem rpow_lt_one_of_one_lt_of_neg {x : nnreal} {z : ℝ} (hx : 1 < x) (hz : z < 0) : x ^ z < 1 :=
  real.rpow_lt_one_of_one_lt_of_neg hx hz

theorem rpow_le_one_of_one_le_of_nonpos {x : nnreal} {z : ℝ} (hx : 1 ≤ x) (hz : z ≤ 0) :
    x ^ z ≤ 1 :=
  real.rpow_le_one_of_one_le_of_nonpos hx hz

theorem one_lt_rpow {x : nnreal} {z : ℝ} (hx : 1 < x) (hz : 0 < z) : 1 < x ^ z :=
  real.one_lt_rpow hx hz

theorem one_le_rpow {x : nnreal} {z : ℝ} (h : 1 ≤ x) (h₁ : 0 ≤ z) : 1 ≤ x ^ z :=
  real.one_le_rpow h h₁

theorem one_lt_rpow_of_pos_of_lt_one_of_neg {x : nnreal} {z : ℝ} (hx1 : 0 < x) (hx2 : x < 1)
    (hz : z < 0) : 1 < x ^ z :=
  real.one_lt_rpow_of_pos_of_lt_one_of_neg hx1 hx2 hz

theorem one_le_rpow_of_pos_of_le_one_of_nonpos {x : nnreal} {z : ℝ} (hx1 : 0 < x) (hx2 : x ≤ 1)
    (hz : z ≤ 0) : 1 ≤ x ^ z :=
  real.one_le_rpow_of_pos_of_le_one_of_nonpos hx1 hx2 hz

theorem pow_nat_rpow_nat_inv (x : nnreal) {n : ℕ} (hn : 0 < n) : (x ^ n) ^ (↑n⁻¹) = x := sorry

theorem rpow_nat_inv_pow_nat (x : nnreal) {n : ℕ} (hn : 0 < n) : (x ^ (↑n⁻¹)) ^ n = x := sorry

theorem continuous_at_rpow {x : nnreal} {y : ℝ} (h : x ≠ 0 ∨ 0 < y) :
    continuous_at (fun (p : nnreal × ℝ) => prod.fst p ^ prod.snd p) (x, y) :=
  sorry

theorem of_real_rpow_of_nonneg {x : ℝ} {y : ℝ} (hx : 0 ≤ x) :
    nnreal.of_real (x ^ y) = nnreal.of_real x ^ y :=
  sorry

end nnreal

theorem nnreal.measurable_rpow : measurable fun (p : nnreal × ℝ) => prod.fst p ^ prod.snd p := sorry

theorem measurable.nnreal_rpow {α : Type u_1} [measurable_space α] {f : α → nnreal}
    (hf : measurable f) {g : α → ℝ} (hg : measurable g) : measurable fun (a : α) => f a ^ g a :=
  id (measurable.comp nnreal.measurable_rpow (measurable.prod hf hg))

theorem nnreal.measurable_rpow_const {y : ℝ} : measurable fun (a : nnreal) => a ^ y := sorry

theorem measurable.nnreal_rpow_const {α : Type u_1} [measurable_space α] {f : α → nnreal}
    (hf : measurable f) {y : ℝ} : measurable fun (a : α) => f a ^ y :=
  measurable.nnreal_rpow hf measurable_const

theorem filter.tendsto.nnrpow {α : Type u_1} {f : filter α} {u : α → nnreal} {v : α → ℝ}
    {x : nnreal} {y : ℝ} (hx : filter.tendsto u f (nhds x)) (hy : filter.tendsto v f (nhds y))
    (h : x ≠ 0 ∨ 0 < y) : filter.tendsto (fun (a : α) => u a ^ v a) f (nhds (x ^ y)) :=
  filter.tendsto.comp (nnreal.continuous_at_rpow h) (filter.tendsto.prod_mk_nhds hx hy)

namespace nnreal

theorem continuous_at_rpow_const {x : nnreal} {y : ℝ} (h : x ≠ 0 ∨ 0 ≤ y) :
    continuous_at (fun (z : nnreal) => z ^ y) x :=
  sorry

theorem continuous_rpow_const {y : ℝ} (h : 0 ≤ y) : continuous fun (x : nnreal) => x ^ y :=
  iff.mpr continuous_iff_continuous_at fun (x : nnreal) => continuous_at_rpow_const (Or.inr h)

end nnreal

namespace ennreal

/-- The real power function `x^y` on extended nonnegative reals, defined for `x : ennreal` and
`y : ℝ` as the restriction of the real power function if `0 < x < ⊤`, and with the natural values
for `0` and `⊤` (i.e., `0 ^ x = 0` for `x > 0`, `1` for `x = 0` and `⊤` for `x < 0`, and
`⊤ ^ x = 1 / 0 ^ x`). -/
def rpow : ennreal → ℝ → ennreal := sorry

protected instance real.has_pow : has_pow ennreal ℝ := has_pow.mk rpow

@[simp] theorem rpow_eq_pow (x : ennreal) (y : ℝ) : rpow x y = x ^ y := rfl

@[simp] theorem rpow_zero {x : ennreal} : x ^ 0 = 1 := sorry

theorem top_rpow_def (y : ℝ) : ⊤ ^ y = ite (0 < y) ⊤ (ite (y = 0) 1 0) := rfl

@[simp] theorem top_rpow_of_pos {y : ℝ} (h : 0 < y) : ⊤ ^ y = ⊤ := sorry

@[simp] theorem top_rpow_of_neg {y : ℝ} (h : y < 0) : ⊤ ^ y = 0 := sorry

@[simp] theorem zero_rpow_of_pos {y : ℝ} (h : 0 < y) : 0 ^ y = 0 := sorry

@[simp] theorem zero_rpow_of_neg {y : ℝ} (h : y < 0) : 0 ^ y = ⊤ := sorry

theorem zero_rpow_def (y : ℝ) : 0 ^ y = ite (0 < y) 0 (ite (y = 0) 1 ⊤) := sorry

theorem coe_rpow_of_ne_zero {x : nnreal} (h : x ≠ 0) (y : ℝ) : ↑x ^ y = ↑(x ^ y) := sorry

theorem coe_rpow_of_nonneg (x : nnreal) {y : ℝ} (h : 0 ≤ y) : ↑x ^ y = ↑(x ^ y) := sorry

theorem coe_rpow_def (x : nnreal) (y : ℝ) : ↑x ^ y = ite (x = 0 ∧ y < 0) ⊤ ↑(x ^ y) := rfl

@[simp] theorem rpow_one (x : ennreal) : x ^ 1 = x := sorry

@[simp] theorem one_rpow (x : ℝ) : 1 ^ x = 1 := sorry

@[simp] theorem rpow_eq_zero_iff {x : ennreal} {y : ℝ} :
    x ^ y = 0 ↔ x = 0 ∧ 0 < y ∨ x = ⊤ ∧ y < 0 :=
  sorry

@[simp] theorem rpow_eq_top_iff {x : ennreal} {y : ℝ} : x ^ y = ⊤ ↔ x = 0 ∧ y < 0 ∨ x = ⊤ ∧ 0 < y :=
  sorry

theorem rpow_eq_top_iff_of_pos {x : ennreal} {y : ℝ} (hy : 0 < y) : x ^ y = ⊤ ↔ x = ⊤ := sorry

theorem rpow_eq_top_of_nonneg (x : ennreal) {y : ℝ} (hy0 : 0 ≤ y) : x ^ y = ⊤ → x = ⊤ := sorry

theorem rpow_ne_top_of_nonneg {x : ennreal} {y : ℝ} (hy0 : 0 ≤ y) (h : x ≠ ⊤) : x ^ y ≠ ⊤ :=
  mt (rpow_eq_top_of_nonneg x hy0) h

theorem rpow_lt_top_of_nonneg {x : ennreal} {y : ℝ} (hy0 : 0 ≤ y) (h : x ≠ ⊤) : x ^ y < ⊤ :=
  iff.mpr ennreal.lt_top_iff_ne_top (rpow_ne_top_of_nonneg hy0 h)

theorem rpow_add {x : ennreal} (y : ℝ) (z : ℝ) (hx : x ≠ 0) (h'x : x ≠ ⊤) :
    x ^ (y + z) = x ^ y * x ^ z :=
  sorry

theorem rpow_neg (x : ennreal) (y : ℝ) : x ^ (-y) = (x ^ y⁻¹) := sorry

theorem rpow_neg_one (x : ennreal) : x ^ (-1) = (x⁻¹) := sorry

theorem rpow_mul (x : ennreal) (y : ℝ) (z : ℝ) : x ^ (y * z) = (x ^ y) ^ z := sorry

@[simp] theorem rpow_nat_cast (x : ennreal) (n : ℕ) : x ^ ↑n = x ^ n := sorry

theorem coe_mul_rpow (x : nnreal) (y : nnreal) (z : ℝ) : (↑x * ↑y) ^ z = ↑x ^ z * ↑y ^ z := sorry

theorem mul_rpow_of_ne_top {x : ennreal} {y : ennreal} (hx : x ≠ ⊤) (hy : y ≠ ⊤) (z : ℝ) :
    (x * y) ^ z = x ^ z * y ^ z :=
  sorry

theorem mul_rpow_of_ne_zero {x : ennreal} {y : ennreal} (hx : x ≠ 0) (hy : y ≠ 0) (z : ℝ) :
    (x * y) ^ z = x ^ z * y ^ z :=
  sorry

theorem mul_rpow_of_nonneg (x : ennreal) (y : ennreal) {z : ℝ} (hz : 0 ≤ z) :
    (x * y) ^ z = x ^ z * y ^ z :=
  sorry

theorem inv_rpow_of_pos {x : ennreal} {y : ℝ} (hy : 0 < y) : x⁻¹ ^ y = (x ^ y⁻¹) := sorry

theorem div_rpow_of_nonneg (x : ennreal) (y : ennreal) {z : ℝ} (hz : 0 ≤ z) :
    (x / y) ^ z = x ^ z / y ^ z :=
  sorry

theorem rpow_le_rpow {x : ennreal} {y : ennreal} {z : ℝ} (h₁ : x ≤ y) (h₂ : 0 ≤ z) :
    x ^ z ≤ y ^ z :=
  sorry

theorem rpow_lt_rpow {x : ennreal} {y : ennreal} {z : ℝ} (h₁ : x < y) (h₂ : 0 < z) :
    x ^ z < y ^ z :=
  sorry

theorem rpow_le_rpow_iff {x : ennreal} {y : ennreal} {z : ℝ} (hz : 0 < z) : x ^ z ≤ y ^ z ↔ x ≤ y :=
  sorry

theorem rpow_lt_rpow_iff {x : ennreal} {y : ennreal} {z : ℝ} (hz : 0 < z) : x ^ z < y ^ z ↔ x < y :=
  sorry

theorem le_rpow_one_div_iff {x : ennreal} {y : ennreal} {z : ℝ} (hz : 0 < z) :
    x ≤ y ^ (1 / z) ↔ x ^ z ≤ y :=
  sorry

theorem lt_rpow_one_div_iff {x : ennreal} {y : ennreal} {z : ℝ} (hz : 0 < z) :
    x < y ^ (1 / z) ↔ x ^ z < y :=
  sorry

theorem rpow_lt_rpow_of_exponent_lt {x : ennreal} {y : ℝ} {z : ℝ} (hx : 1 < x) (hx' : x ≠ ⊤)
    (hyz : y < z) : x ^ y < x ^ z :=
  sorry

theorem rpow_le_rpow_of_exponent_le {x : ennreal} {y : ℝ} {z : ℝ} (hx : 1 ≤ x) (hyz : y ≤ z) :
    x ^ y ≤ x ^ z :=
  sorry

theorem rpow_lt_rpow_of_exponent_gt {x : ennreal} {y : ℝ} {z : ℝ} (hx0 : 0 < x) (hx1 : x < 1)
    (hyz : z < y) : x ^ y < x ^ z :=
  sorry

theorem rpow_le_rpow_of_exponent_ge {x : ennreal} {y : ℝ} {z : ℝ} (hx1 : x ≤ 1) (hyz : z ≤ y) :
    x ^ y ≤ x ^ z :=
  sorry

theorem rpow_le_self_of_le_one {x : ennreal} {z : ℝ} (hx : x ≤ 1) (h_one_le : 1 ≤ z) : x ^ z ≤ x :=
  eq.mpr (id (congr_arg (LessEq (x ^ z)) (Eq._oldrec (Eq.refl x) (Eq.symm (rpow_one x)))))
    (rpow_le_rpow_of_exponent_ge hx h_one_le)

theorem le_rpow_self_of_one_le {x : ennreal} {z : ℝ} (hx : 1 ≤ x) (h_one_le : 1 ≤ z) : x ≤ x ^ z :=
  eq.mpr (id (congr_fun (congr_arg LessEq (Eq._oldrec (Eq.refl x) (Eq.symm (rpow_one x)))) (x ^ z)))
    (rpow_le_rpow_of_exponent_le hx h_one_le)

theorem rpow_pos_of_nonneg {p : ℝ} {x : ennreal} (hx_pos : 0 < x) (hp_nonneg : 0 ≤ p) : 0 < x ^ p :=
  sorry

theorem rpow_pos {p : ℝ} {x : ennreal} (hx_pos : 0 < x) (hx_ne_top : x ≠ ⊤) : 0 < x ^ p := sorry

theorem rpow_lt_one {x : ennreal} {z : ℝ} (hx : x < 1) (hz : 0 < z) : x ^ z < 1 := sorry

theorem rpow_le_one {x : ennreal} {z : ℝ} (hx : x ≤ 1) (hz : 0 ≤ z) : x ^ z ≤ 1 := sorry

theorem rpow_lt_one_of_one_lt_of_neg {x : ennreal} {z : ℝ} (hx : 1 < x) (hz : z < 0) : x ^ z < 1 :=
  sorry

theorem rpow_le_one_of_one_le_of_neg {x : ennreal} {z : ℝ} (hx : 1 ≤ x) (hz : z < 0) : x ^ z ≤ 1 :=
  sorry

theorem one_lt_rpow {x : ennreal} {z : ℝ} (hx : 1 < x) (hz : 0 < z) : 1 < x ^ z := sorry

theorem one_le_rpow {x : ennreal} {z : ℝ} (hx : 1 ≤ x) (hz : 0 < z) : 1 ≤ x ^ z := sorry

theorem one_lt_rpow_of_pos_of_lt_one_of_neg {x : ennreal} {z : ℝ} (hx1 : 0 < x) (hx2 : x < 1)
    (hz : z < 0) : 1 < x ^ z :=
  sorry

theorem one_le_rpow_of_pos_of_le_one_of_neg {x : ennreal} {z : ℝ} (hx1 : 0 < x) (hx2 : x ≤ 1)
    (hz : z < 0) : 1 ≤ x ^ z :=
  sorry

theorem to_nnreal_rpow (x : ennreal) (z : ℝ) :
    ennreal.to_nnreal x ^ z = ennreal.to_nnreal (x ^ z) :=
  sorry

theorem to_real_rpow (x : ennreal) (z : ℝ) : ennreal.to_real x ^ z = ennreal.to_real (x ^ z) :=
  sorry

theorem rpow_left_injective {x : ℝ} (hx : x ≠ 0) : function.injective fun (y : ennreal) => y ^ x :=
  sorry

theorem rpow_left_surjective {x : ℝ} (hx : x ≠ 0) :
    function.surjective fun (y : ennreal) => y ^ x :=
  sorry

theorem rpow_left_bijective {x : ℝ} (hx : x ≠ 0) : function.bijective fun (y : ennreal) => y ^ x :=
  { left := rpow_left_injective hx, right := rpow_left_surjective hx }

theorem rpow_left_monotone_of_nonneg {x : ℝ} (hx : 0 ≤ x) : monotone fun (y : ennreal) => y ^ x :=
  fun (y z : ennreal) (hyz : y ≤ z) => rpow_le_rpow hyz hx

theorem rpow_left_strict_mono_of_pos {x : ℝ} (hx : 0 < x) :
    strict_mono fun (y : ennreal) => y ^ x :=
  fun (y z : ennreal) (hyz : y < z) => rpow_lt_rpow hyz hx

end ennreal

theorem ennreal.measurable_rpow : measurable fun (p : ennreal × ℝ) => prod.fst p ^ prod.snd p :=
  sorry

theorem measurable.ennreal_rpow {α : Type u_1} [measurable_space α] {f : α → ennreal}
    (hf : measurable f) {g : α → ℝ} (hg : measurable g) : measurable fun (a : α) => f a ^ g a :=
  id (measurable.comp ennreal.measurable_rpow (measurable.prod hf hg))

theorem ennreal.measurable_rpow_const {y : ℝ} : measurable fun (a : ennreal) => a ^ y :=
  id (measurable.comp ennreal.measurable_rpow (measurable.prod measurable_id (id measurable_const)))

theorem measurable.ennreal_rpow_const {α : Type u_1} [measurable_space α] {f : α → ennreal}
    (hf : measurable f) {y : ℝ} : measurable fun (a : α) => f a ^ y :=
  measurable.ennreal_rpow hf measurable_const

theorem ae_measurable.ennreal_rpow_const {α : Type u_1} [measurable_space α] {f : α → ennreal}
    {μ : measure_theory.measure α} (hf : ae_measurable f) {y : ℝ} :
    ae_measurable fun (a : α) => f a ^ y :=
  measurable.comp_ae_measurable ennreal.measurable_rpow_const hf

end Mathlib


/-
Copyright (c) 2021 Noam Atar. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Noam Atar
-/
import order.basic
import order.ideal
import order.pfilter

/-!
# Prime ideals

## Main definitions

Throughout this file, `P` is at least a preorder, but some sections require more
structure, such as a bottom element, a top element, or a join-semilattice structure.

- `order.ideal.prime_pair`: A pair of an `ideal` and a `pfilter` which form a partition of `P`.
  This is useful as giving the data of a prime ideal is the same as giving the data of a prime
  filter.
- `order.ideal.is_prime`: a predicate for prime ideals. Dual to the notion of a prime filter.
- `order.pfilter.is_prime`: a predicate for prime filters. Dual to the notion of a prime ideal.

## References

- <https://en.wikipedia.org/wiki/Ideal_(order_theory)>

## Tags

ideal, prime

-/

open order.pfilter

namespace order

variables {P : Type*}

namespace ideal

/-- A pair of an `ideal` and a `pfilter` which form a partition of `P`.
-/
@[nolint has_inhabited_instance]
structure prime_pair (P : Type*) [preorder P] :=
(I            : ideal P)
(F            : pfilter P)
(is_compl_I_F : is_compl (I : set P) F)

namespace prime_pair

variables [preorder P] (IF : prime_pair P)

lemma compl_I_eq_F : (IF.I : set P)ᶜ = IF.F := IF.is_compl_I_F.compl_eq
lemma compl_F_eq_I : (IF.F : set P)ᶜ = IF.I := IF.is_compl_I_F.eq_compl.symm

lemma I_is_proper : is_proper IF.I :=
begin
  cases IF.F.nonempty,
  apply is_proper_of_not_mem (_ : w ∉ IF.I),
  rwa ← IF.compl_I_eq_F at h,
end

lemma disjoint : disjoint (IF.I : set P) IF.F := IF.is_compl_I_F.disjoint

lemma I_union_F : (IF.I : set P) ∪ IF.F = set.univ := IF.is_compl_I_F.sup_eq_top
lemma F_union_I : (IF.F : set P) ∪ IF.I = set.univ := IF.is_compl_I_F.symm.sup_eq_top

end prime_pair

/-- An ideal `I` is prime if its complement is a filter.
-/
@[mk_iff] class is_prime [preorder P] (I : ideal P) extends is_proper I : Prop :=
(compl_filter : is_pfilter (I : set P)ᶜ)

section preorder

variable [preorder P]

/-- Create an element of type `order.ideal.prime_pair` from an ideal satisfying the predicate
`order.ideal.is_prime`. -/
def is_prime.to_prime_pair {I : ideal P} (h : is_prime I) : prime_pair P :=
{ I            := I,
  F            := h.compl_filter.to_pfilter,
  is_compl_I_F := is_compl_compl }

lemma prime_pair.I_is_prime (IF : prime_pair P) : is_prime IF.I :=
{ compl_filter := by { rw IF.compl_I_eq_F, exact IF.F.is_pfilter },
  ..IF.I_is_proper }

end preorder

section semilattice_inf

variables [semilattice_inf P] {x y : P} {I : ideal P}

lemma is_prime.mem_or_mem (hI : is_prime I) {x y : P} : x ⊓ y ∈ I → x ∈ I ∨ y ∈ I :=
begin
  contrapose!,
  let F := hI.compl_filter.to_pfilter,
  show x ∈ F ∧ y ∈ F → x ⊓ y ∈ F,
  exact λ h, inf_mem _ _ h.1 h.2,
end

lemma is_prime.of_mem_or_mem [is_proper I] (hI : ∀ {x y : P}, x ⊓ y ∈ I → x ∈ I ∨ y ∈ I) :
  is_prime I :=
begin
  rw is_prime_iff,
  use ‹_›,
  apply is_pfilter.of_def,
  { exact set.nonempty_compl.2 (I.is_proper_iff.1 ‹_›) },
  { intros x _ y _,
    refine ⟨x ⊓ y, _, inf_le_left, inf_le_right⟩,
    have := mt hI,
    tauto! },
  { exact @mem_compl_of_ge _ _ _ }
end

lemma is_prime_iff_mem_or_mem [is_proper I] : is_prime I ↔ ∀ {x y : P}, x ⊓ y ∈ I → x ∈ I ∨ y ∈ I :=
⟨is_prime.mem_or_mem, is_prime.of_mem_or_mem⟩

end semilattice_inf

section distrib_lattice

variables [distrib_lattice P] {I : ideal P}

@[priority 100]
instance is_maximal.is_prime [is_maximal I] : is_prime I :=
begin
  rw is_prime_iff_mem_or_mem,
  intros x y,
  contrapose!,
  rintro ⟨hx, hynI⟩ hxy,
  apply hynI,
  let J := I ⊔ principal x,
  have hJuniv : (J : set P) = set.univ :=
    is_maximal.maximal_proper (lt_sup_principal_of_not_mem ‹_›),
  have hyJ : y ∈ ↑J := set.eq_univ_iff_forall.mp hJuniv y,
  rw coe_sup_eq at hyJ,
  rcases hyJ with ⟨a, ha, b, hb, hy⟩,
  rw hy,
  apply sup_mem _ _ ha,
  refine I.mem_of_le (le_inf hb _) hxy,
  rw hy,
  exact le_sup_right
end

end distrib_lattice

end ideal

namespace pfilter

variable [preorder P]

/-- A filter `F` is prime if its complement is an ideal.
-/
@[mk_iff] class is_prime (F : pfilter P) : Prop :=
(compl_ideal : is_ideal (F : set P)ᶜ)

/-- Create an element of type `order.ideal.prime_pair` from a filter satisfying the predicate
`order.pfilter.is_prime`. -/
def is_prime.to_prime_pair {F : pfilter P} (h : is_prime F) : ideal.prime_pair P :=
{ I            := h.compl_ideal.to_ideal,
  F            := F,
  is_compl_I_F := is_compl_compl.symm }

lemma _root_.order.ideal.prime_pair.F_is_prime (IF : ideal.prime_pair P) : is_prime IF.F :=
{ compl_ideal := by { rw IF.compl_F_eq_I, exact IF.I.is_ideal } }

end pfilter

end order


/-
Copyright (c) 2021 Jordan Brown, Thomas Browning, Patrick Lutz. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jordan Brown, Thomas Browning, Patrick Lutz

! This file was ported from Lean 3 source module group_theory.commutator
! leanprover-community/mathlib commit 0a0ec35061ed9960bf0e7ffb0335f44447b58977
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Algebra.Group.Commutator
import Mathlib.Data.Bracket
import Mathlib.GroupTheory.Subgroup.Finite
import Mathlib.Tactic.Group

/-!
# Commutators of Subgroups

If `G` is a group and `H₁ H₂ : Subgroup G` then the commutator `⁅H₁, H₂⁆ : Subgroup G`
is the subgroup of `G` generated by the commutators `h₁ * h₂ * h₁⁻¹ * h₂⁻¹`.

## Main definitions

* `⁅g₁, g₂⁆` : the commutator of the elements `g₁` and `g₂`
    (defined by `commutatorElement` elsewhere).
* `⁅H₁, H₂⁆` : the commutator of the subgroups `H₁` and `H₂`.
-/

variable {G G' F : Type _} [Group G] [Group G'] [MonoidHomClass F G G'] (f : F) {g₁ g₂ g₃ g : G}

theorem commutatorElement_eq_one_iff_mul_comm : ⁅g₁, g₂⁆ = 1 ↔ g₁ * g₂ = g₂ * g₁ := by
  rw [commutatorElement_def, mul_inv_eq_one, mul_inv_eq_iff_eq_mul]
#align commutator_element_eq_one_iff_mul_comm commutatorElement_eq_one_iff_mul_comm

theorem commutatorElement_eq_one_iff_commute : ⁅g₁, g₂⁆ = 1 ↔ Commute g₁ g₂ :=
  commutatorElement_eq_one_iff_mul_comm
#align commutator_element_eq_one_iff_commute commutatorElement_eq_one_iff_commute

theorem Commute.commutator_eq (h : Commute g₁ g₂) : ⁅g₁, g₂⁆ = 1 :=
  commutatorElement_eq_one_iff_commute.mpr h
#align commute.commutator_eq Commute.commutator_eq

variable (g₁ g₂ g₃ g)

@[simp]
theorem commutatorElement_one_right : ⁅g, (1 : G)⁆ = 1 :=
  (Commute.one_right g).commutator_eq
#align commutator_element_one_right commutatorElement_one_right

@[simp]
theorem commutatorElement_one_left : ⁅(1 : G), g⁆ = 1 :=
  (Commute.one_left g).commutator_eq
#align commutator_element_one_left commutatorElement_one_left

@[simp]
theorem commutatorElement_self : ⁅g, g⁆ = 1 :=
  (Commute.refl g).commutator_eq
#align commutator_element_self commutatorElement_self

@[simp]
theorem commutatorElement_inv : ⁅g₁, g₂⁆⁻¹ = ⁅g₂, g₁⁆ := by
  simp_rw [commutatorElement_def, mul_inv_rev, inv_inv, mul_assoc]
#align commutator_element_inv commutatorElement_inv

theorem map_commutatorElement : (f ⁅g₁, g₂⁆ : G') = ⁅f g₁, f g₂⁆ := by
  simp_rw [commutatorElement_def, map_mul f, map_inv f]
#align map_commutator_element map_commutatorElement

theorem conjugate_commutatorElement : g₃ * ⁅g₁, g₂⁆ * g₃⁻¹ = ⁅g₃ * g₁ * g₃⁻¹, g₃ * g₂ * g₃⁻¹⁆ :=
  map_commutatorElement (MulAut.conj g₃).toMonoidHom g₁ g₂
#align conjugate_commutator_element conjugate_commutatorElement

namespace Subgroup

/-- The commutator of two subgroups `H₁` and `H₂`. -/
instance commutator : Bracket (Subgroup G) (Subgroup G) :=
  ⟨fun H₁ H₂ => closure { g | ∃ g₁ ∈ H₁, ∃ g₂ ∈ H₂, ⁅g₁, g₂⁆ = g }⟩
#align subgroup.commutator Subgroup.commutator

theorem commutator_def (H₁ H₂ : Subgroup G) :
    ⁅H₁, H₂⁆ = closure { g | ∃ g₁ ∈ H₁, ∃ g₂ ∈ H₂, ⁅g₁, g₂⁆ = g } :=
  rfl
#align subgroup.commutator_def Subgroup.commutator_def

variable {g₁ g₂ g₃} {H₁ H₂ H₃ K₁ K₂ : Subgroup G}

theorem commutator_mem_commutator (h₁ : g₁ ∈ H₁) (h₂ : g₂ ∈ H₂) : ⁅g₁, g₂⁆ ∈ ⁅H₁, H₂⁆ :=
  subset_closure ⟨g₁, h₁, g₂, h₂, rfl⟩
#align subgroup.commutator_mem_commutator Subgroup.commutator_mem_commutator

theorem commutator_le : ⁅H₁, H₂⁆ ≤ H₃ ↔ ∀ g₁ ∈ H₁, ∀ g₂ ∈ H₂, ⁅g₁, g₂⁆ ∈ H₃ :=
  H₃.closure_le.trans
    ⟨fun h a b c d => h ⟨a, b, c, d, rfl⟩, fun h _g ⟨a, b, c, d, h_eq⟩ => h_eq ▸ h a b c d⟩
#align subgroup.commutator_le Subgroup.commutator_le

theorem commutator_mono (h₁ : H₁ ≤ K₁) (h₂ : H₂ ≤ K₂) : ⁅H₁, H₂⁆ ≤ ⁅K₁, K₂⁆ :=
  commutator_le.mpr fun _g₁ hg₁ _g₂ hg₂ => commutator_mem_commutator (h₁ hg₁) (h₂ hg₂)
#align subgroup.commutator_mono Subgroup.commutator_mono

theorem commutator_eq_bot_iff_le_centralizer : ⁅H₁, H₂⁆ = ⊥ ↔ H₁ ≤ H₂.centralizer := by
  rw [eq_bot_iff, commutator_le]
  refine'
    forall_congr' fun p => forall_congr' fun _hp => forall_congr' fun q => forall_congr' fun hq => _
  rw [mem_bot, commutatorElement_eq_one_iff_mul_comm, eq_comm]
#align subgroup.commutator_eq_bot_iff_le_centralizer Subgroup.commutator_eq_bot_iff_le_centralizer

/-- **The Three Subgroups Lemma** (via the Hall-Witt identity) -/
theorem commutator_commutator_eq_bot_of_rotate (h1 : ⁅⁅H₂, H₃⁆, H₁⁆ = ⊥) (h2 : ⁅⁅H₃, H₁⁆, H₂⁆ = ⊥) :
    ⁅⁅H₁, H₂⁆, H₃⁆ = ⊥ := by
  simp_rw [commutator_eq_bot_iff_le_centralizer, commutator_le,
    mem_centralizer_iff_commutator_eq_one, ← commutatorElement_def] at h1 h2⊢
  intro x hx y hy z hz
  trans x * z * ⁅y, ⁅z⁻¹, x⁻¹⁆⁆⁻¹ * z⁻¹ * y * ⁅x⁻¹, ⁅y⁻¹, z⁆⁆⁻¹ * y⁻¹ * x⁻¹
  · group
  · rw [h1 _ (H₂.inv_mem hy) _ hz _ (H₁.inv_mem hx), h2 _ (H₃.inv_mem hz) _ (H₁.inv_mem hx) _ hy]
    group
#align subgroup.commutator_commutator_eq_bot_of_rotate Subgroup.commutator_commutator_eq_bot_of_rotate

variable (H₁ H₂)

theorem commutator_comm_le : ⁅H₁, H₂⁆ ≤ ⁅H₂, H₁⁆ :=
  commutator_le.mpr fun g₁ h₁ g₂ h₂ =>
    commutatorElement_inv g₂ g₁ ▸ ⁅H₂, H₁⁆.inv_mem_iff.mpr (commutator_mem_commutator h₂ h₁)
#align subgroup.commutator_comm_le Subgroup.commutator_comm_le

theorem commutator_comm : ⁅H₁, H₂⁆ = ⁅H₂, H₁⁆ :=
  le_antisymm (commutator_comm_le H₁ H₂) (commutator_comm_le H₂ H₁)
#align subgroup.commutator_comm Subgroup.commutator_comm

section Normal

instance commutator_normal [h₁ : H₁.Normal] [h₂ : H₂.Normal] : Normal ⁅H₁, H₂⁆ := by
  let base : Set G := { x | ∃ g₁ ∈ H₁, ∃ g₂ ∈ H₂, ⁅g₁, g₂⁆ = x }
  change (closure base).Normal
  suffices h_base : base = Group.conjugatesOfSet base
  · rw [h_base]
    exact Subgroup.normalClosure_normal
  refine' Set.Subset.antisymm Group.subset_conjugatesOfSet fun a h => _
  simp_rw [Group.mem_conjugatesOfSet_iff, isConj_iff] at h
  rcases h with ⟨b, ⟨c, hc, e, he, rfl⟩, d, rfl⟩
  exact ⟨_, h₁.conj_mem c hc d, _, h₂.conj_mem e he d, (conjugate_commutatorElement c e d).symm⟩
#align subgroup.commutator_normal Subgroup.commutator_normal

theorem commutator_def' [H₁.Normal] [H₂.Normal] :
    ⁅H₁, H₂⁆ = normalClosure { g | ∃ g₁ ∈ H₁, ∃ g₂ ∈ H₂, ⁅g₁, g₂⁆ = g } :=
  le_antisymm closure_le_normalClosure (normalClosure_le_normal subset_closure)
#align subgroup.commutator_def' Subgroup.commutator_def'

theorem commutator_le_right [h : H₂.Normal] : ⁅H₁, H₂⁆ ≤ H₂ :=
  commutator_le.mpr fun g₁ _h₁ g₂ h₂ => H₂.mul_mem (h.conj_mem g₂ h₂ g₁) (H₂.inv_mem h₂)
#align subgroup.commutator_le_right Subgroup.commutator_le_right

theorem commutator_le_left [H₁.Normal] : ⁅H₁, H₂⁆ ≤ H₁ :=
  commutator_comm H₂ H₁ ▸ commutator_le_right H₂ H₁
#align subgroup.commutator_le_left Subgroup.commutator_le_left

@[simp]
theorem commutator_bot_left : ⁅(⊥ : Subgroup G), H₁⁆ = ⊥ :=
  le_bot_iff.mp (commutator_le_left ⊥ H₁)
#align subgroup.commutator_bot_left Subgroup.commutator_bot_left

@[simp]
theorem commutator_bot_right : ⁅H₁, ⊥⁆ = (⊥ : Subgroup G) :=
  le_bot_iff.mp (commutator_le_right H₁ ⊥)
#align subgroup.commutator_bot_right Subgroup.commutator_bot_right

theorem commutator_le_inf [Normal H₁] [Normal H₂] : ⁅H₁, H₂⁆ ≤ H₁ ⊓ H₂ :=
  le_inf (commutator_le_left H₁ H₂) (commutator_le_right H₁ H₂)
#align subgroup.commutator_le_inf Subgroup.commutator_le_inf

end Normal

theorem map_commutator (f : G →* G') : map f ⁅H₁, H₂⁆ = ⁅map f H₁, map f H₂⁆ := by
  simp_rw [le_antisymm_iff, map_le_iff_le_comap, commutator_le, mem_comap, map_commutatorElement]
  constructor
  · intro p hp q hq
    exact commutator_mem_commutator (mem_map_of_mem _ hp) (mem_map_of_mem _ hq)
  · rintro _ ⟨p, hp, rfl⟩ _ ⟨q, hq, rfl⟩
    rw [← map_commutatorElement]
    exact mem_map_of_mem _ (commutator_mem_commutator hp hq)
#align subgroup.map_commutator Subgroup.map_commutator

variable {H₁ H₂}

theorem commutator_le_map_commutator {f : G →* G'} {K₁ K₂ : Subgroup G'} (h₁ : K₁ ≤ H₁.map f)
    (h₂ : K₂ ≤ H₂.map f) : ⁅K₁, K₂⁆ ≤ ⁅H₁, H₂⁆.map f :=
  (commutator_mono h₁ h₂).trans (ge_of_eq (map_commutator H₁ H₂ f))
#align subgroup.commutator_le_map_commutator Subgroup.commutator_le_map_commutator

variable (H₁ H₂)

instance commutator_characteristic [h₁ : Characteristic H₁] [h₂ : Characteristic H₂] :
    Characteristic ⁅H₁, H₂⁆ :=
  characteristic_iff_le_map.mpr fun ϕ =>
    commutator_le_map_commutator (characteristic_iff_le_map.mp h₁ ϕ)
      (characteristic_iff_le_map.mp h₂ ϕ)
#align subgroup.commutator_characteristic Subgroup.commutator_characteristic

theorem commutator_prod_prod (K₁ K₂ : Subgroup G') :
    ⁅H₁.prod K₁, H₂.prod K₂⁆ = ⁅H₁, H₂⁆.prod ⁅K₁, K₂⁆ := by
  apply le_antisymm
  · rw [commutator_le]
    rintro ⟨p₁, p₂⟩ ⟨hp₁, hp₂⟩ ⟨q₁, q₂⟩ ⟨hq₁, hq₂⟩
    exact ⟨commutator_mem_commutator hp₁ hq₁, commutator_mem_commutator hp₂ hq₂⟩
  · rw [prod_le_iff]
    constructor <;>
      · rw [map_commutator]
        apply commutator_mono <;>
          simp [le_prod_iff, map_map, MonoidHom.fst_comp_inl, MonoidHom.snd_comp_inl,
            MonoidHom.fst_comp_inr, MonoidHom.snd_comp_inr]
#align subgroup.commutator_prod_prod Subgroup.commutator_prod_prod

/-- The commutator of direct product is contained in the direct product of the commutators.

See `commutator_pi_pi_of_finite` for equality given `Fintype η`.
-/
theorem commutator_pi_pi_le {η : Type _} {Gs : η → Type _} [∀ i, Group (Gs i)]
    (H K : ∀ i, Subgroup (Gs i)) :
    ⁅Subgroup.pi Set.univ H, Subgroup.pi Set.univ K⁆ ≤ Subgroup.pi Set.univ fun i => ⁅H i, K i⁆ :=
  commutator_le.mpr fun _p hp _q hq i hi => commutator_mem_commutator (hp i hi) (hq i hi)
#align subgroup.commutator_pi_pi_le Subgroup.commutator_pi_pi_le

/-- The commutator of a finite direct product is contained in the direct product of the commutators.
-/
theorem commutator_pi_pi_of_finite {η : Type _} [Finite η] {Gs : η → Type _} [∀ i, Group (Gs i)]
    (H K : ∀ i, Subgroup (Gs i)) :
    ⁅Subgroup.pi Set.univ H, Subgroup.pi Set.univ K⁆ = Subgroup.pi Set.univ fun i => ⁅H i, K i⁆ :=
  by
  classical
    apply le_antisymm (commutator_pi_pi_le H K)
    · rw [pi_le_iff]
      intro i hi
      rw [map_commutator]
      apply commutator_mono <;>
        · rw [le_pi_iff]
          intro j _hj
          rintro _ ⟨_, ⟨x, hx, rfl⟩, rfl⟩
          by_cases h : j = i
          · subst h
            simpa using hx
          · simp [h, one_mem]
#align subgroup.commutator_pi_pi_of_finite Subgroup.commutator_pi_pi_of_finite

end Subgroup

variable (G)

/-- The set of commutator elements `⁅g₁, g₂⁆` in `G`. -/
def commutatorSet : Set G :=
  { g | ∃ g₁ g₂ : G, ⁅g₁, g₂⁆ = g }
#align commutator_set commutatorSet

theorem commutatorSet_def : commutatorSet G = { g | ∃ g₁ g₂ : G, ⁅g₁, g₂⁆ = g } :=
  rfl
#align commutator_set_def commutatorSet_def

theorem one_mem_commutatorSet : (1 : G) ∈ commutatorSet G :=
  ⟨1, 1, commutatorElement_self 1⟩
#align one_mem_commutator_set one_mem_commutatorSet

instance : Nonempty (commutatorSet G) :=
  ⟨⟨1, one_mem_commutatorSet G⟩⟩

variable {G g}

theorem mem_commutatorSet_iff : g ∈ commutatorSet G ↔ ∃ g₁ g₂ : G, ⁅g₁, g₂⁆ = g :=
  Iff.rfl
#align mem_commutator_set_iff mem_commutatorSet_iff

theorem commutator_mem_commutatorSet : ⁅g₁, g₂⁆ ∈ commutatorSet G :=
  ⟨g₁, g₂, rfl⟩
#align commutator_mem_commutator_set commutator_mem_commutatorSet