/-
Copyright (c) 2014 Jeremy Avigad. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jeremy Avigad, Mario Carneiro, Yaël Dillies

! This file was ported from Lean 3 source module order.monotone.basic
! leanprover-community/mathlib commit ac5a7cec422c3909db52e13dde2e729657d19b0e
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Init.Data.Int.Order
import Mathlib.Order.Compare
import Mathlib.Order.Max
import Mathlib.Order.RelClasses
import Mathlib.Tactic.Choose
import Mathlib.Tactic.SimpRw
import Mathlib.Tactic.Coe

/-!
# Monotonicity

This file defines (strictly) monotone/antitone functions. Contrary to standard mathematical usage,
"monotone"/"mono" here means "increasing", not "increasing or decreasing". We use "antitone"/"anti"
to mean "decreasing".

## Definitions

* `Monotone f`: A function `f` between two preorders is monotone if `a ≤ b` implies `f a ≤ f b`.
* `Antitone f`: A function `f` between two preorders is antitone if `a ≤ b` implies `f b ≤ f a`.
* `MonotoneOn f s`: Same as `Monotone f`, but for all `a, b ∈ s`.
* `AntitoneoN f s`: Same as `Antitone f`, but for all `a, b ∈ s`.
* `StrictMono f` : A function `f` between two preorders is strictly monotone if `a < b` implies
  `f a < f b`.
* `StrictAnti f` : A function `f` between two preorders is strictly antitone if `a < b` implies
  `f b < f a`.
* `StrictMonoOn f s`: Same as `StrictMono f`, but for all `a, b ∈ s`.
* `StrictAntiOn f s`: Same as `StrictAnti f`, but for all `a, b ∈ s`.

## Main theorems

* `monotone_nat_of_le_succ`, `monotone_int_of_le_succ`: If `f : ℕ → α` or `f : ℤ → α` and
  `f n ≤ f (n + 1)` for all `n`, then `f` is monotone.
* `antitone_nat_of_succ_le`, `antitone_int_of_succ_le`: If `f : ℕ → α` or `f : ℤ → α` and
  `f (n + 1) ≤ f n` for all `n`, then `f` is antitone.
* `strictMono_nat_of_lt_succ`, `strictMono_int_of_lt_succ`: If `f : ℕ → α` or `f : ℤ → α` and
  `f n < f (n + 1)` for all `n`, then `f` is strictly monotone.
* `strictAnti_nat_of_succ_lt`, `strictAnti_int_of_succ_lt`: If `f : ℕ → α` or `f : ℤ → α` and
  `f (n + 1) < f n` for all `n`, then `f` is strictly antitone.

## Implementation notes

Some of these definitions used to only require `LE α` or `LT α`. The advantage of this is
unclear and it led to slight elaboration issues. Now, everything requires `Preorder α` and seems to
work fine. Related Zulip discussion:
https://leanprover.zulipchat.com/#narrow/stream/113488-general/topic/Order.20diamond/near/254353352.

## TODO

The above theorems are also true in `ℕ+`, `Fin n`... To make that work, we need `SuccOrder α`
and `SuccArchmidean α`.

## Tags

monotone, strictly monotone, antitone, strictly antitone, increasing, strictly increasing,
decreasing, strictly decreasing
-/

open Function OrderDual

universe u v w

variable {α : Type u} {β : Type v} {γ : Type w} {δ : Type _} {r : α → α → Prop}

section MonotoneDef

variable [Preorder α] [Preorder β]

/-- A function `f` is monotone if `a ≤ b` implies `f a ≤ f b`. -/
def Monotone (f : α → β) : Prop :=
  ∀ ⦃a b⦄, a ≤ b → f a ≤ f b
#align monotone Monotone

/-- A function `f` is antitone if `a ≤ b` implies `f b ≤ f a`. -/
def Antitone (f : α → β) : Prop :=
  ∀ ⦃a b⦄, a ≤ b → f b ≤ f a
#align antitone Antitone

/-- A function `f` is monotone on `s` if, for all `a, b ∈ s`, `a ≤ b` implies `f a ≤ f b`. -/
def MonotoneOn (f : α → β) (s : Set α) : Prop :=
  ∀ ⦃a⦄ (_ : a ∈ s) ⦃b⦄ (_ : b ∈ s), a ≤ b → f a ≤ f b
#align monotone_on MonotoneOn

/-- A function `f` is antitone on `s` if, for all `a, b ∈ s`, `a ≤ b` implies `f b ≤ f a`. -/
def AntitoneOn (f : α → β) (s : Set α) : Prop :=
  ∀ ⦃a⦄ (_ : a ∈ s) ⦃b⦄ (_ : b ∈ s), a ≤ b → f b ≤ f a
#align antitone_on AntitoneOn

/-- A function `f` is strictly monotone if `a < b` implies `f a < f b`. -/
def StrictMono (f : α → β) : Prop :=
  ∀ ⦃a b⦄, a < b → f a < f b
#align strict_mono StrictMono

/-- A function `f` is strictly antitone if `a < b` implies `f b < f a`. -/
def StrictAnti (f : α → β) : Prop :=
  ∀ ⦃a b⦄, a < b → f b < f a
#align strict_anti StrictAnti

/-- A function `f` is strictly monotone on `s` if, for all `a, b ∈ s`, `a < b` implies
`f a < f b`. -/
def StrictMonoOn (f : α → β) (s : Set α) : Prop :=
  ∀ ⦃a⦄ (_ : a ∈ s) ⦃b⦄ (_ : b ∈ s), a < b → f a < f b
#align strict_mono_on StrictMonoOn

/-- A function `f` is strictly antitone on `s` if, for all `a, b ∈ s`, `a < b` implies
`f b < f a`. -/
def StrictAntiOn (f : α → β) (s : Set α) : Prop :=
  ∀ ⦃a⦄ (_ : a ∈ s) ⦃b⦄ (_ : b ∈ s), a < b → f b < f a
#align strict_anti_on StrictAntiOn

end MonotoneDef

/-! ### Monotonicity on the dual order

Strictly, many of the `*On.dual` lemmas in this section should use `ofDual ⁻¹' s` instead of `s`,
but right now this is not possible as `Set.preimage` is not defined yet, and importing it creates
an import cycle.

Often, you should not need the rewriting lemmas. Instead, you probably want to add `.dual`,
`.dual_left` or `.dual_right` to your `Monotone`/`Antitone` hypothesis.
-/

section OrderDual

variable [Preorder α] [Preorder β] {f : α → β} {s : Set α}

@[simp]
theorem monotone_comp_ofDual_iff : Monotone (f ∘ ofDual) ↔ Antitone f :=
  forall_swap
#align monotone_comp_of_dual_iff monotone_comp_ofDual_iff

@[simp]
theorem antitone_comp_ofDual_iff : Antitone (f ∘ ofDual) ↔ Monotone f :=
  forall_swap
#align antitone_comp_of_dual_iff antitone_comp_ofDual_iff

-- Porting note:
-- Here (and below) without the type ascription, Lean is seeing through the
-- defeq `βᵒᵈ = β` and picking up the wrong `Preorder` instance.
-- https://leanprover.zulipchat.com/#narrow/stream/287929-mathlib4/topic/logic.2Eequiv.2Ebasic.20mathlib4.23631/near/311744939
@[simp]
theorem monotone_toDual_comp_iff : Monotone (toDual ∘ f : α → βᵒᵈ) ↔ Antitone f :=
  Iff.rfl
#align monotone_to_dual_comp_iff monotone_toDual_comp_iff

@[simp]
theorem antitone_toDual_comp_iff : Antitone (toDual ∘ f : α → βᵒᵈ) ↔ Monotone f :=
  Iff.rfl
#align antitone_to_dual_comp_iff antitone_toDual_comp_iff

@[simp]
theorem monotoneOn_comp_ofDual_iff : MonotoneOn (f ∘ ofDual) s ↔ AntitoneOn f s :=
  forall₂_swap
#align monotone_on_comp_of_dual_iff monotoneOn_comp_ofDual_iff

@[simp]
theorem antitoneOn_comp_ofDual_iff : AntitoneOn (f ∘ ofDual) s ↔ MonotoneOn f s :=
  forall₂_swap
#align antitone_on_comp_of_dual_iff antitoneOn_comp_ofDual_iff

@[simp]
theorem monotoneOn_toDual_comp_iff : MonotoneOn (toDual ∘ f : α → βᵒᵈ) s ↔ AntitoneOn f s :=
  Iff.rfl
#align monotone_on_to_dual_comp_iff monotoneOn_toDual_comp_iff

@[simp]
theorem antitoneOn_toDual_comp_iff : AntitoneOn (toDual ∘ f : α → βᵒᵈ) s ↔ MonotoneOn f s :=
  Iff.rfl
#align antitone_on_to_dual_comp_iff antitoneOn_toDual_comp_iff

@[simp]
theorem strictMono_comp_ofDual_iff : StrictMono (f ∘ ofDual) ↔ StrictAnti f :=
  forall_swap
#align strict_mono_comp_of_dual_iff strictMono_comp_ofDual_iff

@[simp]
theorem strictAnti_comp_ofDual_iff : StrictAnti (f ∘ ofDual) ↔ StrictMono f :=
  forall_swap
#align strict_anti_comp_of_dual_iff strictAnti_comp_ofDual_iff

@[simp]
theorem strictMono_toDual_comp_iff : StrictMono (toDual ∘ f : α → βᵒᵈ) ↔ StrictAnti f :=
  Iff.rfl
#align strict_mono_to_dual_comp_iff strictMono_toDual_comp_iff

@[simp]
theorem strictAnti_toDual_comp_iff : StrictAnti (toDual ∘ f : α → βᵒᵈ) ↔ StrictMono f :=
  Iff.rfl
#align strict_anti_to_dual_comp_iff strictAnti_toDual_comp_iff

@[simp]
theorem strictMonoOn_comp_ofDual_iff : StrictMonoOn (f ∘ ofDual) s ↔ StrictAntiOn f s :=
  forall₂_swap
#align strict_mono_on_comp_of_dual_iff strictMonoOn_comp_ofDual_iff

@[simp]
theorem strictAntiOn_comp_ofDual_iff : StrictAntiOn (f ∘ ofDual) s ↔ StrictMonoOn f s :=
  forall₂_swap
#align strict_anti_on_comp_of_dual_iff strictAntiOn_comp_ofDual_iff

@[simp]
theorem strictMonoOn_toDual_comp_iff : StrictMonoOn (toDual ∘ f : α → βᵒᵈ) s ↔ StrictAntiOn f s :=
  Iff.rfl
#align strict_mono_on_to_dual_comp_iff strictMonoOn_toDual_comp_iff

@[simp]
theorem strictAntiOn_toDual_comp_iff : StrictAntiOn (toDual ∘ f : α → βᵒᵈ) s ↔ StrictMonoOn f s :=
  Iff.rfl
#align strict_anti_on_to_dual_comp_iff strictAntiOn_toDual_comp_iff

protected theorem Monotone.dual (hf : Monotone f) : Monotone (toDual ∘ f ∘ ofDual : αᵒᵈ → βᵒᵈ) :=
  swap hf
#align monotone.dual Monotone.dual

protected theorem Antitone.dual (hf : Antitone f) : Antitone (toDual ∘ f ∘ ofDual : αᵒᵈ → βᵒᵈ) :=
  swap hf
#align antitone.dual Antitone.dual

protected theorem MonotoneOn.dual (hf : MonotoneOn f s) :
    MonotoneOn (toDual ∘ f ∘ ofDual : αᵒᵈ → βᵒᵈ) s :=
  swap₂ hf
#align monotone_on.dual MonotoneOn.dual

protected theorem AntitoneOn.dual (hf : AntitoneOn f s) :
    AntitoneOn (toDual ∘ f ∘ ofDual : αᵒᵈ → βᵒᵈ) s :=
  swap₂ hf
#align antitone_on.dual AntitoneOn.dual

protected theorem StrictMono.dual (hf : StrictMono f) :
    StrictMono (toDual ∘ f ∘ ofDual : αᵒᵈ → βᵒᵈ) :=
  swap hf
#align strict_mono.dual StrictMono.dual

protected theorem StrictAnti.dual (hf : StrictAnti f) :
    StrictAnti (toDual ∘ f ∘ ofDual : αᵒᵈ → βᵒᵈ) :=
  swap hf
#align strict_anti.dual StrictAnti.dual

protected theorem StrictMonoOn.dual (hf : StrictMonoOn f s) :
    StrictMonoOn (toDual ∘ f ∘ ofDual : αᵒᵈ → βᵒᵈ) s :=
  swap₂ hf
#align strict_mono_on.dual StrictMonoOn.dual

protected theorem StrictAntiOn.dual (hf : StrictAntiOn f s) :
    StrictAntiOn (toDual ∘ f ∘ ofDual : αᵒᵈ → βᵒᵈ) s :=
  swap₂ hf
#align strict_anti_on.dual StrictAntiOn.dual

alias antitone_comp_ofDual_iff ↔ _ Monotone.dual_left
#align monotone.dual_left Monotone.dual_left

alias monotone_comp_ofDual_iff ↔ _ Antitone.dual_left
#align antitone.dual_left Antitone.dual_left

alias antitone_toDual_comp_iff ↔ _ Monotone.dual_right
#align monotone.dual_right Monotone.dual_right

alias monotone_toDual_comp_iff ↔ _ Antitone.dual_right
#align antitone.dual_right Antitone.dual_right

alias antitoneOn_comp_ofDual_iff ↔ _ MonotoneOn.dual_left
#align monotone_on.dual_left MonotoneOn.dual_left

alias monotoneOn_comp_ofDual_iff ↔ _ AntitoneOn.dual_left
#align antitone_on.dual_left AntitoneOn.dual_left

alias antitoneOn_toDual_comp_iff ↔ _ MonotoneOn.dual_right
#align monotone_on.dual_right MonotoneOn.dual_right

alias monotoneOn_toDual_comp_iff ↔ _ AntitoneOn.dual_right
#align antitone_on.dual_right AntitoneOn.dual_right

alias strictAnti_comp_ofDual_iff ↔ _ StrictMono.dual_left
#align strict_mono.dual_left StrictMono.dual_left

alias strictMono_comp_ofDual_iff ↔ _ StrictAnti.dual_left
#align strict_anti.dual_left StrictAnti.dual_left

alias strictAnti_toDual_comp_iff ↔ _ StrictMono.dual_right
#align strict_mono.dual_right StrictMono.dual_right

alias strictMono_toDual_comp_iff ↔ _ StrictAnti.dual_right
#align strict_anti.dual_right StrictAnti.dual_right

alias strictAntiOn_comp_ofDual_iff ↔ _ StrictMonoOn.dual_left
#align strict_mono_on.dual_left StrictMonoOn.dual_left

alias strictMonoOn_comp_ofDual_iff ↔ _ StrictAntiOn.dual_left
#align strict_anti_on.dual_left StrictAntiOn.dual_left

alias strictAntiOn_toDual_comp_iff ↔ _ StrictMonoOn.dual_right
#align strict_mono_on.dual_right StrictMonoOn.dual_right

alias strictMonoOn_toDual_comp_iff ↔ _ StrictAntiOn.dual_right
#align strict_anti_on.dual_right StrictAntiOn.dual_right

end OrderDual

/-! ### Monotonicity in function spaces -/

section Preorder

variable [Preorder α]

theorem Monotone.comp_le_comp_left
    [Preorder β] {f : β → α} {g h : γ → β} (hf : Monotone f) (le_gh : g ≤ h) :
    LE.le.{max w u} (f ∘ g) (f ∘ h) :=
  fun x ↦ hf (le_gh x)
#align monotone.comp_le_comp_left Monotone.comp_le_comp_left

variable [Preorder γ]

theorem monotone_lam {f : α → β → γ} (hf : ∀ b, Monotone fun a ↦ f a b) : Monotone f :=
  fun _ _ h b ↦ hf b h
#align monotone_lam monotone_lam

theorem monotone_app (f : β → α → γ) (b : β) (hf : Monotone fun a b ↦ f b a) : Monotone (f b) :=
  fun _ _ h ↦ hf h b
#align monotone_app monotone_app

theorem antitone_lam {f : α → β → γ} (hf : ∀ b, Antitone fun a ↦ f a b) : Antitone f :=
  fun _ _ h b ↦ hf b h
#align antitone_lam antitone_lam

theorem antitone_app (f : β → α → γ) (b : β) (hf : Antitone fun a b ↦ f b a) : Antitone (f b) :=
  fun _ _ h ↦ hf h b
#align antitone_app antitone_app

end Preorder

theorem Function.monotone_eval {ι : Type u} {α : ι → Type v} [∀ i, Preorder (α i)] (i : ι) :
    Monotone (Function.eval i : (∀ i, α i) → α i) := fun _ _ H ↦ H i
#align function.monotone_eval Function.monotone_eval

/-! ### Monotonicity hierarchy -/

section Preorder

variable [Preorder α]

section Preorder

variable [Preorder β] {f : α → β} {a b : α}

/-!
These four lemmas are there to strip off the semi-implicit arguments `⦃a b : α⦄`. This is useful
when you do not want to apply a `Monotone` assumption (i.e. your goal is `a ≤ b → f a ≤ f b`).
However if you find yourself writing `hf.imp h`, then you should have written `hf h` instead.
-/

theorem Monotone.imp (hf : Monotone f) (h : a ≤ b) : f a ≤ f b :=
  hf h
#align monotone.imp Monotone.imp

theorem Antitone.imp (hf : Antitone f) (h : a ≤ b) : f b ≤ f a :=
  hf h
#align antitone.imp Antitone.imp

theorem StrictMono.imp (hf : StrictMono f) (h : a < b) : f a < f b :=
  hf h
#align strict_mono.imp StrictMono.imp

theorem StrictAnti.imp (hf : StrictAnti f) (h : a < b) : f b < f a :=
  hf h
#align strict_anti.imp StrictAnti.imp

protected theorem Monotone.monotoneOn (hf : Monotone f) (s : Set α) : MonotoneOn f s :=
  fun _ _ _ _ ↦ hf.imp
#align monotone.monotone_on Monotone.monotoneOn

protected theorem Antitone.antitoneOn (hf : Antitone f) (s : Set α) : AntitoneOn f s :=
  fun _ _ _ _ ↦ hf.imp
#align antitone.antitone_on Antitone.antitoneOn

@[simp] theorem monotoneOn_univ : MonotoneOn f Set.univ ↔ Monotone f :=
  ⟨fun h _ _ ↦ h trivial trivial, fun h ↦ h.monotoneOn _⟩
#align monotone_on_univ monotoneOn_univ

@[simp] theorem antitoneOn_univ : AntitoneOn f Set.univ ↔ Antitone f :=
  ⟨fun h _ _ ↦ h trivial trivial, fun h ↦ h.antitoneOn _⟩
#align antitone_on_univ antitoneOn_univ

protected theorem StrictMono.strictMonoOn (hf : StrictMono f) (s : Set α) : StrictMonoOn f s :=
  fun _ _ _ _ ↦ hf.imp
#align strict_mono.strict_mono_on StrictMono.strictMonoOn

protected theorem StrictAnti.strictAntiOn (hf : StrictAnti f) (s : Set α) : StrictAntiOn f s :=
  fun _ _ _ _ ↦ hf.imp
#align strict_anti.strict_anti_on StrictAnti.strictAntiOn

@[simp] theorem strictMonoOn_univ : StrictMonoOn f Set.univ ↔ StrictMono f :=
  ⟨fun h _ _ ↦ h trivial trivial, fun h ↦ h.strictMonoOn _⟩
#align strict_mono_on_univ strictMonoOn_univ

@[simp] theorem strictAntiOn_univ : StrictAntiOn f Set.univ ↔ StrictAnti f :=
  ⟨fun h _ _ ↦ h trivial trivial, fun h ↦ h.strictAntiOn _⟩
#align strict_anti_on_univ strictAntiOn_univ

end Preorder

section PartialOrder

variable [PartialOrder β] {f : α → β}

theorem Monotone.strictMono_of_injective (h₁ : Monotone f) (h₂ : Injective f) : StrictMono f :=
  fun _ _ h ↦ (h₁ h.le).lt_of_ne fun H ↦ h.ne <| h₂ H
#align monotone.strict_mono_of_injective Monotone.strictMono_of_injective

theorem Antitone.strictAnti_of_injective (h₁ : Antitone f) (h₂ : Injective f) : StrictAnti f :=
  fun _ _ h ↦ (h₁ h.le).lt_of_ne fun H ↦ h.ne <| h₂ H.symm
#align antitone.strict_anti_of_injective Antitone.strictAnti_of_injective

end PartialOrder

end Preorder

section PartialOrder

variable [PartialOrder α] [Preorder β] {f : α → β} {s : Set α}

theorem monotone_iff_forall_lt : Monotone f ↔ ∀ ⦃a b⦄, a < b → f a ≤ f b :=
  forall₂_congr fun _ _ ↦
    ⟨fun hf h ↦ hf h.le, fun hf h ↦ h.eq_or_lt.elim (fun H ↦ (congr_arg _ H).le) hf⟩
#align monotone_iff_forall_lt monotone_iff_forall_lt

theorem antitone_iff_forall_lt : Antitone f ↔ ∀ ⦃a b⦄, a < b → f b ≤ f a :=
  forall₂_congr fun _ _ ↦
    ⟨fun hf h ↦ hf h.le, fun hf h ↦ h.eq_or_lt.elim (fun H ↦ (congr_arg _ H).ge) hf⟩
#align antitone_iff_forall_lt antitone_iff_forall_lt

theorem monotoneOn_iff_forall_lt :
    MonotoneOn f s ↔ ∀ ⦃a⦄ (_ : a ∈ s) ⦃b⦄ (_ : b ∈ s), a < b → f a ≤ f b :=
  ⟨fun hf _ ha _ hb h ↦ hf ha hb h.le,
   fun hf _ ha _ hb h ↦ h.eq_or_lt.elim (fun H ↦ (congr_arg _ H).le) (hf ha hb)⟩
#align monotone_on_iff_forall_lt monotoneOn_iff_forall_lt

theorem antitoneOn_iff_forall_lt :
    AntitoneOn f s ↔ ∀ ⦃a⦄ (_ : a ∈ s) ⦃b⦄ (_ : b ∈ s), a < b → f b ≤ f a :=
  ⟨fun hf _ ha _ hb h ↦ hf ha hb h.le,
   fun hf _ ha _ hb h ↦ h.eq_or_lt.elim (fun H ↦ (congr_arg _ H).ge) (hf ha hb)⟩
#align antitone_on_iff_forall_lt antitoneOn_iff_forall_lt

-- `Preorder α` isn't strong enough: if the preorder on `α` is an equivalence relation,
-- then `StrictMono f` is vacuously true.
protected theorem StrictMonoOn.monotoneOn (hf : StrictMonoOn f s) : MonotoneOn f s :=
  monotoneOn_iff_forall_lt.2 fun _ ha _ hb h ↦ (hf ha hb h).le
#align strict_mono_on.monotone_on StrictMonoOn.monotoneOn

protected theorem StrictAntiOn.antitoneOn (hf : StrictAntiOn f s) : AntitoneOn f s :=
  antitoneOn_iff_forall_lt.2 fun _ ha _ hb h ↦ (hf ha hb h).le
#align strict_anti_on.antitone_on StrictAntiOn.antitoneOn

protected theorem StrictMono.monotone (hf : StrictMono f) : Monotone f :=
  monotone_iff_forall_lt.2 fun _ _ h ↦ (hf h).le
#align strict_mono.monotone StrictMono.monotone

protected theorem StrictAnti.antitone (hf : StrictAnti f) : Antitone f :=
  antitone_iff_forall_lt.2 fun _ _ h ↦ (hf h).le
#align strict_anti.antitone StrictAnti.antitone

end PartialOrder

/-! ### Monotonicity from and to subsingletons -/

namespace Subsingleton

variable [Preorder α] [Preorder β]

protected

protected theorem antitone [Subsingleton α] (f : α → β) : Antitone f :=
  fun _ _ _ ↦ (congr_arg _ <| Subsingleton.elim _ _).le
#align subsingleton.antitone Subsingleton.antitone

theorem monotone' [Subsingleton β] (f : α → β) : Monotone f :=
  fun _ _ _ ↦ (Subsingleton.elim _ _).le
#align subsingleton.monotone' Subsingleton.monotone'

theorem antitone' [Subsingleton β] (f : α → β) : Antitone f :=
  fun _ _ _ ↦ (Subsingleton.elim _ _).le
#align subsingleton.antitone' Subsingleton.antitone'

protected theorem strictMono [Subsingleton α] (f : α → β) : StrictMono f :=
  fun _ _ h ↦ (h.ne <| Subsingleton.elim _ _).elim
#align subsingleton.strict_mono Subsingleton.strictMono

protected theorem strictAnti [Subsingleton α] (f : α → β) : StrictAnti f :=
  fun _ _ h ↦ (h.ne <| Subsingleton.elim _ _).elim
#align subsingleton.strict_anti Subsingleton.strictAnti

end Subsingleton

/-! ### Miscellaneous monotonicity results -/

theorem monotone_id [Preorder α] : Monotone (id : α → α) := fun _ _ ↦ id
#align monotone_id monotone_id

theorem monotoneOn_id [Preorder α] {s : Set α} : MonotoneOn id s := fun _ _ _ _ ↦ id
#align monotone_on_id monotoneOn_id

theorem strictMono_id [Preorder α] : StrictMono (id : α → α) := fun _ _ ↦ id
#align strict_mono_id strictMono_id

theorem strictMonoOn_id [Preorder α] {s : Set α} : StrictMonoOn id s := fun _ _ _ _ ↦ id
#align strict_mono_on_id strictMonoOn_id

theorem monotone_const [Preorder α] [Preorder β] {c : β} : Monotone fun _ : α ↦ c :=
  fun _ _ _ ↦ le_rfl
#align monotone_const monotone_const

theorem monotoneOn_const [Preorder α] [Preorder β] {c : β} {s : Set α} :
    MonotoneOn (fun _ : α ↦ c) s :=
  fun _ _ _ _ _ ↦ le_rfl
#align monotone_on_const monotoneOn_const

theorem antitone_const [Preorder α] [Preorder β] {c : β} : Antitone fun _ : α ↦ c :=
  fun _ _ _ ↦ le_refl c
#align antitone_const antitone_const

theorem antitoneOn_const [Preorder α] [Preorder β] {c : β} {s : Set α} :
    AntitoneOn (fun _ : α ↦ c) s :=
  fun _ _ _ _ _ ↦ le_rfl
#align antitone_on_const antitoneOn_const

theorem strictMono_of_le_iff_le [Preorder α] [Preorder β] {f : α → β}
    (h : ∀ x y, x ≤ y ↔ f x ≤ f y) : StrictMono f :=
  fun _ _ ↦ (lt_iff_lt_of_le_iff_le' (h _ _) (h _ _)).1
#align strict_mono_of_le_iff_le strictMono_of_le_iff_le

theorem strictAnti_of_le_iff_le [Preorder α] [Preorder β] {f : α → β}
    (h : ∀ x y, x ≤ y ↔ f y ≤ f x) : StrictAnti f :=
  fun _ _ ↦ (lt_iff_lt_of_le_iff_le' (h _ _) (h _ _)).1
#align strict_anti_of_le_iff_le strictAnti_of_le_iff_le

-- Porting note: mathlib3 proof uses `contrapose` tactic
theorem injective_of_lt_imp_ne [LinearOrder α] {f : α → β} (h : ∀ x y, x < y → f x ≠ f y) :
    Injective f := by
  intro x y hf
  rcases lt_trichotomy x y with (hxy | rfl | hxy)
  · exact absurd hf <| h _ _ hxy
  · rfl
  · exact absurd hf.symm <| h _ _ hxy
#align injective_of_lt_imp_ne injective_of_lt_imp_ne

theorem injective_of_le_imp_le [PartialOrder α] [Preorder β] (f : α → β)
    (h : ∀ {x y}, f x ≤ f y → x ≤ y) : Injective f :=
  fun _ _ hxy ↦ (h hxy.le).antisymm (h hxy.ge)
#align injective_of_le_imp_le injective_of_le_imp_le

section Preorder

variable [Preorder α] [Preorder β] {f g : α → β} {a : α}

theorem StrictMono.isMax_of_apply (hf : StrictMono f) (ha : IsMax (f a)) : IsMax a :=
  of_not_not fun h ↦
    let ⟨_, hb⟩ := not_isMax_iff.1 h
    (hf hb).not_isMax ha
#align strict_mono.is_max_of_apply StrictMono.isMax_of_apply

theorem StrictMono.isMin_of_apply (hf : StrictMono f) (ha : IsMin (f a)) : IsMin a :=
  of_not_not fun h ↦
    let ⟨_, hb⟩ := not_isMin_iff.1 h
    (hf hb).not_isMin ha
#align strict_mono.is_min_of_apply StrictMono.isMin_of_apply

theorem StrictAnti.isMax_of_apply (hf : StrictAnti f) (ha : IsMin (f a)) : IsMax a :=
  of_not_not fun h ↦
    let ⟨_, hb⟩ := not_isMax_iff.1 h
    (hf hb).not_isMin ha
#align strict_anti.is_max_of_apply StrictAnti.isMax_of_apply

theorem StrictAnti.isMin_of_apply (hf : StrictAnti f) (ha : IsMax (f a)) : IsMin a :=
  of_not_not fun h ↦
    let ⟨_, hb⟩ := not_isMin_iff.1 h
    (hf hb).not_isMax ha
#align strict_anti.is_min_of_apply StrictAnti.isMin_of_apply

protected theorem StrictMono.ite' (hf : StrictMono f) (hg : StrictMono g) {p : α → Prop}
    [DecidablePred p]
    (hp : ∀ ⦃x y⦄, x < y → p y → p x) (hfg : ∀ ⦃x y⦄, p x → ¬p y → x < y → f x < g y) :
    StrictMono fun x ↦ if p x then f x else g x := by
  intro x y h
  by_cases hy:p y
  · have hx : p x := hp h hy
    simpa [hx, hy] using hf h
  by_cases hx:p x
  · simpa [hx, hy] using hfg hx hy h
  · simpa [hx, hy] using hg h
#align strict_mono.ite' StrictMono.ite'

protected theorem StrictMono.ite (hf : StrictMono f) (hg : StrictMono g) {p : α → Prop}
    [DecidablePred p] (hp : ∀ ⦃x y⦄, x < y → p y → p x) (hfg : ∀ x, f x ≤ g x) :
    StrictMono fun x ↦ if p x then f x else g x :=
  (hf.ite' hg hp) fun _ y _ _ h ↦ (hf h).trans_le (hfg y)
#align strict_mono.ite StrictMono.ite

-- Porting note: `Strict*.dual_right` dot notation is not working here for some reason
protected theorem StrictAnti.ite' (hf : StrictAnti f) (hg : StrictAnti g) {p : α → Prop}
    [DecidablePred p]
    (hp : ∀ ⦃x y⦄, x < y → p y → p x) (hfg : ∀ ⦃x y⦄, p x → ¬p y → x < y → g y < f x) :
    StrictAnti fun x ↦ if p x then f x else g x :=
  StrictMono.ite' (StrictAnti.dual_right hf) (StrictAnti.dual_right hg) hp hfg
#align strict_anti.ite' StrictAnti.ite'

protected theorem StrictAnti.ite (hf : StrictAnti f) (hg : StrictAnti g) {p : α → Prop}
    [DecidablePred p] (hp : ∀ ⦃x y⦄, x < y → p y → p x) (hfg : ∀ x, g x ≤ f x) :
    StrictAnti fun x ↦ if p x then f x else g x :=
  (hf.ite' hg hp) fun _ y _ _ h ↦ (hfg y).trans_lt (hf h)
#align strict_anti.ite StrictAnti.ite

end Preorder

/-! ### Monotonicity under composition -/

section Composition

variable [Preorder α] [Preorder β] [Preorder γ] {g : β → γ} {f : α → β} {s : Set α}

protected theorem Monotone.comp (hg : Monotone g) (hf : Monotone f) : Monotone (g ∘ f) :=
  fun _ _ h ↦ hg (hf h)
#align monotone.comp Monotone.comp

theorem Monotone.comp_antitone (hg : Monotone g) (hf : Antitone f) : Antitone (g ∘ f) :=
  fun _ _ h ↦ hg (hf h)
#align monotone.comp_antitone Monotone.comp_antitone

protected theorem Antitone.comp (hg : Antitone g) (hf : Antitone f) : Monotone (g ∘ f) :=
  fun _ _ h ↦ hg (hf h)
#align antitone.comp Antitone.comp

theorem Antitone.comp_monotone (hg : Antitone g) (hf : Monotone f) : Antitone (g ∘ f) :=
  fun _ _ h ↦ hg (hf h)
#align antitone.comp_monotone Antitone.comp_monotone

protected theorem Monotone.iterate {f : α → α} (hf : Monotone f) (n : ℕ) : Monotone (f^[n]) :=
  Nat.recOn n monotone_id fun _ h ↦ h.comp hf
#align monotone.iterate Monotone.iterate

protected theorem Monotone.comp_monotoneOn (hg : Monotone g) (hf : MonotoneOn f s) :
    MonotoneOn (g ∘ f) s :=
  fun _ ha _ hb h ↦ hg (hf ha hb h)
#align monotone.comp_monotone_on Monotone.comp_monotoneOn

theorem Monotone.comp_antitoneOn (hg : Monotone g) (hf : AntitoneOn f s) : AntitoneOn (g ∘ f) s :=
  fun _ ha _ hb h ↦ hg (hf ha hb h)
#align monotone.comp_antitone_on Monotone.comp_antitoneOn

protected theorem Antitone.comp_antitoneOn (hg : Antitone g) (hf : AntitoneOn f s) :
    MonotoneOn (g ∘ f) s :=
  fun _ ha _ hb h ↦ hg (hf ha hb h)
#align antitone.comp_antitone_on Antitone.comp_antitoneOn

theorem Antitone.comp_monotoneOn (hg : Antitone g) (hf : MonotoneOn f s) : AntitoneOn (g ∘ f) s :=
  fun _ ha _ hb h ↦ hg (hf ha hb h)
#align antitone.comp_monotone_on Antitone.comp_monotoneOn

protected theorem StrictMono.comp (hg : StrictMono g) (hf : StrictMono f) : StrictMono (g ∘ f) :=
  fun _ _ h ↦ hg (hf h)
#align strict_mono.comp StrictMono.comp

theorem StrictMono.comp_strictAnti (hg : StrictMono g) (hf : StrictAnti f) : StrictAnti (g ∘ f) :=
  fun _ _ h ↦ hg (hf h)
#align strict_mono.comp_strict_anti StrictMono.comp_strictAnti

protected theorem StrictAnti.comp (hg : StrictAnti g) (hf : StrictAnti f) : StrictMono (g ∘ f) :=
  fun _ _ h ↦ hg (hf h)
#align strict_anti.comp StrictAnti.comp

theorem StrictAnti.comp_strictMono (hg : StrictAnti g) (hf : StrictMono f) : StrictAnti (g ∘ f) :=
  fun _ _ h ↦ hg (hf h)
#align strict_anti.comp_strict_mono StrictAnti.comp_strictMono

protected theorem StrictMono.iterate {f : α → α} (hf : StrictMono f) (n : ℕ) : StrictMono (f^[n]) :=
  Nat.recOn n strictMono_id fun _ h ↦ h.comp hf
#align strict_mono.iterate StrictMono.iterate

protected theorem StrictMono.comp_strictMonoOn (hg : StrictMono g) (hf : StrictMonoOn f s) :
    StrictMonoOn (g ∘ f) s :=
  fun _ ha _ hb h ↦ hg (hf ha hb h)
#align strict_mono.comp_strict_mono_on StrictMono.comp_strictMonoOn

theorem StrictMono.comp_strictAntiOn (hg : StrictMono g) (hf : StrictAntiOn f s) :
    StrictAntiOn (g ∘ f) s :=
  fun _ ha _ hb h ↦ hg (hf ha hb h)
#align strict_mono.comp_strict_anti_on StrictMono.comp_strictAntiOn

protected theorem StrictAnti.comp_strictAntiOn (hg : StrictAnti g) (hf : StrictAntiOn f s) :
    StrictMonoOn (g ∘ f) s :=
  fun _ ha _ hb h ↦ hg (hf ha hb h)
#align strict_anti.comp_strict_anti_on StrictAnti.comp_strictAntiOn

theorem StrictAnti.comp_strictMonoOn (hg : StrictAnti g) (hf : StrictMonoOn f s) :
    StrictAntiOn (g ∘ f) s :=
  fun _ ha _ hb h ↦ hg (hf ha hb h)
#align strict_anti.comp_strict_mono_on StrictAnti.comp_strictMonoOn

end Composition

namespace List

section Fold

theorem foldl_monotone [Preorder α] {f : α → β → α} (H : ∀ b, Monotone fun a ↦ f a b)
    (l : List β) : Monotone fun a ↦ l.foldl f a :=
  List.recOn l (fun _ _ ↦ id) fun _ _ hl _ _ h ↦ hl (H _ h)
#align list.foldl_monotone List.foldl_monotone

theorem foldr_monotone [Preorder β] {f : α → β → β} (H : ∀ a, Monotone (f a)) (l : List α) :
    Monotone fun b ↦ l.foldr f b := fun _ _ h ↦ List.recOn l h fun i _ hl ↦ H i hl
#align list.foldr_monotone List.foldr_monotone

theorem foldl_strictMono [Preorder α] {f : α → β → α} (H : ∀ b, StrictMono fun a ↦ f a b)
    (l : List β) : StrictMono fun a ↦ l.foldl f a :=
  List.recOn l (fun _ _ ↦ id) fun _ _ hl _ _ h ↦ hl (H _ h)
#align list.foldl_strict_mono List.foldl_strictMono

theorem foldr_strictMono [Preorder β] {f : α → β → β} (H : ∀ a, StrictMono (f a)) (l : List α) :
    StrictMono fun b ↦ l.foldr f b := fun _ _ h ↦ List.recOn l h fun i _ hl ↦ H i hl
#align list.foldr_strict_mono List.foldr_strictMono

end Fold

end List

/-! ### Monotonicity in linear orders  -/

section LinearOrder

variable [LinearOrder α]

section Preorder

variable [Preorder β] {f : α → β} {s : Set α}

open Ordering

theorem Monotone.reflect_lt (hf : Monotone f) {a b : α} (h : f a < f b) : a < b :=
  lt_of_not_ge fun h' ↦ h.not_le (hf h')
#align monotone.reflect_lt Monotone.reflect_lt

theorem Antitone.reflect_lt (hf : Antitone f) {a b : α} (h : f a < f b) : b < a :=
  lt_of_not_ge fun h' ↦ h.not_le (hf h')
#align antitone.reflect_lt Antitone.reflect_lt

theorem MonotoneOn.reflect_lt (hf : MonotoneOn f s) {a b : α} (ha : a ∈ s) (hb : b ∈ s)
    (h : f a < f b) : a < b :=
  lt_of_not_ge fun h' ↦ h.not_le <| hf hb ha h'
#align monotone_on.reflect_lt MonotoneOn.reflect_lt

theorem AntitoneOn.reflect_lt (hf : AntitoneOn f s) {a b : α} (ha : a ∈ s) (hb : b ∈ s)
    (h : f a < f b) : b < a :=
  lt_of_not_ge fun h' ↦ h.not_le <| hf ha hb h'
#align antitone_on.reflect_lt AntitoneOn.reflect_lt

theorem StrictMonoOn.le_iff_le (hf : StrictMonoOn f s) {a b : α} (ha : a ∈ s) (hb : b ∈ s)
    : f a ≤ f b ↔ a ≤ b :=
  ⟨fun h ↦ le_of_not_gt fun h' ↦ (hf hb ha h').not_le h, fun h ↦
    h.lt_or_eq_dec.elim (fun h' ↦ (hf ha hb h').le) fun h' ↦ h' ▸ le_rfl⟩
#align strict_mono_on.le_iff_le StrictMonoOn.le_iff_le

theorem StrictAntiOn.le_iff_le (hf : StrictAntiOn f s) {a b : α} (ha : a ∈ s) (hb : b ∈ s) :
    f a ≤ f b ↔ b ≤ a :=
  hf.dual_right.le_iff_le hb ha
#align strict_anti_on.le_iff_le StrictAntiOn.le_iff_le

theorem StrictMonoOn.eq_iff_eq (hf : StrictMonoOn f s) {a b : α} (ha : a ∈ s) (hb : b ∈ s) :
    f a = f b ↔ a = b :=
  ⟨fun h ↦ le_antisymm ((hf.le_iff_le ha hb).mp h.le) ((hf.le_iff_le hb ha).mp h.ge), by
    rintro rfl
    rfl⟩
#align strict_mono_on.eq_iff_eq StrictMonoOn.eq_iff_eq

theorem StrictAntiOn.eq_iff_eq (hf : StrictAntiOn f s) {a b : α} (ha : a ∈ s) (hb : b ∈ s) :
    f a = f b ↔ b = a :=
  (hf.dual_right.eq_iff_eq ha hb).trans eq_comm
#align strict_anti_on.eq_iff_eq StrictAntiOn.eq_iff_eq

theorem StrictMonoOn.lt_iff_lt (hf : StrictMonoOn f s) {a b : α} (ha : a ∈ s) (hb : b ∈ s) :
    f a < f b ↔ a < b := by
  rw [lt_iff_le_not_le, lt_iff_le_not_le, hf.le_iff_le ha hb, hf.le_iff_le hb ha]
#align strict_mono_on.lt_iff_lt StrictMonoOn.lt_iff_lt

theorem StrictAntiOn.lt_iff_lt (hf : StrictAntiOn f s) {a b : α} (ha : a ∈ s) (hb : b ∈ s) :
    f a < f b ↔ b < a :=
  hf.dual_right.lt_iff_lt hb ha
#align strict_anti_on.lt_iff_lt StrictAntiOn.lt_iff_lt

theorem StrictMono.le_iff_le (hf : StrictMono f) {a b : α} : f a ≤ f b ↔ a ≤ b :=
  (hf.strictMonoOn Set.univ).le_iff_le trivial trivial
#align strict_mono.le_iff_le StrictMono.le_iff_le

theorem StrictAnti.le_iff_le (hf : StrictAnti f) {a b : α} : f a ≤ f b ↔ b ≤ a :=
  (hf.strictAntiOn Set.univ).le_iff_le trivial trivial
#align strict_anti.le_iff_le StrictAnti.le_iff_le

theorem StrictMono.lt_iff_lt (hf : StrictMono f) {a b : α} : f a < f b ↔ a < b :=
  (hf.strictMonoOn Set.univ).lt_iff_lt trivial trivial
#align strict_mono.lt_iff_lt StrictMono.lt_iff_lt

theorem StrictAnti.lt_iff_lt (hf : StrictAnti f) {a b : α} : f a < f b ↔ b < a :=
  (hf.strictAntiOn Set.univ).lt_iff_lt trivial trivial
#align strict_anti.lt_iff_lt StrictAnti.lt_iff_lt

protected theorem StrictMonoOn.compares (hf : StrictMonoOn f s) {a b : α} (ha : a ∈ s)
    (hb : b ∈ s) : ∀ {o : Ordering}, o.Compares (f a) (f b) ↔ o.Compares a b
  | Ordering.lt => hf.lt_iff_lt ha hb
  | Ordering.eq => ⟨fun h ↦ ((hf.le_iff_le ha hb).1 h.le).antisymm
                      ((hf.le_iff_le hb ha).1 h.symm.le), congr_arg _⟩
  | Ordering.gt => hf.lt_iff_lt hb ha
#align strict_mono_on.compares StrictMonoOn.compares

protected theorem StrictAntiOn.compares (hf : StrictAntiOn f s) {a b : α} (ha : a ∈ s)
    (hb : b ∈ s) {o : Ordering} : o.Compares (f a) (f b) ↔ o.Compares b a :=
  toDual_compares_toDual.trans <| hf.dual_right.compares hb ha
#align strict_anti_on.compares StrictAntiOn.compares

protected theorem StrictMono.compares (hf : StrictMono f) {a b : α} {o : Ordering} :
    o.Compares (f a) (f b) ↔ o.Compares a b :=
  (hf.strictMonoOn Set.univ).compares trivial trivial
#align strict_mono.compares StrictMono.compares

protected theorem StrictAnti.compares (hf : StrictAnti f) {a b : α} {o : Ordering} :
    o.Compares (f a) (f b) ↔ o.Compares b a :=
  (hf.strictAntiOn Set.univ).compares trivial trivial
#align strict_anti.compares StrictAnti.compares

theorem StrictMono.injective (hf : StrictMono f) : Injective f :=
  fun x y h ↦ show Compares eq x y from hf.compares.1 h
#align strict_mono.injective StrictMono.injective

theorem StrictAnti.injective (hf : StrictAnti f) : Injective f :=
  fun x y h ↦ show Compares eq x y from hf.compares.1 h.symm
#align strict_anti.injective StrictAnti.injective

theorem StrictMono.maximal_of_maximal_image (hf : StrictMono f) {a} (hmax : ∀ p, p ≤ f a) (x : α) :
    x ≤ a :=
  hf.le_iff_le.mp (hmax (f x))
#align strict_mono.maximal_of_maximal_image StrictMono.maximal_of_maximal_image

theorem StrictMono.minimal_of_minimal_image (hf : StrictMono f) {a} (hmin : ∀ p, f a ≤ p) (x : α) :
    a ≤ x :=
  hf.le_iff_le.mp (hmin (f x))
#align strict_mono.minimal_of_minimal_image StrictMono.minimal_of_minimal_image

theorem StrictAnti.minimal_of_maximal_image (hf : StrictAnti f) {a} (hmax : ∀ p, p ≤ f a) (x : α) :
    a ≤ x :=
  hf.le_iff_le.mp (hmax (f x))
#align strict_anti.minimal_of_maximal_image StrictAnti.minimal_of_maximal_image

theorem StrictAnti.maximal_of_minimal_image (hf : StrictAnti f) {a} (hmin : ∀ p, f a ≤ p) (x : α) :
    x ≤ a :=
  hf.le_iff_le.mp (hmin (f x))
#align strict_anti.maximal_of_minimal_image StrictAnti.maximal_of_minimal_image

end Preorder

section PartialOrder

variable [PartialOrder β] {f : α → β}

theorem Monotone.strictMono_iff_injective (hf : Monotone f) : StrictMono f ↔ Injective f :=
  ⟨fun h ↦ h.injective, hf.strictMono_of_injective⟩
#align monotone.strict_mono_iff_injective Monotone.strictMono_iff_injective

theorem Antitone.strictAnti_iff_injective (hf : Antitone f) : StrictAnti f ↔ Injective f :=
  ⟨fun h ↦ h.injective, hf.strictAnti_of_injective⟩
#align antitone.strict_anti_iff_injective Antitone.strictAnti_iff_injective

end PartialOrder

variable [LinearOrder β] {f : α → β} {s : Set α} {x y : α}

/-- A function between linear orders which is neither monotone nor antitone makes a dent upright or
downright. -/
lemma not_monotone_not_antitone_iff_exists_le_le :
  ¬ Monotone f ∧ ¬ Antitone f ↔ ∃ a b c, a ≤ b ∧ b ≤ c ∧
    (f a < f b ∧ f c < f b ∨ f b < f a ∧ f b < f c) := by
  simp_rw [Monotone, Antitone, not_forall, not_le]
  refine' Iff.symm ⟨_, _⟩
  { rintro ⟨a, b, c, hab, hbc, ⟨hfab, hfcb⟩ | ⟨hfba, hfbc⟩⟩
    exacts [⟨⟨_, _, hbc, hfcb⟩, _, _, hab, hfab⟩, ⟨⟨_, _, hab, hfba⟩, _, _, hbc, hfbc⟩] }
  rintro ⟨⟨a, b, hab, hfba⟩, c, d, hcd, hfcd⟩
  obtain hda | had := le_total d a
  { obtain hfad | hfda := le_total (f a) (f d)
    { exact ⟨c, d, b, hcd, hda.trans hab, Or.inl ⟨hfcd, hfba.trans_le hfad⟩⟩ }
    { exact ⟨c, a, b, hcd.trans hda, hab, Or.inl ⟨hfcd.trans_le hfda, hfba⟩⟩ } }
  obtain hac | hca := le_total a c
  { obtain hfdb | hfbd := le_or_lt (f d) (f b)
    { exact ⟨a, c, d, hac, hcd, Or.inr ⟨hfcd.trans $ hfdb.trans_lt hfba, hfcd⟩⟩ }
    obtain hfca | hfac := lt_or_le (f c) (f a)
    { exact ⟨a, c, d, hac, hcd, Or.inr ⟨hfca, hfcd⟩⟩ }
    obtain hbd | hdb := le_total b d
    { exact ⟨a, b, d, hab, hbd, Or.inr ⟨hfba, hfbd⟩⟩ }
    { exact ⟨a, d, b, had, hdb, Or.inl ⟨hfac.trans_lt hfcd, hfbd⟩⟩ } }
  { obtain hfdb | hfbd := le_or_lt (f d) (f b)
    { exact ⟨c, a, b, hca, hab, Or.inl ⟨hfcd.trans $ hfdb.trans_lt hfba, hfba⟩⟩ }
    obtain hfca | hfac := lt_or_le (f c) (f a)
    { exact ⟨c, a, b, hca, hab, Or.inl ⟨hfca, hfba⟩⟩ }
    obtain hbd | hdb := le_total b d
    { exact ⟨a, b, d, hab, hbd, Or.inr ⟨hfba, hfbd⟩⟩ }
    { exact ⟨a, d, b, had, hdb, Or.inl ⟨hfac.trans_lt hfcd, hfbd⟩⟩ } }
#align not_monotone_not_antitone_iff_exists_le_le not_monotone_not_antitone_iff_exists_le_le

/-- A function between linear orders which is neither monotone nor antitone makes a dent upright or
downright. -/
lemma not_monotone_not_antitone_iff_exists_lt_lt :
  ¬ Monotone f ∧ ¬ Antitone f ↔ ∃ a b c, a < b ∧ b < c ∧
    (f a < f b ∧ f c < f b ∨ f b < f a ∧ f b < f c) := by
  simp_rw [not_monotone_not_antitone_iff_exists_le_le, ←and_assoc]
  refine' exists₃_congr (fun a b c ↦ and_congr_left $
    fun h ↦ (Ne.le_iff_lt _).and $ Ne.le_iff_lt _) <;>
  (rintro rfl; simp at h)
#align not_monotone_not_antitone_iff_exists_lt_lt not_monotone_not_antitone_iff_exists_lt_lt

/-!
### Strictly monotone functions and `cmp`
-/

variable [LinearOrder β] {f : α → β} {s : Set α} {x y : α}

theorem StrictMonoOn.cmp_map_eq (hf : StrictMonoOn f s) (hx : x ∈ s) (hy : y ∈ s) :
    cmp (f x) (f y) = cmp x y :=
  ((hf.compares hx hy).2 (cmp_compares x y)).cmp_eq
#align strict_mono_on.cmp_map_eq StrictMonoOn.cmp_map_eq

theorem StrictMono.cmp_map_eq (hf : StrictMono f) (x y : α) : cmp (f x) (f y) = cmp x y :=
  (hf.strictMonoOn Set.univ).cmp_map_eq trivial trivial
#align strict_mono.cmp_map_eq StrictMono.cmp_map_eq

theorem StrictAntiOn.cmp_map_eq (hf : StrictAntiOn f s) (hx : x ∈ s) (hy : y ∈ s) :
    cmp (f x) (f y) = cmp y x :=
  hf.dual_right.cmp_map_eq hy hx
#align strict_anti_on.cmp_map_eq StrictAntiOn.cmp_map_eq

theorem StrictAnti.cmp_map_eq (hf : StrictAnti f) (x y : α) : cmp (f x) (f y) = cmp y x :=
  (hf.strictAntiOn Set.univ).cmp_map_eq trivial trivial
#align strict_anti.cmp_map_eq StrictAnti.cmp_map_eq

end LinearOrder

/-! ### Monotonicity in `ℕ` and `ℤ` -/

section Preorder

variable [Preorder α]

theorem Nat.rel_of_forall_rel_succ_of_le_of_lt (r : β → β → Prop) [IsTrans β r] {f : ℕ → β} {a : ℕ}
    (h : ∀ n, a ≤ n → r (f n) (f (n + 1))) ⦃b c : ℕ⦄ (hab : a ≤ b) (hbc : b < c) :
    r (f b) (f c) := by
  induction' hbc with k b_lt_k r_b_k
  exacts[h _ hab, _root_.trans r_b_k (h _ (hab.trans_lt b_lt_k).le)]
#align nat.rel_of_forall_rel_succ_of_le_of_lt Nat.rel_of_forall_rel_succ_of_le_of_lt

theorem Nat.rel_of_forall_rel_succ_of_le_of_le (r : β → β → Prop) [IsRefl β r] [IsTrans β r]
    {f : ℕ → β} {a : ℕ} (h : ∀ n, a ≤ n → r (f n) (f (n + 1)))
    ⦃b c : ℕ⦄ (hab : a ≤ b) (hbc : b ≤ c) : r (f b) (f c) :=
  hbc.eq_or_lt.elim (fun h ↦ h ▸ refl _) (Nat.rel_of_forall_rel_succ_of_le_of_lt r h hab)
#align nat.rel_of_forall_rel_succ_of_le_of_le Nat.rel_of_forall_rel_succ_of_le_of_le

theorem Nat.rel_of_forall_rel_succ_of_lt (r : β → β → Prop) [IsTrans β r] {f : ℕ → β}
    (h : ∀ n, r (f n) (f (n + 1))) ⦃a b : ℕ⦄ (hab : a < b) : r (f a) (f b) :=
  Nat.rel_of_forall_rel_succ_of_le_of_lt r (fun n _ ↦ h n) le_rfl hab
#align nat.rel_of_forall_rel_succ_of_lt Nat.rel_of_forall_rel_succ_of_lt

theorem Nat.rel_of_forall_rel_succ_of_le (r : β → β → Prop) [IsRefl β r] [IsTrans β r] {f : ℕ → β}
    (h : ∀ n, r (f n) (f (n + 1))) ⦃a b : ℕ⦄ (hab : a ≤ b) : r (f a) (f b) :=
  Nat.rel_of_forall_rel_succ_of_le_of_le r (fun n _ ↦ h n) le_rfl hab
#align nat.rel_of_forall_rel_succ_of_le Nat.rel_of_forall_rel_succ_of_le

theorem monotone_nat_of_le_succ {f : ℕ → α} (hf : ∀ n, f n ≤ f (n + 1)) : Monotone f :=
  Nat.rel_of_forall_rel_succ_of_le (· ≤ ·) hf
#align monotone_nat_of_le_succ monotone_nat_of_le_succ

theorem antitone_nat_of_succ_le {f : ℕ → α} (hf : ∀ n, f (n + 1) ≤ f n) : Antitone f :=
  @monotone_nat_of_le_succ αᵒᵈ _ _ hf
#align antitone_nat_of_succ_le antitone_nat_of_succ_le

theorem strictMono_nat_of_lt_succ {f : ℕ → α} (hf : ∀ n, f n < f (n + 1)) : StrictMono f :=
  Nat.rel_of_forall_rel_succ_of_lt (· < ·) hf
#align strict_mono_nat_of_lt_succ strictMono_nat_of_lt_succ

theorem strictAnti_nat_of_succ_lt {f : ℕ → α} (hf : ∀ n, f (n + 1) < f n) : StrictAnti f :=
  @strictMono_nat_of_lt_succ αᵒᵈ _ f hf
#align strict_anti_nat_of_succ_lt strictAnti_nat_of_succ_lt

namespace Nat

/-- If `α` is a preorder with no maximal elements, then there exists a strictly monotone function
`ℕ → α` with any prescribed value of `f 0`. -/
theorem exists_strictMono' [NoMaxOrder α] (a : α) : ∃ f : ℕ → α, StrictMono f ∧ f 0 = a := by
  choose g hg using fun x : α ↦ exists_gt x
  exact ⟨fun n ↦ Nat.recOn n a fun _ ↦ g, strictMono_nat_of_lt_succ fun n ↦ hg _, rfl⟩
#align nat.exists_strict_mono' Nat.exists_strictMono'

/-- If `α` is a preorder with no maximal elements, then there exists a strictly antitone function
`ℕ → α` with any prescribed value of `f 0`. -/
theorem exists_strictAnti' [NoMinOrder α] (a : α) : ∃ f : ℕ → α, StrictAnti f ∧ f 0 = a :=
  exists_strictMono' (OrderDual.toDual a)
#align nat.exists_strict_anti' Nat.exists_strictAnti'

variable (α)

/-- If `α` is a nonempty preorder with no maximal elements, then there exists a strictly monotone
function `ℕ → α`. -/
theorem exists_strictMono [Nonempty α] [NoMaxOrder α] : ∃ f : ℕ → α, StrictMono f :=
  let ⟨a⟩ := ‹Nonempty α›
  let ⟨f, hf, _⟩ := exists_strictMono' a
  ⟨f, hf⟩
#align nat.exists_strict_mono Nat.exists_strictMono

/-- If `α` is a nonempty preorder with no minimal elements, then there exists a strictly antitone
function `ℕ → α`. -/
theorem exists_strictAnti [Nonempty α] [NoMinOrder α] : ∃ f : ℕ → α, StrictAnti f :=
  exists_strictMono αᵒᵈ
#align nat.exists_strict_anti Nat.exists_strictAnti

end Nat

theorem Int.rel_of_forall_rel_succ_of_lt (r : β → β → Prop) [IsTrans β r] {f : ℤ → β}
    (h : ∀ n, r (f n) (f (n + 1))) ⦃a b : ℤ⦄ (hab : a < b) : r (f a) (f b) := by
  rcases lt.dest hab with ⟨n, rfl⟩
  clear hab
  induction' n with n ihn
  · rw [Int.ofNat_one]
    apply h
  · rw [Int.ofNat_succ, ← Int.add_assoc]
    exact _root_.trans ihn (h _)
#align int.rel_of_forall_rel_succ_of_lt Int.rel_of_forall_rel_succ_of_lt

theorem Int.rel_of_forall_rel_succ_of_le (r : β → β → Prop) [IsRefl β r] [IsTrans β r] {f : ℤ → β}
    (h : ∀ n, r (f n) (f (n + 1))) ⦃a b : ℤ⦄ (hab : a ≤ b) : r (f a) (f b) :=
  hab.eq_or_lt.elim (fun h ↦ h ▸ refl _) fun h' ↦ Int.rel_of_forall_rel_succ_of_lt r h h'
#align int.rel_of_forall_rel_succ_of_le Int.rel_of_forall_rel_succ_of_le

theorem monotone_int_of_le_succ {f : ℤ → α} (hf : ∀ n, f n ≤ f (n + 1)) : Monotone f :=
  Int.rel_of_forall_rel_succ_of_le (· ≤ ·) hf
#align monotone_int_of_le_succ monotone_int_of_le_succ

theorem antitone_int_of_succ_le {f : ℤ → α} (hf : ∀ n, f (n + 1) ≤ f n) : Antitone f :=
  Int.rel_of_forall_rel_succ_of_le (· ≥ ·) hf
#align antitone_int_of_succ_le antitone_int_of_succ_le

theorem strictMono_int_of_lt_succ {f : ℤ → α} (hf : ∀ n, f n < f (n + 1)) : StrictMono f :=
  Int.rel_of_forall_rel_succ_of_lt (· < ·) hf
#align strict_mono_int_of_lt_succ strictMono_int_of_lt_succ

theorem strictAnti_int_of_succ_lt {f : ℤ → α} (hf : ∀ n, f (n + 1) < f n) : StrictAnti f :=
  Int.rel_of_forall_rel_succ_of_lt (· > ·) hf
#align strict_anti_int_of_succ_lt strictAnti_int_of_succ_lt

namespace Int

variable (α) [Preorder α] [Nonempty α] [NoMinOrder α] [NoMaxOrder α]

/-- If `α` is a nonempty preorder with no minimal or maximal elements, then there exists a strictly
monotone function `f : ℤ → α`. -/
theorem exists_strictMono : ∃ f : ℤ → α, StrictMono f := by
  inhabit α
  rcases Nat.exists_strictMono' (default : α) with ⟨f, hf, hf₀⟩
  rcases Nat.exists_strictAnti' (default : α) with ⟨g, hg, hg₀⟩
  refine' ⟨fun n ↦ Int.casesOn n f fun n ↦ g (n + 1), strictMono_int_of_lt_succ _⟩
  rintro (n | _ | n)
  · exact hf n.lt_succ_self
  · show g 1 < f 0
    rw [hf₀, ← hg₀]
    exact hg Nat.zero_lt_one
  · exact hg (Nat.lt_succ_self _)

#align int.exists_strict_mono Int.exists_strictMono

/-- If `α` is a nonempty preorder with no minimal or maximal elements, then there exists a strictly
antitone function `f : ℤ → α`. -/
theorem exists_strictAnti : ∃ f : ℤ → α, StrictAnti f :=
  exists_strictMono αᵒᵈ
#align int.exists_strict_anti Int.exists_strictAnti

end Int

-- TODO@Yael: Generalize the following four to succ orders
/-- If `f` is a monotone function from `ℕ` to a preorder such that `x` lies between `f n` and
  `f (n + 1)`, then `x` doesn't lie in the range of `f`. -/
theorem Monotone.ne_of_lt_of_lt_nat {f : ℕ → α} (hf : Monotone f) (n : ℕ) {x : α} (h1 : f n < x)
    (h2 : x < f (n + 1)) (a : ℕ) : f a ≠ x := by
  rintro rfl
  exact (hf.reflect_lt h1).not_le (Nat.le_of_lt_succ <| hf.reflect_lt h2)
#align monotone.ne_of_lt_of_lt_nat Monotone.ne_of_lt_of_lt_nat

/-- If `f` is an antitone function from `ℕ` to a preorder such that `x` lies between `f (n + 1)` and
`f n`, then `x` doesn't lie in the range of `f`. -/
theorem Antitone.ne_of_lt_of_lt_nat {f : ℕ → α} (hf : Antitone f) (n : ℕ) {x : α}
    (h1 : f (n + 1) < x) (h2 : x < f n) (a : ℕ) : f a ≠ x := by
  rintro rfl
  exact (hf.reflect_lt h2).not_le (Nat.le_of_lt_succ <| hf.reflect_lt h1)
#align antitone.ne_of_lt_of_lt_nat Antitone.ne_of_lt_of_lt_nat

/-- If `f` is a monotone function from `ℤ` to a preorder and `x` lies between `f n` and
  `f (n + 1)`, then `x` doesn't lie in the range of `f`. -/
theorem Monotone.ne_of_lt_of_lt_int {f : ℤ → α} (hf : Monotone f) (n : ℤ) {x : α} (h1 : f n < x)
    (h2 : x < f (n + 1)) (a : ℤ) : f a ≠ x := by
  rintro rfl
  exact (hf.reflect_lt h1).not_le (Int.le_of_lt_add_one <| hf.reflect_lt h2)
#align monotone.ne_of_lt_of_lt_int Monotone.ne_of_lt_of_lt_int

/-- If `f` is an antitone function from `ℤ` to a preorder and `x` lies between `f (n + 1)` and
`f n`, then `x` doesn't lie in the range of `f`. -/
theorem Antitone.ne_of_lt_of_lt_int {f : ℤ → α} (hf : Antitone f) (n : ℤ) {x : α}
    (h1 : f (n + 1) < x) (h2 : x < f n) (a : ℤ) : f a ≠ x := by
  rintro rfl
  exact (hf.reflect_lt h2).not_le (Int.le_of_lt_add_one <| hf.reflect_lt h1)
#align antitone.ne_of_lt_of_lt_int Antitone.ne_of_lt_of_lt_int

theorem StrictMono.id_le {φ : ℕ → ℕ} (h : StrictMono φ) : ∀ n, n ≤ φ n := fun n ↦
  Nat.recOn n (Nat.zero_le _) fun n hn ↦ Nat.succ_le_of_lt (hn.trans_lt <| h <| Nat.lt_succ_self n)
#align strict_mono.id_le StrictMono.id_le

end Preorder

theorem Subtype.mono_coe [Preorder α] (t : Set α) : Monotone ((↑) : Subtype t → α) :=
  fun _ _ ↦ id
#align subtype.mono_coe Subtype.mono_coe

theorem Subtype.strictMono_coe [Preorder α] (t : Set α) :
    StrictMono ((↑) : Subtype t → α) :=
  fun _ _ ↦ id
#align subtype.strict_mono_coe Subtype.strictMono_coe

section Preorder

variable [Preorder α] [Preorder β] [Preorder γ] [Preorder δ] {f : α → γ} {g : β → δ} {a b : α}

theorem monotone_fst : Monotone (@Prod.fst α β) := fun _ _ ↦ And.left
#align monotone_fst monotone_fst

theorem monotone_snd : Monotone (@Prod.snd α β) := fun _ _ ↦ And.right
#align monotone_snd monotone_snd

theorem Monotone.prod_map (hf : Monotone f) (hg : Monotone g) : Monotone (Prod.map f g) :=
  fun _ _ h ↦ ⟨hf h.1, hg h.2⟩
#align monotone.prod_map Monotone.prod_map

theorem Antitone.prod_map (hf : Antitone f) (hg : Antitone g) : Antitone (Prod.map f g) :=
  fun _ _ h ↦ ⟨hf h.1, hg h.2⟩
#align antitone.prod_map Antitone.prod_map

end Preorder

section PartialOrder

variable [PartialOrder α] [PartialOrder β] [Preorder γ] [Preorder δ] {f : α → γ} {g : β → δ}

theorem StrictMono.prod_map (hf : StrictMono f) (hg : StrictMono g) : StrictMono (Prod.map f g) :=
  fun a b ↦ by
  simp only [Prod.lt_iff]
  exact Or.imp (And.imp hf.imp hg.monotone.imp) (And.imp hf.monotone.imp hg.imp)
#align strict_mono.prod_map StrictMono.prod_map

theorem StrictAnti.prod_map (hf : StrictAnti f) (hg : StrictAnti g) : StrictAnti (Prod.map f g) :=
  fun a b ↦ by
  simp only [Prod.lt_iff]
  exact Or.imp (And.imp hf.imp hg.antitone.imp) (And.imp hf.antitone.imp hg.imp)
#align strict_anti.prod_map StrictAnti.prod_map

end PartialOrder

namespace Function

variable [Preorder α]

theorem const_mono : Monotone (const β : α → β → α) := fun _ _ h _ ↦ h
#align function.const_mono Function.const_mono

theorem const_strictMono [Nonempty β] : StrictMono (const β : α → β → α) :=
  fun _ _ ↦ const_lt_const.2
#align function.const_strict_mono Function.const_strictMono

end Function


import tactic

example (n m k : ℕ) : n * (m - k) = n * m - n * k :=
begin
  -- library_search,
  exact nat.mul_sub_left_distrib n m k,
end

-- Al colocar el cursor sobre library_search escribe
--    Try this: exact nat.mul_sub_left_distrib n m k

example (n m k : ℕ) : n * (m - k) = n * m - n * k :=
begin
  exact nat.mul_sub_left_distrib n m k,
end

-- Ver la documentación en https://bit.ly/3dEmh0l


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

! This file was ported from Lean 3 source module topology.metric_space.infsep
! leanprover-community/mathlib commit 5316314b553dcf8c6716541851517c1a9715e22b
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Topology.MetricSpace.Basic

/-!
# Infimum separation

This file defines the extended infimum separation of a set. This is approximately dual to the
diameter of a set, but where the extended diameter of a set is the supremum of the extended distance
between elements of the set, the extended infimum separation is the infimum of the (extended)
distance between *distinct* elements in the set.

We also define the infimum separation as the cast of the extended infimum separation to the reals.
This is the infimum of the distance between distinct elements of the set when in a pseudometric
space.

All lemmas and definitions are in the `set` namespace to give access to dot notation.

## Main definitions
* `Set.einfsep`: Extended infimum separation of a set.
* `Set.infsep`: Infimum separation of a set (when in a pseudometric space).

!-/

variable {α β : Type _}

namespace Set

section Einfsep

open ENNReal

open Function

/-- The "extended infimum separation" of a set with an edist function. -/
noncomputable def einfsep [EDist α] (s : Set α) : ℝ≥0∞ :=
  ⨅ (x ∈ s) (y ∈ s) (_hxy : x ≠ y), edist x y
#align set.einfsep Set.einfsep

section EDist

variable [EDist α] {x y : α} {s t : Set α}

theorem le_einfsep_iff {d} :
    d ≤ s.einfsep ↔ ∀ (x) (_ : x ∈ s) (y) (_ : y ∈ s) (_hxy : x ≠ y), d ≤ edist x y := by
  simp_rw [einfsep, le_infᵢ_iff]
#align set.le_einfsep_iff Set.le_einfsep_iff

theorem einfsep_zero :
    s.einfsep = 0 ↔
      ∀ (C) (_hC : 0 < C), ∃ (x : _)(_ : x ∈ s)(y : _)(_ : y ∈ s)(_hxy : x ≠ y), edist x y < C :=
  by simp_rw [einfsep, ← _root_.bot_eq_zero, infᵢ_eq_bot, infᵢ_lt_iff]
#align set.einfsep_zero Set.einfsep_zero

theorem einfsep_pos :
    0 < s.einfsep ↔
      ∃ (C : _)(_hC : 0 < C), ∀ (x) (_ : x ∈ s) (y) (_ : y ∈ s) (_hxy : x ≠ y), C ≤ edist x y := by
  rw [pos_iff_ne_zero, Ne.def, einfsep_zero]
  simp only [not_forall, not_exists, not_lt]
#align set.einfsep_pos Set.einfsep_pos

theorem einfsep_top :
    s.einfsep = ∞ ↔ ∀ (x) (_ : x ∈ s) (y) (_ : y ∈ s) (_hxy : x ≠ y), edist x y = ∞ := by
  simp_rw [einfsep, infᵢ_eq_top]
#align set.einfsep_top Set.einfsep_top

theorem einfsep_lt_top :
    s.einfsep < ∞ ↔ ∃ (x : _)(_ : x ∈ s)(y : _)(_ : y ∈ s)(_hxy : x ≠ y), edist x y < ∞ := by
  simp_rw [einfsep, infᵢ_lt_iff]
#align set.einfsep_lt_top Set.einfsep_lt_top

theorem einfsep_ne_top :
    s.einfsep ≠ ∞ ↔ ∃ (x : _)(_ : x ∈ s)(y : _)(_ : y ∈ s)(_hxy : x ≠ y), edist x y ≠ ∞ := by
  simp_rw [← lt_top_iff_ne_top, einfsep_lt_top]
#align set.einfsep_ne_top Set.einfsep_ne_top

theorem einfsep_lt_iff {d} :
    s.einfsep < d ↔ ∃ (x : _)(_ : x ∈ s)(y : _)(_ : y ∈ s)(_h : x ≠ y), edist x y < d := by
  simp_rw [einfsep, infᵢ_lt_iff]
#align set.einfsep_lt_iff Set.einfsep_lt_iff

theorem nontrivial_of_einfsep_lt_top (hs : s.einfsep < ∞) : s.Nontrivial := by
  rcases einfsep_lt_top.1 hs with ⟨_, hx, _, hy, hxy, _⟩
  exact ⟨_, hx, _, hy, hxy⟩
#align set.nontrivial_of_einfsep_lt_top Set.nontrivial_of_einfsep_lt_top

theorem nontrivial_of_einfsep_ne_top (hs : s.einfsep ≠ ∞) : s.Nontrivial :=
  nontrivial_of_einfsep_lt_top (lt_top_iff_ne_top.mpr hs)
#align set.nontrivial_of_einfsep_ne_top Set.nontrivial_of_einfsep_ne_top

theorem Subsingleton.einfsep (hs : s.Subsingleton) : s.einfsep = ∞ := by
  rw [einfsep_top]
  exact fun _ hx _ hy hxy => (hxy <| hs hx hy).elim
#align set.subsingleton.einfsep Set.Subsingleton.einfsep

theorem le_einfsep_image_iff {d} {f : β → α} {s : Set β} :
    d ≤ einfsep (f '' s) ↔ ∀ (x) (_ : x ∈ s) (y) (_ : y ∈ s), f x ≠ f y → d ≤ edist (f x) (f y) :=
  by simp_rw [le_einfsep_iff, ball_image_iff]
#align set.le_einfsep_image_iff Set.le_einfsep_image_iff

theorem le_edist_of_le_einfsep {d x} (hx : x ∈ s) {y} (hy : y ∈ s) (hxy : x ≠ y)
    (hd : d ≤ s.einfsep) : d ≤ edist x y :=
  le_einfsep_iff.1 hd x hx y hy hxy
#align set.le_edist_of_le_einfsep Set.le_edist_of_le_einfsep

theorem einfsep_le_edist_of_mem {x} (hx : x ∈ s) {y} (hy : y ∈ s) (hxy : x ≠ y) :
    s.einfsep ≤ edist x y :=
  le_edist_of_le_einfsep hx hy hxy le_rfl
#align set.einfsep_le_edist_of_mem Set.einfsep_le_edist_of_mem

theorem einfsep_le_of_mem_of_edist_le {d x} (hx : x ∈ s) {y} (hy : y ∈ s) (hxy : x ≠ y)
    (hxy' : edist x y ≤ d) : s.einfsep ≤ d :=
  le_trans (einfsep_le_edist_of_mem hx hy hxy) hxy'
#align set.einfsep_le_of_mem_of_edist_le Set.einfsep_le_of_mem_of_edist_le

theorem le_einfsep {d} (h : ∀ (x) (_ : x ∈ s) (y) (_ : y ∈ s) (_hxy : x ≠ y), d ≤ edist x y) :
    d ≤ s.einfsep :=
  le_einfsep_iff.2 h
#align set.le_einfsep Set.le_einfsep

@[simp]
theorem einfsep_empty : (∅ : Set α).einfsep = ∞ :=
  subsingleton_empty.einfsep
#align set.einfsep_empty Set.einfsep_empty

@[simp]
theorem einfsep_singleton : ({x} : Set α).einfsep = ∞ :=
  subsingleton_singleton.einfsep
#align set.einfsep_singleton Set.einfsep_singleton

theorem einfsep_unionᵢ_mem_option {ι : Type _} (o : Option ι) (s : ι → Set α) :
    (⋃ i ∈ o, s i).einfsep = ⨅ i ∈ o, (s i).einfsep := by cases o <;> simp
#align set.einfsep_Union_mem_option Set.einfsep_unionᵢ_mem_option

theorem einfsep_anti (hst : s ⊆ t) : t.einfsep ≤ s.einfsep :=
  le_einfsep fun _x hx _y hy => einfsep_le_edist_of_mem (hst hx) (hst hy)
#align set.einfsep_anti Set.einfsep_anti

theorem einfsep_insert_le : (insert x s).einfsep ≤ ⨅ (y ∈ s) (_hxy : x ≠ y), edist x y := by
  simp_rw [le_infᵢ_iff]
  refine' fun _ hy hxy => einfsep_le_edist_of_mem (mem_insert _ _) (mem_insert_of_mem _ hy) hxy
#align set.einfsep_insert_le Set.einfsep_insert_le

theorem le_einfsep_pair : edist x y ⊓ edist y x ≤ ({x, y} : Set α).einfsep := by
  simp_rw [le_einfsep_iff, inf_le_iff, mem_insert_iff, mem_singleton_iff]
  rintro a (rfl | rfl) b (rfl | rfl) hab <;> simp only [le_refl, true_or, or_true] <;> contradiction
#align set.le_einfsep_pair Set.le_einfsep_pair

theorem einfsep_pair_le_left (hxy : x ≠ y) : ({x, y} : Set α).einfsep ≤ edist x y :=
  einfsep_le_edist_of_mem (mem_insert _ _) (mem_insert_of_mem _ (mem_singleton _)) hxy
#align set.einfsep_pair_le_left Set.einfsep_pair_le_left

theorem einfsep_pair_le_right (hxy : x ≠ y) : ({x, y} : Set α).einfsep ≤ edist y x := by
  rw [pair_comm] ; exact einfsep_pair_le_left hxy.symm
#align set.einfsep_pair_le_right Set.einfsep_pair_le_right

theorem einfsep_pair_eq_inf (hxy : x ≠ y) : ({x, y} : Set α).einfsep = edist x y ⊓ edist y x :=
  le_antisymm (le_inf (einfsep_pair_le_left hxy) (einfsep_pair_le_right hxy)) le_einfsep_pair
#align set.einfsep_pair_eq_inf Set.einfsep_pair_eq_inf

theorem einfsep_eq_infᵢ : s.einfsep = ⨅ d : s.offDiag, (uncurry edist) (d : α × α) := by
  refine' eq_of_forall_le_iff fun _ => _
  simp_rw [le_einfsep_iff, le_infᵢ_iff, imp_forall_iff, SetCoe.forall, Subtype.coe_mk, mem_offDiag,
    Prod.forall, uncurry_apply_pair, and_imp]
#align set.einfsep_eq_infi Set.einfsep_eq_infᵢ

theorem Finite.einfsep (hs : s.Finite) : s.einfsep = hs.offDiag.toFinset.inf (uncurry edist) := by
  refine' eq_of_forall_le_iff fun _ => _
  simp_rw [le_einfsep_iff, imp_forall_iff, Finset.le_inf_iff, Finite.mem_toFinset, mem_offDiag,
    Prod.forall, uncurry_apply_pair, and_imp]
#align set.finite.einfsep Set.Finite.einfsep

theorem Finset.coe_einfsep [DecidableEq α] {s : Finset α} :
    (s : Set α).einfsep = s.offDiag.inf (uncurry edist) := by
  simp_rw [einfsep_of_fintype, ← Finset.coe_offDiag, Finset.toFinset_coe]
#align set.finset.coe_einfsep Set.Finset.coe_einfsep

theorem Nontrivial.einfsep_exists_of_finite [Finite s] (hs : s.Nontrivial) :
    ∃ (x : _)(_ : x ∈ s)(y : _)(_ : y ∈ s)(_hxy : x ≠ y), s.einfsep = edist x y := by
  classical
    cases nonempty_fintype s
    simp_rw [einfsep_of_fintype]
    rcases@Finset.exists_mem_eq_inf _ _ _ _ s.offDiag.toFinset (by simpa) (uncurry edist) with
      ⟨w, hxy, hed⟩
    simp_rw [mem_toFinset] at hxy
    refine' ⟨w.fst, hxy.1, w.snd, hxy.2.1, hxy.2.2, hed⟩
#align set.nontrivial.einfsep_exists_of_finite Set.Nontrivial.einfsep_exists_of_finite

theorem Finite.einfsep_exists_of_nontrivial (hsf : s.Finite) (hs : s.Nontrivial) :
    ∃ (x : _)(_ : x ∈ s)(y : _)(_ : y ∈ s)(_hxy : x ≠ y), s.einfsep = edist x y :=
  letI := hsf.fintype
  hs.einfsep_exists_of_finite
#align set.finite.einfsep_exists_of_nontrivial Set.Finite.einfsep_exists_of_nontrivial

end EDist

section PseudoEMetricSpace

variable [PseudoEMetricSpace α] {x y z : α} {s t : Set α}

theorem einfsep_pair (hxy : x ≠ y) : ({x, y} : Set α).einfsep = edist x y := by
  nth_rw 1 [← min_self (edist x y)]
  convert einfsep_pair_eq_inf hxy using 2
  rw [edist_comm]
#align set.einfsep_pair Set.einfsep_pair

theorem einfsep_insert : einfsep (insert x s) = (⨅ (y ∈ s) (_hxy : x ≠ y), edist x y) ⊓ s.einfsep :=
  by
  refine' le_antisymm (le_min einfsep_insert_le (einfsep_anti (subset_insert _ _))) _
  simp_rw [le_einfsep_iff, inf_le_iff, mem_insert_iff]
  rintro y (rfl | hy) z (rfl | hz) hyz
  · exact False.elim (hyz rfl)
  · exact Or.inl (infᵢ_le_of_le _ (infᵢ₂_le hz hyz))
  · rw [edist_comm]
    exact Or.inl (infᵢ_le_of_le _ (infᵢ₂_le hy hyz.symm))
  · exact Or.inr (einfsep_le_edist_of_mem hy hz hyz)
#align set.einfsep_insert Set.einfsep_insert

theorem einfsep_triple (hxy : x ≠ y) (hyz : y ≠ z) (hxz : x ≠ z) :
    einfsep ({x, y, z} : Set α) = edist x y ⊓ edist x z ⊓ edist y z := by
  simp_rw [einfsep_insert, infᵢ_insert, infᵢ_singleton, einfsep_singleton, inf_top_eq,
    cinfᵢ_pos hxy, cinfᵢ_pos hyz, cinfᵢ_pos hxz]
#align set.einfsep_triple Set.einfsep_triple

theorem le_einfsep_pi_of_le {π : β → Type _} [Fintype β] [∀ b, PseudoEMetricSpace (π b)]
    {s : ∀ b : β, Set (π b)} {c : ℝ≥0∞} (h : ∀ b, c ≤ einfsep (s b)) :
    c ≤ einfsep (Set.pi univ s) := by
  refine' le_einfsep fun x hx y hy hxy => _
  rw [mem_univ_pi] at hx hy
  rcases Function.ne_iff.mp hxy with ⟨i, hi⟩
  exact le_trans (le_einfsep_iff.1 (h i) _ (hx _) _ (hy _) hi) (edist_le_pi_edist _ _ i)
#align set.le_einfsep_pi_of_le Set.le_einfsep_pi_of_le

end PseudoEMetricSpace

section PseudoMetricSpace

variable [PseudoMetricSpace α] {s : Set α}

theorem subsingleton_of_einfsep_eq_top (hs : s.einfsep = ∞) : s.Subsingleton := by
  rw [einfsep_top] at hs
  exact fun _ hx _ hy => of_not_not fun hxy => edist_ne_top _ _ (hs _ hx _ hy hxy)
#align set.subsingleton_of_einfsep_eq_top Set.subsingleton_of_einfsep_eq_top

theorem einfsep_eq_top_iff : s.einfsep = ∞ ↔ s.Subsingleton :=
  ⟨subsingleton_of_einfsep_eq_top, Subsingleton.einfsep⟩
#align set.einfsep_eq_top_iff Set.einfsep_eq_top_iff

theorem Nontrivial.einfsep_ne_top (hs : s.Nontrivial) : s.einfsep ≠ ∞ := by
  contrapose! hs
  rw [not_nontrivial_iff]
  exact subsingleton_of_einfsep_eq_top hs
#align set.nontrivial.einfsep_ne_top Set.Nontrivial.einfsep_ne_top

theorem Nontrivial.einfsep_lt_top (hs : s.Nontrivial) : s.einfsep < ∞ := by
  rw [lt_top_iff_ne_top]
  exact hs.einfsep_ne_top
#align set.nontrivial.einfsep_lt_top Set.Nontrivial.einfsep_lt_top

theorem einfsep_lt_top_iff : s.einfsep < ∞ ↔ s.Nontrivial :=
  ⟨nontrivial_of_einfsep_lt_top, Nontrivial.einfsep_lt_top⟩
#align set.einfsep_lt_top_iff Set.einfsep_lt_top_iff

theorem einfsep_ne_top_iff : s.einfsep ≠ ∞ ↔ s.Nontrivial :=
  ⟨nontrivial_of_einfsep_ne_top, Nontrivial.einfsep_ne_top⟩
#align set.einfsep_ne_top_iff Set.einfsep_ne_top_iff

theorem le_einfsep_of_forall_dist_le {d}
    (h : ∀ (x) (_ : x ∈ s) (y) (_ : y ∈ s) (_hxy : x ≠ y), d ≤ dist x y) :
    ENNReal.ofReal d ≤ s.einfsep :=
  le_einfsep fun x hx y hy hxy => (edist_dist x y).symm ▸ ENNReal.ofReal_le_ofReal (h x hx y hy hxy)
#align set.le_einfsep_of_forall_dist_le Set.le_einfsep_of_forall_dist_le

end PseudoMetricSpace

section EMetricSpace

variable [EMetricSpace α] {x y z : α} {s t : Set α} {C : ℝ≥0∞} {sC : Set ℝ≥0∞}

theorem einfsep_pos_of_finite [Finite s] : 0 < s.einfsep := by
  cases nonempty_fintype s
  by_cases hs : s.Nontrivial
  · rcases hs.einfsep_exists_of_finite with ⟨x, _hx, y, _hy, hxy, hxy'⟩
    exact hxy'.symm ▸ edist_pos.2 hxy
  · rw [not_nontrivial_iff] at hs
    exact hs.einfsep.symm ▸ WithTop.zero_lt_top
#align set.einfsep_pos_of_finite Set.einfsep_pos_of_finite

theorem relatively_discrete_of_finite [Finite s] :
    ∃ (C : _)(_hC : 0 < C), ∀ (x) (_ : x ∈ s) (y) (_ : y ∈ s) (_hxy : x ≠ y), C ≤ edist x y := by
  rw [← einfsep_pos]
  exact einfsep_pos_of_finite
#align set.relatively_discrete_of_finite Set.relatively_discrete_of_finite

theorem Finite.einfsep_pos (hs : s.Finite) : 0 < s.einfsep :=
  letI := hs.fintype
  einfsep_pos_of_finite
#align set.finite.einfsep_pos Set.Finite.einfsep_pos

theorem Finite.relatively_discrete (hs : s.Finite) :
    ∃ (C : _)(_hC : 0 < C), ∀ (x) (_ : x ∈ s) (y) (_ : y ∈ s) (_hxy : x ≠ y), C ≤ edist x y :=
  letI := hs.fintype
  relatively_discrete_of_finite
#align set.finite.relatively_discrete Set.Finite.relatively_discrete

end EMetricSpace

end Einfsep

section Infsep

open ENNReal

open Set Function

/-- The "infimum separation" of a set with an edist function. -/
noncomputable def infsep [EDist α] (s : Set α) : ℝ :=
  ENNReal.toReal s.einfsep
#align set.infsep Set.infsep

section EDist

variable [EDist α] {x y : α} {s : Set α}

theorem infsep_zero : s.infsep = 0 ↔ s.einfsep = 0 ∨ s.einfsep = ∞ := by
  rw [infsep, ENNReal.toReal_eq_zero_iff]
#align set.infsep_zero Set.infsep_zero

theorem infsep_nonneg : 0 ≤ s.infsep :=
  ENNReal.toReal_nonneg
#align set.infsep_nonneg Set.infsep_nonneg

theorem infsep_pos : 0 < s.infsep ↔ 0 < s.einfsep ∧ s.einfsep < ∞ := by
  simp_rw [infsep, ENNReal.toReal_pos_iff]
#align set.infsep_pos Set.infsep_pos

theorem Subsingleton.infsep_zero (hs : s.Subsingleton) : s.infsep = 0 := by
  rw [infsep_zero.mpr]
  right
  exact hs.einfsep
#align set.subsingleton.infsep_zero Set.Subsingleton.infsep_zero

theorem nontrivial_of_infsep_pos (hs : 0 < s.infsep) : s.Nontrivial := by
  contrapose hs
  rw [not_nontrivial_iff] at hs
  exact hs.infsep_zero ▸ lt_irrefl _
#align set.nontrivial_of_infsep_pos Set.nontrivial_of_infsep_pos

theorem infsep_empty : (∅ : Set α).infsep = 0 :=
  subsingleton_empty.infsep_zero
#align set.infsep_empty Set.infsep_empty

theorem infsep_singleton : ({x} : Set α).infsep = 0 :=
  subsingleton_singleton.infsep_zero
#align set.infsep_singleton Set.infsep_singleton

theorem infsep_pair_le_toReal_inf (hxy : x ≠ y) :
    ({x, y} : Set α).infsep ≤ (edist x y ⊓ edist y x).toReal := by
  simp_rw [infsep, einfsep_pair_eq_inf hxy]
  simp
#align set.infsep_pair_le_to_real_inf Set.infsep_pair_le_toReal_inf

end EDist

section PseudoEMetricSpace

variable [PseudoEMetricSpace α] {x y : α} {s : Set α}

theorem infsep_pair_eq_toReal : ({x, y} : Set α).infsep = (edist x y).toReal := by
  by_cases hxy : x = y
  · rw [hxy]
    simp only [infsep_singleton, pair_eq_singleton, edist_self, ENNReal.zero_toReal]
  · rw [infsep, einfsep_pair hxy]
#align set.infsep_pair_eq_to_real Set.infsep_pair_eq_toReal

end PseudoEMetricSpace

section PseudoMetricSpace

variable [PseudoMetricSpace α] {x y z : α} {s t : Set α}

theorem Nontrivial.le_infsep_iff {d} (hs : s.Nontrivial) :
    d ≤ s.infsep ↔ ∀ (x) (_ : x ∈ s) (y) (_ : y ∈ s) (_hxy : x ≠ y), d ≤ dist x y := by
  simp_rw [infsep, ← ENNReal.ofReal_le_iff_le_toReal hs.einfsep_ne_top, le_einfsep_iff, edist_dist,
    ENNReal.ofReal_le_ofReal_iff dist_nonneg]
#align set.nontrivial.le_infsep_iff Set.Nontrivial.le_infsep_iff

theorem Nontrivial.infsep_lt_iff {d} (hs : s.Nontrivial) :
    s.infsep < d ↔ ∃ (x : _)(_ : x ∈ s)(y : _)(_ : y ∈ s)(_hxy : x ≠ y), dist x y < d := by
  rw [← not_iff_not]
  push_neg
  exact hs.le_infsep_iff
#align set.nontrivial.infsep_lt_iff Set.Nontrivial.infsep_lt_iff

theorem Nontrivial.le_infsep {d} (hs : s.Nontrivial)
    (h : ∀ (x) (_ : x ∈ s) (y) (_ : y ∈ s) (_hxy : x ≠ y), d ≤ dist x y) : d ≤ s.infsep :=
  hs.le_infsep_iff.2 h
#align set.nontrivial.le_infsep Set.Nontrivial.le_infsep

theorem le_edist_of_le_infsep {d x} (hx : x ∈ s) {y} (hy : y ∈ s) (hxy : x ≠ y)
    (hd : d ≤ s.infsep) : d ≤ dist x y := by
  by_cases hs : s.Nontrivial
  · exact hs.le_infsep_iff.1 hd x hx y hy hxy
  · rw [not_nontrivial_iff] at hs
    rw [hs.infsep_zero] at hd
    exact le_trans hd dist_nonneg
#align set.le_edist_of_le_infsep Set.le_edist_of_le_infsep

theorem infsep_le_dist_of_mem (hx : x ∈ s) (hy : y ∈ s) (hxy : x ≠ y) : s.infsep ≤ dist x y :=
  le_edist_of_le_infsep hx hy hxy le_rfl
#align set.infsep_le_dist_of_mem Set.infsep_le_dist_of_mem

theorem infsep_le_of_mem_of_edist_le {d x} (hx : x ∈ s) {y} (hy : y ∈ s) (hxy : x ≠ y)
    (hxy' : dist x y ≤ d) : s.infsep ≤ d :=
  le_trans (infsep_le_dist_of_mem hx hy hxy) hxy'
#align set.infsep_le_of_mem_of_edist_le Set.infsep_le_of_mem_of_edist_le

theorem infsep_pair : ({x, y} : Set α).infsep = dist x y := by
  rw [infsep_pair_eq_toReal, edist_dist]
  exact ENNReal.toReal_ofReal dist_nonneg
#align set.infsep_pair Set.infsep_pair

theorem infsep_triple (hxy : x ≠ y) (hyz : y ≠ z) (hxz : x ≠ z) :
    ({x, y, z} : Set α).infsep = dist x y ⊓ dist x z ⊓ dist y z := by
  simp only [infsep, einfsep_triple hxy hyz hxz, ENNReal.toReal_inf, edist_ne_top x y,
    edist_ne_top x z, edist_ne_top y z, dist_edist, Ne.def, inf_eq_top_iff, and_self_iff,
    not_false_iff]
#align set.infsep_triple Set.infsep_triple

theorem Nontrivial.infsep_anti (hs : s.Nontrivial) (hst : s ⊆ t) : t.infsep ≤ s.infsep :=
  ENNReal.toReal_mono hs.einfsep_ne_top (einfsep_anti hst)
#align set.nontrivial.infsep_anti Set.Nontrivial.infsep_anti

theorem infsep_eq_infᵢ [Decidable s.Nontrivial] :
    s.infsep = if s.Nontrivial then ⨅ d : s.offDiag, (uncurry dist) (d : α × α) else 0 := by
  split_ifs with hs
  · have hb : BddBelow (uncurry dist '' s.offDiag) := by
      refine' ⟨0, fun d h => _⟩
      simp_rw [mem_image, Prod.exists, uncurry_apply_pair] at h
      rcases h with ⟨_, _, _, rfl⟩
      exact dist_nonneg
    refine' eq_of_forall_le_iff fun _ => _
    simp_rw [hs.le_infsep_iff, le_cinfᵢ_set_iff (offDiag_nonempty.mpr hs) hb, imp_forall_iff,
      mem_offDiag, Prod.forall, uncurry_apply_pair, and_imp]
  · exact (not_nontrivial_iff.mp hs).infsep_zero
#align set.infsep_eq_infi Set.infsep_eq_infᵢ

theorem Nontrivial.infsep_eq_infᵢ (hs : s.Nontrivial) :
    s.infsep = ⨅ d : s.offDiag, (uncurry dist) (d : α × α) := by
  classical rw [Set.infsep_eq_infᵢ, if_pos hs]
#align set.nontrivial.infsep_eq_infi Set.Nontrivial.infsep_eq_infᵢ

theorem infsep_of_fintype [Decidable s.Nontrivial] [DecidableEq α] [Fintype s] :
    s.infsep = if hs : s.Nontrivial then s.offDiag.toFinset.inf' (by simpa) (uncurry dist) else 0 :=
  by
  split_ifs with hs
  · refine' eq_of_forall_le_iff fun _ => _
    simp_rw [hs.le_infsep_iff, imp_forall_iff, Finset.le_inf'_iff, mem_toFinset, mem_offDiag,
      Prod.forall, uncurry_apply_pair, and_imp]
  · rw [not_nontrivial_iff] at hs
    exact hs.infsep_zero
#align set.infsep_of_fintype Set.infsep_of_fintype

theorem Nontrivial.infsep_of_fintype [DecidableEq α] [Fintype s] (hs : s.Nontrivial) :
    s.infsep = s.offDiag.toFinset.inf' (by simpa) (uncurry dist) := by
  classical rw [Set.infsep_of_fintype, dif_pos hs]
#align set.nontrivial.infsep_of_fintype Set.Nontrivial.infsep_of_fintype

theorem Finite.infsep [Decidable s.Nontrivial] (hsf : s.Finite) :
    s.infsep =
      if hs : s.Nontrivial then hsf.offDiag.toFinset.inf' (by simpa) (uncurry dist) else 0 := by
  split_ifs with hs
  · refine' eq_of_forall_le_iff fun _ => _
    simp_rw [hs.le_infsep_iff, imp_forall_iff, Finset.le_inf'_iff, Finite.mem_toFinset,
      mem_offDiag, Prod.forall, uncurry_apply_pair, and_imp]
  · rw [not_nontrivial_iff] at hs
    exact hs.infsep_zero
#align set.finite.infsep Set.Finite.infsep

theorem Finite.infsep_of_nontrivial (hsf : s.Finite) (hs : s.Nontrivial) :
    s.infsep = hsf.offDiag.toFinset.inf' (by simpa) (uncurry dist) := by
  classical simp_rw [hsf.infsep, dif_pos hs]
#align set.finite.infsep_of_nontrivial Set.Finite.infsep_of_nontrivial

theorem Finset.coe_infsep [DecidableEq α] (s : Finset α) :
    (s : Set α).infsep = if hs : s.offDiag.Nonempty then s.offDiag.inf' hs (uncurry dist) else 0 :=
  by
  have H : (s : Set α).Nontrivial ↔ s.offDiag.Nonempty := by
    rw [← Set.offDiag_nonempty, ← Finset.coe_offDiag, Finset.coe_nonempty]
  split_ifs with hs
  · simp_rw [(H.mpr hs).infsep_of_fintype, ← Finset.coe_offDiag, Finset.toFinset_coe]
  · exact (not_nontrivial_iff.mp (H.mp.mt hs)).infsep_zero
#align finset.coe_infsep Set.Finset.coe_infsep

theorem Finset.coe_infsep_of_offDiag_nonempty [DecidableEq α] {s : Finset α}
    (hs : s.offDiag.Nonempty) : (s : Set α).infsep = s.offDiag.inf' hs (uncurry dist) := by
  rw [Finset.coe_infsep, dif_pos hs]
#align finset.coe_infsep_of_off_diag_nonempty Set.Finset.coe_infsep_of_offDiag_nonempty

theorem Finset.coe_infsep_of_offDiag_empty [DecidableEq α] {s : Finset α} (hs : s.offDiag = ∅) :
    (s : Set α).infsep = 0 := by
  rw [← Finset.not_nonempty_iff_eq_empty] at hs
  rw [Finset.coe_infsep, dif_neg hs]
#align finset.coe_infsep_of_off_diag_empty Set.Finset.coe_infsep_of_offDiag_empty

theorem Nontrivial.infsep_exists_of_finite [Finite s] (hs : s.Nontrivial) :
    ∃ (x : _)(_ : x ∈ s)(y : _)(_ : y ∈ s)(_hxy : x ≠ y), s.infsep = dist x y := by
  classical
    cases nonempty_fintype s
    simp_rw [hs.infsep_of_fintype]
    rcases@Finset.exists_mem_eq_inf' _ _ _ s.offDiag.toFinset (by simpa) (uncurry dist) with
      ⟨w, hxy, hed⟩
    simp_rw [mem_toFinset] at hxy
    exact ⟨w.fst, hxy.1, w.snd, hxy.2.1, hxy.2.2, hed⟩
#align set.nontrivial.infsep_exists_of_finite Set.Nontrivial.infsep_exists_of_finite

theorem Finite.infsep_exists_of_nontrivial (hsf : s.Finite) (hs : s.Nontrivial) :
    ∃ (x : _)(_ : x ∈ s)(y : _)(_ : y ∈ s)(_hxy : x ≠ y), s.infsep = dist x y :=
  letI := hsf.fintype
  hs.infsep_exists_of_finite
#align set.finite.infsep_exists_of_nontrivial Set.Finite.infsep_exists_of_nontrivial

end PseudoMetricSpace

section MetricSpace

variable [MetricSpace α] {s : Set α}

theorem infsep_zero_iff_subsingleton_of_finite [Finite s] : s.infsep = 0 ↔ s.Subsingleton := by
  rw [infsep_zero, einfsep_eq_top_iff, or_iff_right_iff_imp]
  exact fun H => (einfsep_pos_of_finite.ne' H).elim
#align set.infsep_zero_iff_subsingleton_of_finite Set.infsep_zero_iff_subsingleton_of_finite

theorem infsep_pos_iff_nontrivial_of_finite [Finite s] : 0 < s.infsep ↔ s.Nontrivial := by
  rw [infsep_pos, einfsep_lt_top_iff, and_iff_right_iff_imp]
  exact fun _ => einfsep_pos_of_finite
#align set.infsep_pos_iff_nontrivial_of_finite Set.infsep_pos_iff_nontrivial_of_finite

theorem Finite.infsep_zero_iff_subsingleton (hs : s.Finite) : s.infsep = 0 ↔ s.Subsingleton :=
  letI := hs.fintype
  infsep_zero_iff_subsingleton_of_finite
#align set.finite.infsep_zero_iff_subsingleton Set.Finite.infsep_zero_iff_subsingleton

theorem Finite.infsep_pos_iff_nontrivial (hs : s.Finite) : 0 < s.infsep ↔ s.Nontrivial :=
  letI := hs.fintype
  infsep_pos_iff_nontrivial_of_finite
#align set.finite.infsep_pos_iff_nontrivial Set.Finite.infsep_pos_iff_nontrivial

theorem Finset.infsep_zero_iff_subsingleton (s : Finset α) :
    (s : Set α).infsep = 0 ↔ (s : Set α).Subsingleton :=
  infsep_zero_iff_subsingleton_of_finite
#align finset.infsep_zero_iff_subsingleton Set.Finset.infsep_zero_iff_subsingleton

theorem Finset.infsep_pos_iff_nontrivial (s : Finset α) :
    0 < (s : Set α).infsep ↔ (s : Set α).Nontrivial :=
  infsep_pos_iff_nontrivial_of_finite
#align finset.infsep_pos_iff_nontrivial Set.Finset.infsep_pos_iff_nontrivial

end MetricSpace

end Infsep

end Set


import analysis.analytic.basic
import hp.tactic.hp_interactive
universe u

namespace examples

lemma x_sub_x_union_y {α : Type} {A B : set α} : A ⊆ A ∪ B :=
λ a h, or.inl h

lemma y_sub_x_union_y {α : Type} {A B : set α} : B ⊆ A ∪ B :=
λ a h, or.inr h

class met_space (X : Type) extends has_dist X :=
(dist_self : ∀ x, dist x x = 0)
(dist_comm : ∀ x y, dist x y = dist y x)
(dist_triangle : ∀ x y z, dist x z ≤ dist x y + dist y z)

attribute [classnoun "metric space"] met_space

open met_space

variables {X Y: Type} [met_space X] [met_space Y] {A B : set X}

def open_ball (ε : ℝ) (x : X) := {y | dist x y < ε}

def is_open (A : set X) : Prop :=
∀ (y : X), y ∈ A → ∃ (ε : ℝ), (ε > 0) ∧ ∀ (x : X), dist x y < ε → x ∈ A

@[relational_noun_predicate "uniform limit" "uniform limits" "of"]
def is_uniform_limit (f : ℕ → X → Y) (g : X → Y) :=
∀ (ε : ℝ), ε > 0 → ∃ (N : ℕ), ∀ (x : X), ∀ (n : ℕ), (n ≥ N) → dist (f n x) (g x) < ε

@[adjective "continuous"]
def continuous (f : X → Y) :=
∀ (x : X), ∀ (ε : ℝ), (ε > 0) → ∃ (δ : ℝ), (δ > 0) ∧ ∀ (y : X), dist x y < δ → dist (f x) (f y) < ε

@[relational_noun "sequence" "sequences" "of"]
def sequence (X : Type) := ℕ → X

lemma dist_helper {ε η θ}
  {x y z : X}
  (h₁ : dist x y < η)
  (h₂ : dist y z < θ)
  (h₃ : η + θ ≤ ε)
  : dist x z < ε :=
calc _ ≤ dist x y + dist y z : dist_triangle _ _ _
   ... < η + θ               : add_lt_add h₁ h₂
   ... ≤ ε                   : h₃

example (f : sequence (X → Y)) (g : X → Y)
  (h₁ : is_uniform_limit f g)
  (h₂ : ∀ n, continuous (f n)) : continuous g :=
begin
  assume (x : X) (ε : ℝ) (ε_pos : ε > 0),
  have h₄ : ε / 3 > 0,
    show ε / 3 > 0, apply div_pos ε_pos,
    show 0 < (3 : ℝ), norm_num,
  obtain ⟨N, h₅⟩ : ∃ N, ∀ (x : X) (n : ℕ), n ≥ N → dist (f n x) (g x) < ε / 3,
    apply h₁ (ε / 3) h₄,
  obtain ⟨δ, δ_pos, h₆⟩ : ∃ δ, δ > 0 ∧ ∀ y, dist x y < δ → dist (f N x) (f N y) < ε / 3,
    apply h₂ N x (ε / 3) h₄,
  existsi δ,
  existsi δ_pos,
  assume (y : X) (h₃ : dist x y < δ),
  show dist (g x) (g y) < ε,
  apply dist_helper,
  calc dist (g x) (f N x) = dist (f N x) (g x) : by rw examples.met_space.dist_comm
                      ... < ε / 3 : by apply h₅ x N (le_refl N),
  show dist (f N x) (g y) < (ε / 3 + ε / 3),
    apply dist_helper,
  show dist (f N x) (f N y) < ε / 3,
    apply h₆ y h₃,
  show dist (f N y) (g y) < ε / 3,
    apply h₅ y N (le_refl N),
  show ε / 3 + ε / 3 ≤ ε / 3 + ε / 3,
    apply le_refl,
  show ε / 3 + (ε / 3 + ε / 3) ≤ ε,
    apply le_of_eq,
    show ε / 3 + (ε / 3 + ε / 3) = ε, by ring
end

example {A B : set X} : is_open A → is_open B → is_open (A ∩ B)
| oa ob y ⟨ha, hb⟩ :=
  let ⟨εa, h2a, h3a⟩ := oa y ha in
  let ⟨εb, h2b, h3b⟩ := ob y hb in
  ⟨ εa ⊓ εb
  , lt_min h2a h2b
  , λ x dh,
    ⟨ h3a _ $ lt_of_lt_of_le dh $ min_le_left  _ _
    , h3b _ $ lt_of_lt_of_le dh $ min_le_right _ _
    ⟩
  ⟩

end examples


/-
Copyright (c) 2017 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johannes Hölzl, Yaël Dillies
-/
import order.complete_lattice
import order.directed
import logic.equiv.set

/-!
# Frames, completely distributive lattices and Boolean algebras

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

In this file we define and provide API for frames, completely distributive lattices and completely
distributive Boolean algebras.

## Typeclasses

* `order.frame`: Frame: A complete lattice whose `⊓` distributes over `⨆`.
* `order.coframe`: Coframe: A complete lattice whose `⊔` distributes over `⨅`.
* `complete_distrib_lattice`: Completely distributive lattices: A complete lattice whose `⊓` and `⊔`
  distribute over `⨆` and `⨅` respectively.
* `complete_boolean_algebra`: Completely distributive Boolean algebra: A Boolean algebra whose `⊓`
  and `⊔` distribute over `⨆` and `⨅` respectively.

A set of opens gives rise to a topological space precisely if it forms a frame. Such a frame is also
completely distributive, but not all frames are. `filter` is a coframe but not a completely
distributive lattice.

## TODO

Add instances for `prod`

## References

* [Wikipedia, *Complete Heyting algebra*](https://en.wikipedia.org/wiki/Complete_Heyting_algebra)
* [Francis Borceux, *Handbook of Categorical Algebra III*][borceux-vol3]
-/

set_option old_structure_cmd true

open function set

universes u v w
variables {α : Type u} {β : Type v} {ι : Sort w} {κ : ι → Sort*}

/-- A frame, aka complete Heyting algebra, is a complete lattice whose `⊓` distributes over `⨆`. -/
class order.frame (α : Type*) extends complete_lattice α :=
(inf_Sup_le_supr_inf (a : α) (s : set α) : a ⊓ Sup s ≤ ⨆ b ∈ s, a ⊓ b)

/-- A coframe, aka complete Brouwer algebra or complete co-Heyting algebra, is a complete lattice
whose `⊔` distributes over `⨅`. -/
class order.coframe (α : Type*) extends complete_lattice α :=
(infi_sup_le_sup_Inf (a : α) (s : set α) : (⨅ b ∈ s, a ⊔ b) ≤ a ⊔ Inf s)

open order

/-- A completely distributive lattice is a complete lattice whose `⊔` and `⊓` respectively
distribute over `⨅` and `⨆`. -/
class complete_distrib_lattice (α : Type*) extends frame α :=
(infi_sup_le_sup_Inf : ∀ a s, (⨅ b ∈ s, a ⊔ b) ≤ a ⊔ Inf s)

@[priority 100] -- See note [lower instance priority]
instance complete_distrib_lattice.to_coframe [complete_distrib_lattice α] : coframe α :=
{ .. ‹complete_distrib_lattice α› }

section frame
variables [frame α] {s t : set α} {a b : α}

instance order_dual.coframe : coframe αᵒᵈ :=
{ infi_sup_le_sup_Inf := frame.inf_Sup_le_supr_inf, ..order_dual.complete_lattice α }

lemma inf_Sup_eq : a ⊓ Sup s = ⨆ b ∈ s, a ⊓ b :=
(frame.inf_Sup_le_supr_inf _ _).antisymm supr_inf_le_inf_Sup

lemma Sup_inf_eq : Sup s ⊓ b = ⨆ a ∈ s, a ⊓ b :=
by simpa only [inf_comm] using @inf_Sup_eq α _ s b

lemma supr_inf_eq (f : ι → α) (a : α) : (⨆ i, f i) ⊓ a = ⨆ i, f i ⊓ a :=
by rw [supr, Sup_inf_eq, supr_range]

lemma inf_supr_eq (a : α) (f : ι → α) : a ⊓ (⨆ i, f i) = ⨆ i, a ⊓ f i :=
by simpa only [inf_comm] using supr_inf_eq f a

lemma bsupr_inf_eq {f : Π i, κ i → α} (a : α) : (⨆ i j, f i j) ⊓ a = ⨆ i j, f i j ⊓ a :=
by simp only [supr_inf_eq]

lemma inf_bsupr_eq {f : Π i, κ i → α} (a : α) : a ⊓ (⨆ i j, f i j) = ⨆ i j, a ⊓ f i j :=
by simp only [inf_supr_eq]

lemma supr_inf_supr {ι ι' : Type*} {f : ι → α} {g : ι' → α} :
  (⨆ i, f i) ⊓ (⨆ j, g j) = ⨆ i : ι × ι', f i.1 ⊓ g i.2 :=
by simp only [inf_supr_eq, supr_inf_eq, supr_prod]

lemma bsupr_inf_bsupr {ι ι' : Type*} {f : ι → α} {g : ι' → α} {s : set ι} {t : set ι'} :
  (⨆ i ∈ s, f i) ⊓ (⨆ j ∈ t, g j) = ⨆ p ∈ s ×ˢ t, f (p : ι × ι').1 ⊓ g p.2 :=
begin
  simp only [supr_subtype', supr_inf_supr],
  exact (equiv.surjective _).supr_congr (equiv.set.prod s t).symm (λ x, rfl)
end

lemma Sup_inf_Sup : Sup s ⊓ Sup t = ⨆ p ∈ s ×ˢ t, (p : α × α).1 ⊓ p.2 :=
by simp only [Sup_eq_supr, bsupr_inf_bsupr]

lemma supr_disjoint_iff {f : ι → α} : disjoint (⨆ i, f i) a ↔ ∀ i, disjoint (f i) a :=
by simp only [disjoint_iff, supr_inf_eq, supr_eq_bot]

lemma disjoint_supr_iff {f : ι → α} : disjoint a (⨆ i, f i) ↔ ∀ i, disjoint a (f i) :=
by simpa only [disjoint.comm] using supr_disjoint_iff

lemma supr₂_disjoint_iff {f : Π i, κ i → α} :
  disjoint (⨆ i j, f i j) a ↔ ∀ i j, disjoint (f i j) a :=
by simp_rw supr_disjoint_iff

lemma disjoint_supr₂_iff {f : Π i, κ i → α} :
  disjoint a (⨆ i j, f i j) ↔ ∀ i j, disjoint a (f i j) :=
by simp_rw disjoint_supr_iff

lemma Sup_disjoint_iff {s : set α} : disjoint (Sup s) a ↔ ∀ b ∈ s, disjoint b a :=
by simp only [disjoint_iff, Sup_inf_eq, supr_eq_bot]

lemma disjoint_Sup_iff {s : set α} : disjoint a (Sup s) ↔ ∀ b ∈ s, disjoint a b :=
by simpa only [disjoint.comm] using Sup_disjoint_iff

lemma supr_inf_of_monotone {ι : Type*} [preorder ι] [is_directed ι (≤)] {f g : ι → α}
  (hf : monotone f) (hg : monotone g) :
  (⨆ i, f i ⊓ g i) = (⨆ i, f i) ⊓ (⨆ i, g i) :=
begin
  refine (le_supr_inf_supr f g).antisymm _,
  rw [supr_inf_supr],
  refine supr_mono' (λ i, _),
  rcases directed_of (≤) i.1 i.2 with ⟨j, h₁, h₂⟩,
  exact ⟨j, inf_le_inf (hf h₁) (hg h₂)⟩
end

lemma supr_inf_of_antitone {ι : Type*} [preorder ι] [is_directed ι (swap (≤))] {f g : ι → α}
  (hf : antitone f) (hg : antitone g) :
  (⨆ i, f i ⊓ g i) = (⨆ i, f i) ⊓ (⨆ i, g i) :=
@supr_inf_of_monotone α _ ιᵒᵈ _ _ f g hf.dual_left hg.dual_left

instance pi.frame {ι : Type*} {π : ι → Type*} [Π i, frame (π i)] : frame (Π i, π i) :=
{ inf_Sup_le_supr_inf := λ a s i,
    by simp only [complete_lattice.Sup, Sup_apply, supr_apply, pi.inf_apply, inf_supr_eq,
      ← supr_subtype''],
  ..pi.complete_lattice }

@[priority 100] -- see Note [lower instance priority]
instance frame.to_distrib_lattice : distrib_lattice α :=
distrib_lattice.of_inf_sup_le $ λ a b c,
  by rw [←Sup_pair, ←Sup_pair, inf_Sup_eq, ←Sup_image, image_pair]

end frame

section coframe
variables [coframe α] {s t : set α} {a b : α}

instance order_dual.frame : frame αᵒᵈ :=
{ inf_Sup_le_supr_inf := coframe.infi_sup_le_sup_Inf, ..order_dual.complete_lattice α }

lemma sup_Inf_eq : a ⊔ Inf s = ⨅ b ∈ s, a ⊔ b := @inf_Sup_eq αᵒᵈ _ _ _
lemma Inf_sup_eq : Inf s ⊔ b = ⨅ a ∈ s, a ⊔ b := @Sup_inf_eq αᵒᵈ _ _ _

lemma infi_sup_eq (f : ι → α) (a : α) : (⨅ i, f i) ⊔ a = ⨅ i, f i ⊔ a := @supr_inf_eq αᵒᵈ _ _ _ _
lemma sup_infi_eq (a : α) (f : ι → α) : a ⊔ (⨅ i, f i) = ⨅ i, a ⊔ f i := @inf_supr_eq αᵒᵈ _ _ _ _

lemma binfi_sup_eq {f : Π i, κ i → α} (a : α) : (⨅ i j, f i j) ⊔ a = ⨅ i j, f i j ⊔ a :=
@bsupr_inf_eq αᵒᵈ _ _ _ _ _

lemma sup_binfi_eq {f : Π i, κ i → α} (a : α) : a ⊔ (⨅ i j, f i j) = ⨅ i j, a ⊔ f i j :=
@inf_bsupr_eq αᵒᵈ _ _ _ _ _

lemma infi_sup_infi {ι ι' : Type*} {f : ι → α} {g : ι' → α} :
  (⨅ i, f i) ⊔ (⨅ i, g i) = ⨅ i : ι × ι', f i.1 ⊔ g i.2 :=
@supr_inf_supr αᵒᵈ _ _ _ _ _

lemma binfi_sup_binfi {ι ι' : Type*} {f : ι → α} {g : ι' → α} {s : set ι} {t : set ι'} :
  (⨅ i ∈ s, f i) ⊔ (⨅ j ∈ t, g j) = ⨅ p ∈ s ×ˢ t, f (p : ι × ι').1 ⊔ g p.2 :=
@bsupr_inf_bsupr αᵒᵈ _ _ _ _ _ _ _

theorem Inf_sup_Inf : Inf s ⊔ Inf t = (⨅ p ∈ s ×ˢ t, (p : α × α).1 ⊔ p.2) :=
@Sup_inf_Sup αᵒᵈ _ _ _

lemma infi_sup_of_monotone {ι : Type*} [preorder ι] [is_directed ι (swap (≤))] {f g : ι → α}
  (hf : monotone f) (hg : monotone g) :
  (⨅ i, f i ⊔ g i) = (⨅ i, f i) ⊔ (⨅ i, g i) :=
supr_inf_of_antitone hf.dual_right hg.dual_right

lemma infi_sup_of_antitone {ι : Type*} [preorder ι] [is_directed ι (≤)] {f g : ι → α}
  (hf : antitone f) (hg : antitone g) :
  (⨅ i, f i ⊔ g i) = (⨅ i, f i) ⊔ (⨅ i, g i) :=
supr_inf_of_monotone hf.dual_right hg.dual_right

instance pi.coframe {ι : Type*} {π : ι → Type*} [Π i, coframe (π i)] : coframe (Π i, π i) :=
{ Inf := Inf,
  infi_sup_le_sup_Inf := λ a s i,
    by simp only [←sup_infi_eq, Inf_apply, ←infi_subtype'', infi_apply, pi.sup_apply],
  ..pi.complete_lattice }

@[priority 100] -- see Note [lower instance priority]
instance coframe.to_distrib_lattice : distrib_lattice α :=
{ le_sup_inf := λ a b c, by rw [←Inf_pair, ←Inf_pair, sup_Inf_eq, ←Inf_image, image_pair],
  ..‹coframe α› }

end coframe

section complete_distrib_lattice
variables [complete_distrib_lattice α] {a b : α} {s t : set α}

instance : complete_distrib_lattice αᵒᵈ := { ..order_dual.frame, ..order_dual.coframe }

instance pi.complete_distrib_lattice {ι : Type*} {π : ι → Type*}
  [Π i, complete_distrib_lattice (π i)] : complete_distrib_lattice (Π i, π i) :=
{ ..pi.frame, ..pi.coframe }

end complete_distrib_lattice

/-- A complete Boolean algebra is a completely distributive Boolean algebra. -/
class complete_boolean_algebra α extends boolean_algebra α, complete_distrib_lattice α

instance pi.complete_boolean_algebra {ι : Type*} {π : ι → Type*}
  [∀ i, complete_boolean_algebra (π i)] : complete_boolean_algebra (Π i, π i) :=
{ .. pi.boolean_algebra, .. pi.complete_distrib_lattice }

instance Prop.complete_boolean_algebra : complete_boolean_algebra Prop :=
{ infi_sup_le_sup_Inf := λ p s, iff.mp $
    by simp only [forall_or_distrib_left, complete_lattice.Inf, infi_Prop_eq, sup_Prop_eq],
  inf_Sup_le_supr_inf := λ p s, iff.mp $
    by simp only [complete_lattice.Sup, exists_and_distrib_left, inf_Prop_eq, supr_Prop_eq],
  .. Prop.boolean_algebra, .. Prop.complete_lattice }

section complete_boolean_algebra
variables [complete_boolean_algebra α] {a b : α} {s : set α} {f : ι → α}

theorem compl_infi : (infi f)ᶜ = (⨆ i, (f i)ᶜ) :=
le_antisymm
  (compl_le_of_compl_le $ le_infi $ λ i, compl_le_of_compl_le $ le_supr (compl ∘ f) i)
  (supr_le $ λ i, compl_le_compl $ infi_le _ _)

theorem compl_supr : (supr f)ᶜ = (⨅ i, (f i)ᶜ) :=
compl_injective (by simp [compl_infi])

lemma compl_Inf : (Inf s)ᶜ = (⨆ i ∈ s, iᶜ) := by simp only [Inf_eq_infi, compl_infi]
lemma compl_Sup : (Sup s)ᶜ = (⨅ i ∈ s, iᶜ) := by simp only [Sup_eq_supr, compl_supr]
lemma compl_Inf' : (Inf s)ᶜ = Sup (compl '' s) := compl_Inf.trans Sup_image.symm
lemma compl_Sup' : (Sup s)ᶜ = Inf (compl '' s) := compl_Sup.trans Inf_image.symm

end complete_boolean_algebra

section lift

/-- Pullback an `order.frame` along an injection. -/
@[reducible] -- See note [reducible non-instances]
protected def function.injective.frame [has_sup α] [has_inf α] [has_Sup α] [has_Inf α] [has_top α]
  [has_bot α] [frame β] (f : α → β) (hf : injective f) (map_sup : ∀ a b, f (a ⊔ b) = f a ⊔ f b)
  (map_inf : ∀ a b, f (a ⊓ b) = f a ⊓ f b) (map_Sup : ∀ s, f (Sup s) = ⨆ a ∈ s, f a)
  (map_Inf : ∀ s, f (Inf s) = ⨅ a ∈ s, f a) (map_top : f ⊤ = ⊤) (map_bot : f ⊥ = ⊥) :
  frame α :=
{ inf_Sup_le_supr_inf := λ a s, begin
    change f (a ⊓ Sup s) ≤ f _,
    rw [←Sup_image, map_inf, map_Sup s, inf_bsupr_eq],
    simp_rw ←map_inf,
    exact ((map_Sup _).trans supr_image).ge,
  end,
  ..hf.complete_lattice f map_sup map_inf map_Sup map_Inf map_top map_bot }

/-- Pullback an `order.coframe` along an injection. -/
@[reducible] -- See note [reducible non-instances]
protected def function.injective.coframe [has_sup α] [has_inf α] [has_Sup α] [has_Inf α] [has_top α]
  [has_bot α] [coframe β] (f : α → β) (hf : injective f) (map_sup : ∀ a b, f (a ⊔ b) = f a ⊔ f b)
  (map_inf : ∀ a b, f (a ⊓ b) = f a ⊓ f b) (map_Sup : ∀ s, f (Sup s) = ⨆ a ∈ s, f a)
  (map_Inf : ∀ s, f (Inf s) = ⨅ a ∈ s, f a) (map_top : f ⊤ = ⊤) (map_bot : f ⊥ = ⊥) :
  coframe α :=
{ infi_sup_le_sup_Inf := λ a s, begin
    change f _ ≤ f (a ⊔ Inf s),
    rw [←Inf_image, map_sup, map_Inf s, sup_binfi_eq],
    simp_rw ←map_sup,
    exact ((map_Inf _).trans infi_image).le,
  end,
  ..hf.complete_lattice f map_sup map_inf map_Sup map_Inf map_top map_bot }

/-- Pullback a `complete_distrib_lattice` along an injection. -/
@[reducible] -- See note [reducible non-instances]
protected def function.injective.complete_distrib_lattice [has_sup α] [has_inf α] [has_Sup α]
  [has_Inf α] [has_top α] [has_bot α] [complete_distrib_lattice β]
  (f : α → β) (hf : function.injective f) (map_sup : ∀ a b, f (a ⊔ b) = f a ⊔ f b)
  (map_inf : ∀ a b, f (a ⊓ b) = f a ⊓ f b) (map_Sup : ∀ s, f (Sup s) = ⨆ a ∈ s, f a)
  (map_Inf : ∀ s, f (Inf s) = ⨅ a ∈ s, f a) (map_top : f ⊤ = ⊤) (map_bot : f ⊥ = ⊥) :
  complete_distrib_lattice α :=
{ ..hf.frame f map_sup map_inf map_Sup map_Inf map_top map_bot,
  ..hf.coframe f map_sup map_inf map_Sup map_Inf map_top map_bot }

/-- Pullback a `complete_boolean_algebra` along an injection. -/
@[reducible] -- See note [reducible non-instances]
protected def function.injective.complete_boolean_algebra [has_sup α] [has_inf α] [has_Sup α]
  [has_Inf α] [has_top α] [has_bot α] [has_compl α] [has_sdiff α] [complete_boolean_algebra β]
  (f : α → β) (hf : function.injective f) (map_sup : ∀ a b, f (a ⊔ b) = f a ⊔ f b)
  (map_inf : ∀ a b, f (a ⊓ b) = f a ⊓ f b) (map_Sup : ∀ s, f (Sup s) = ⨆ a ∈ s, f a)
  (map_Inf : ∀ s, f (Inf s) = ⨅ a ∈ s, f a) (map_top : f ⊤ = ⊤) (map_bot : f ⊥ = ⊥)
  (map_compl : ∀ a, f aᶜ = (f a)ᶜ) (map_sdiff : ∀ a b, f (a \ b) = f a \ f b) :
  complete_boolean_algebra α :=
{ ..hf.complete_distrib_lattice f map_sup map_inf map_Sup map_Inf map_top map_bot,
  ..hf.boolean_algebra f map_sup map_inf map_top map_bot map_compl map_sdiff }

end lift

namespace punit
variables (s : set punit.{u+1}) (x y : punit.{u+1})

instance : complete_boolean_algebra punit :=
by refine_struct
{ Sup := λ _, star,
  Inf := λ _, star,
  ..punit.boolean_algebra };
    intros; trivial <|> simp only [eq_iff_true_of_subsingleton, not_true, and_false]

@[simp] lemma Sup_eq : Sup s = star := rfl
@[simp] lemma Inf_eq : Inf s = star := rfl

end punit


import data.nat.basic
import data.int.basic
import data.rat.basic
import data.real.basic
import data.complex.basic
import data.list

import tactic
import tactic.rewrite_search.frontend

namespace notes

section
  parameters (G : Type) [has_mul G]
  -- parameters (mul_assoc : ∀ a b c : G, a * (b * c) = (a * b) * c)
  -- parameters (mul_comm : ∀ a b : G, a * b = b * a)

  def is_left_id  (e : G) : Prop := ∀ g : G, e * g = g
  def is_right_id (e : G) : Prop := ∀ g : G, g * e = g

  section
    parameters (e₁ : G) (he₁ : is_left_id e₁)
    parameters (e₂ : G) (he₂ : is_right_id e₂)

    lemma left_id_eq_right_id : e₁ = e₂ := eq.trans (eq.symm (he₂ e₁)) (he₁ e₂)
  end

  def is_id (e : G) : Prop := is_left_id e ∧ is_right_id e

  section
    parameters (e : G) (he : is_id e)
    parameters (e' : G) (he' : is_id e')

    lemma id_unique : e = e' :=
      left_id_eq_right_id _ he.left _ he'.right
  end

  section
    parameters (e : G) (he : is_left_id e ∧ is_right_id e)
    parameters (g : G)

    def has_left_inv_of  (h : G) : Prop := h * g = e
    def has_right_inv_of (h : G) : Prop := g * h = e

    section
      parameters (mul_assoc' : ∀ a b c : G, a * (b * c) = (a * b) * c)
      parameters (h₁ : G) (hh₁ : has_left_inv_of h₁)
      parameters (h₂ : G) (hh₂ : has_right_inv_of h₂)

      -- h₁ = h₁ * e = h₁ * (g * h₂) = (h₁ * g) * h₂ = e * h₂ = h₂
      include g e he h₁ hh₁ h₂ hh₂ mul_assoc'
      lemma left_inv_eq_right_inv : h₁ = h₂ := by
        calc  h₁
            = h₁ * e        : eq.symm (he.right h₁)
        ... = h₁ * (g * h₂) : congr_arg2 (*) rfl hh₂.symm
        ... = (h₁ * g) * h₂ : mul_assoc' _ _ _
        ... = e * h₂        : congr_arg2 (*) hh₁ rfl
        ... = h₂            : he.left h₂

      -- I don't know why tactics are not working properly here!
      -- Probably another reason why I have to invent my own prover...
    end

    def has_inv_of (h : G) : Prop := h * g = e ∧ g * h = e

    section
      parameters (mul_assoc' : ∀ a b c : G, a * (b * c) = (a * b) * c)
      parameters (h : G) (hh : has_inv_of h)
      parameters (h' : G) (hh' : has_inv_of h')

      include g e he h hh h' hh' mul_assoc'
      lemma inv_unique : h = h' :=
        left_inv_eq_right_inv mul_assoc' _ hh.left _ hh'.right
    end
  end
end

@[class]
structure group (G : Type) : Type :=
  (mul : G → G → G)
  (mul_assoc : ∀ a b c : G, mul (mul a b) c = mul a (mul b c))
  (e : G)
  (he : ∀ g : G, mul e g = g ∧ mul g e = g)
  (i : G → G)
  (hi : ∀ g : G, mul (i g) g = e ∧ mul g (i g) = e)

end notes


-- Producto_de_potencias_de_la_misma_base_en_monoides.lean
-- Producto_de_potencias_de_la_misma_base_en_monoides
-- José A. Alonso Jiménez
-- Sevilla, 30 de junio de 2021
-- ---------------------------------------------------------------------

-- ---------------------------------------------------------------------
-- En los [monoides](https://en.wikipedia.org/wiki/Monoid) se define la
-- potencia con exponentes naturales. En Lean la potencia x^n se
-- se caracteriza por los siguientes lemas:
--    pow_zero : x^0 = 1
--    pow_succ : x^(succ n) = x * x^n
--
-- Demostrar que
--    x^(m + n) = x^m * x^n
-- ---------------------------------------------------------------------

import algebra.group_power.basic
open monoid nat

variables {M : Type} [monoid M]
variable  x : M
variables (m n : ℕ)

-- Para que no use la notación con puntos
set_option pp.structure_projections false

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

example :
  x^(m + n) = x^m * x^n :=
begin
  induction m with m HI,
  { calc x^(0 + n)
         = x^n             : congr_arg ((^) x) (nat.zero_add n)
     ... = 1 * x^n         : (monoid.one_mul (x^n)).symm
     ... = x^0 * x^n       : congr_arg (* (x^n)) (pow_zero x).symm, },
  { calc x^(succ m + n)
         = x^succ (m + n)  : congr_arg ((^) x) (succ_add m n)
     ... = x * x^(m + n)   : pow_succ x (m + n)
     ... = x * (x^m * x^n) : congr_arg ((*) x) HI
     ... = (x * x^m) * x^n : (monoid.mul_assoc x (x^m) (x^n)).symm
     ... = x^succ m * x^n  : congr_arg (* x^n) (pow_succ x m).symm, },
end

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

example :
  x^(m + n) = x^m * x^n :=
begin
  induction m with m HI,
  { calc x^(0 + n)
         = x^n             : by simp only [nat.zero_add]
     ... = 1 * x^n         : by simp only [monoid.one_mul]
     ... = x^0 * x^n       : by simp [pow_zero] },
  { calc x^(succ m + n)
         = x^succ (m + n)  : by simp only [succ_add]
     ... = x * x^(m + n)   : by simp only [pow_succ]
     ... = x * (x^m * x^n) : by simp only [HI]
     ... = (x * x^m) * x^n : (monoid.mul_assoc x (x^m) (x^n)).symm
     ... = x^succ m * x^n  : by simp only [pow_succ], },
end

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

example :
  x^(m + n) = x^m * x^n :=
begin
  induction m with m HI,
  { calc x^(0 + n)
         = x^n             : by simp [nat.zero_add]
     ... = 1 * x^n         : by simp
     ... = x^0 * x^n       : by simp, },
  { calc x^(succ m + n)
         = x^succ (m + n)  : by simp [succ_add]
     ... = x * x^(m + n)   : by simp [pow_succ]
     ... = x * (x^m * x^n) : by simp [HI]
     ... = (x * x^m) * x^n : (monoid.mul_assoc x (x^m) (x^n)).symm
     ... = x^succ m * x^n  : by simp [pow_succ], },
end

-- 4ª demostración
-- ===============

example :
  x^(m + n) = x^m * x^n :=
begin
  induction m with m HI,
  { show x^(0 + n) = x^0 * x^n,
      by simp [nat.zero_add] },
  { show x^(succ m + n) = x^succ m * x^n,
      by finish [succ_add,
                 HI,
                 monoid.mul_assoc,
                 pow_succ], },
end

-- 5ª demostración
-- ===============

example :
  x^(m + n) = x^m * x^n :=
pow_add x m n