/-
Copyright (c) 2018 Kenny Lau. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kenny Lau

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

/-!
# Multiplicative opposite and algebraic operations on it

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

In this file we define `mul_opposite α = αᵐᵒᵖ` to be the multiplicative opposite of `α`. It inherits
all additive algebraic structures on `α` (in other files), and reverses the order of multipliers in
multiplicative structures, i.e., `op (x * y) = op y * op x`, where `mul_opposite.op` is the
canonical map from `α` to `αᵐᵒᵖ`.

We also define `add_opposite α = αᵃᵒᵖ` to be the additive opposite of `α`. It inherits all
multiplicative algebraic structures on `α` (in other files), and reverses the order of summands in
additive structures, i.e. `op (x + y) = op y + op x`, where `add_opposite.op` is the canonical map
from `α` to `αᵃᵒᵖ`.

## Notation

* `αᵐᵒᵖ = mul_opposite α`
* `αᵃᵒᵖ = add_opposite α`

## Tags

multiplicative opposite, additive opposite
-/

universe u v

open Function

#print MulOpposite /-
/-- Multiplicative opposite of a type. This type inherits all additive structures on `α` and
reverses left and right in multiplication.-/
@[to_additive
      "Additive opposite of a type. This type inherits all multiplicative structures on\n`α` and reverses left and right in addition."]
def MulOpposite (α : Type u) : Type u :=
  α
#align mul_opposite MulOpposite
#align add_opposite AddOpposite
-/

-- mathport name: «expr ᵐᵒᵖ»
postfix:max "ᵐᵒᵖ" => MulOpposite

-- mathport name: «expr ᵃᵒᵖ»
postfix:max "ᵃᵒᵖ" => AddOpposite

variable {α : Type u}

namespace MulOpposite

#print MulOpposite.op /-
/-- The element of `mul_opposite α` that represents `x : α`. -/
@[pp_nodot, to_additive "The element of `αᵃᵒᵖ` that represents `x : α`."]
def op : α → αᵐᵒᵖ :=
  id
#align mul_opposite.op MulOpposite.op
#align add_opposite.op AddOpposite.op
-/

#print MulOpposite.unop /-
/-- The element of `α` represented by `x : αᵐᵒᵖ`. -/
@[pp_nodot, to_additive "The element of `α` represented by `x : αᵃᵒᵖ`."]
def unop : αᵐᵒᵖ → α :=
  id
#align mul_opposite.unop MulOpposite.unop
#align add_opposite.unop AddOpposite.unop
-/

attribute [pp_nodot] AddOpposite.op AddOpposite.unop

#print MulOpposite.unop_op /-
@[simp, to_additive]
theorem unop_op (x : α) : unop (op x) = x :=
  rfl
#align mul_opposite.unop_op MulOpposite.unop_op
#align add_opposite.unop_op AddOpposite.unop_op
-/

#print MulOpposite.op_unop /-
@[simp, to_additive]
theorem op_unop (x : αᵐᵒᵖ) : op (unop x) = x :=
  rfl
#align mul_opposite.op_unop MulOpposite.op_unop
#align add_opposite.op_unop AddOpposite.op_unop
-/

#print MulOpposite.op_comp_unop /-
@[simp, to_additive]
theorem op_comp_unop : (op : α → αᵐᵒᵖ) ∘ unop = id :=
  rfl
#align mul_opposite.op_comp_unop MulOpposite.op_comp_unop
#align add_opposite.op_comp_unop AddOpposite.op_comp_unop
-/

#print MulOpposite.unop_comp_op /-
@[simp, to_additive]
theorem unop_comp_op : (unop : αᵐᵒᵖ → α) ∘ op = id :=
  rfl
#align mul_opposite.unop_comp_op MulOpposite.unop_comp_op
#align add_opposite.unop_comp_op AddOpposite.unop_comp_op
-/

/- warning: mul_opposite.rec -> MulOpposite.rec' is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {F : (MulOpposite.{u1} α) -> Sort.{u2}}, (forall (X : α), F (MulOpposite.op.{u1} α X)) -> (forall (X : MulOpposite.{u1} α), F X)
but is expected to have type
  forall {α : Type.{u2}} {F : (MulOpposite.{u2} α) -> Sort.{u1}}, (forall (X : α), F (MulOpposite.op.{u2} α X)) -> (forall (X : MulOpposite.{u2} α), F X)
Case conversion may be inaccurate. Consider using '#align mul_opposite.rec MulOpposite.rec'ₓ'. -/
/-- A recursor for `mul_opposite`. Use as `induction x using mul_opposite.rec`. -/
@[simp, to_additive "A recursor for `add_opposite`. Use as `induction x using add_opposite.rec`."]
protected def rec' {F : ∀ X : αᵐᵒᵖ, Sort v} (h : ∀ X, F (op X)) : ∀ X, F X := fun X => h (unop X)
#align mul_opposite.rec MulOpposite.rec'
#align add_opposite.rec AddOpposite.rec'

#print MulOpposite.opEquiv /-
/-- The canonical bijection between `α` and `αᵐᵒᵖ`. -/
@[to_additive "The canonical bijection between `α` and `αᵃᵒᵖ`.",
  simps (config := { fullyApplied := false }) apply symm_apply]
def opEquiv : α ≃ αᵐᵒᵖ :=
  ⟨op, unop, unop_op, op_unop⟩
#align mul_opposite.op_equiv MulOpposite.opEquiv
#align add_opposite.op_equiv AddOpposite.opEquiv
-/

#print MulOpposite.op_bijective /-
@[to_additive]
theorem op_bijective : Bijective (op : α → αᵐᵒᵖ) :=
  opEquiv.Bijective
#align mul_opposite.op_bijective MulOpposite.op_bijective
#align add_opposite.op_bijective AddOpposite.op_bijective
-/

#print MulOpposite.unop_bijective /-
@[to_additive]
theorem unop_bijective : Bijective (unop : αᵐᵒᵖ → α) :=
  opEquiv.symm.Bijective
#align mul_opposite.unop_bijective MulOpposite.unop_bijective
#align add_opposite.unop_bijective AddOpposite.unop_bijective
-/

#print MulOpposite.op_injective /-
@[to_additive]
theorem op_injective : Injective (op : α → αᵐᵒᵖ) :=
  op_bijective.Injective
#align mul_opposite.op_injective MulOpposite.op_injective
#align add_opposite.op_injective AddOpposite.op_injective
-/

#print MulOpposite.op_surjective /-
@[to_additive]
theorem op_surjective : Surjective (op : α → αᵐᵒᵖ) :=
  op_bijective.Surjective
#align mul_opposite.op_surjective MulOpposite.op_surjective
#align add_opposite.op_surjective AddOpposite.op_surjective
-/

#print MulOpposite.unop_injective /-
@[to_additive]
theorem unop_injective : Injective (unop : αᵐᵒᵖ → α) :=
  unop_bijective.Injective
#align mul_opposite.unop_injective MulOpposite.unop_injective
#align add_opposite.unop_injective AddOpposite.unop_injective
-/

#print MulOpposite.unop_surjective /-
@[to_additive]
theorem unop_surjective : Surjective (unop : αᵐᵒᵖ → α) :=
  unop_bijective.Surjective
#align mul_opposite.unop_surjective MulOpposite.unop_surjective
#align add_opposite.unop_surjective AddOpposite.unop_surjective
-/

#print MulOpposite.op_inj /-
@[simp, to_additive]
theorem op_inj {x y : α} : op x = op y ↔ x = y :=
  op_injective.eq_iff
#align mul_opposite.op_inj MulOpposite.op_inj
#align add_opposite.op_inj AddOpposite.op_inj
-/

#print MulOpposite.unop_inj /-
@[simp, to_additive]
theorem unop_inj {x y : αᵐᵒᵖ} : unop x = unop y ↔ x = y :=
  unop_injective.eq_iff
#align mul_opposite.unop_inj MulOpposite.unop_inj
#align add_opposite.unop_inj AddOpposite.unop_inj
-/

variable (α)

@[to_additive]
instance [Nontrivial α] : Nontrivial αᵐᵒᵖ :=
  op_injective.Nontrivial

@[to_additive]
instance [Inhabited α] : Inhabited αᵐᵒᵖ :=
  ⟨op default⟩

@[to_additive]
instance [Subsingleton α] : Subsingleton αᵐᵒᵖ :=
  unop_injective.Subsingleton

@[to_additive]
instance [Unique α] : Unique αᵐᵒᵖ :=
  Unique.mk' _

@[to_additive]
instance [IsEmpty α] : IsEmpty αᵐᵒᵖ :=
  Function.isEmpty unop

instance [Zero α] : Zero αᵐᵒᵖ where zero := op 0

@[to_additive]
instance [One α] : One αᵐᵒᵖ where one := op 1

instance [Add α] : Add αᵐᵒᵖ where add x y := op (unop x + unop y)

instance [Sub α] : Sub αᵐᵒᵖ where sub x y := op (unop x - unop y)

instance [Neg α] : Neg αᵐᵒᵖ where neg x := op <| -unop x

instance [InvolutiveNeg α] : InvolutiveNeg αᵐᵒᵖ :=
  { MulOpposite.hasNeg α with neg_neg := fun a => unop_injective <| neg_neg _ }

@[to_additive]
instance [Mul α] : Mul αᵐᵒᵖ where mul x y := op (unop y * unop x)

@[to_additive]
instance [Inv α] : Inv αᵐᵒᵖ where inv x := op <| (unop x)⁻¹

@[to_additive]
instance [InvolutiveInv α] : InvolutiveInv αᵐᵒᵖ :=
  { MulOpposite.hasInv α with inv_inv := fun a => unop_injective <| inv_inv _ }

@[to_additive]
instance (R : Type _) [SMul R α] : SMul R αᵐᵒᵖ where smul c x := op (c • unop x)

section

variable (α)

#print MulOpposite.op_zero /-
@[simp]
theorem op_zero [Zero α] : op (0 : α) = 0 :=
  rfl
#align mul_opposite.op_zero MulOpposite.op_zero
-/

#print MulOpposite.unop_zero /-
@[simp]
theorem unop_zero [Zero α] : unop (0 : αᵐᵒᵖ) = 0 :=
  rfl
#align mul_opposite.unop_zero MulOpposite.unop_zero
-/

#print MulOpposite.op_one /-
@[simp, to_additive]
theorem op_one [One α] : op (1 : α) = 1 :=
  rfl
#align mul_opposite.op_one MulOpposite.op_one
#align add_opposite.op_zero AddOpposite.op_zero
-/

#print MulOpposite.unop_one /-
@[simp, to_additive]
theorem unop_one [One α] : unop (1 : αᵐᵒᵖ) = 1 :=
  rfl
#align mul_opposite.unop_one MulOpposite.unop_one
#align add_opposite.unop_zero AddOpposite.unop_zero
-/

variable {α}

#print MulOpposite.op_add /-
@[simp]
theorem op_add [Add α] (x y : α) : op (x + y) = op x + op y :=
  rfl
#align mul_opposite.op_add MulOpposite.op_add
-/

#print MulOpposite.unop_add /-
@[simp]
theorem unop_add [Add α] (x y : αᵐᵒᵖ) : unop (x + y) = unop x + unop y :=
  rfl
#align mul_opposite.unop_add MulOpposite.unop_add
-/

#print MulOpposite.op_neg /-
@[simp]
theorem op_neg [Neg α] (x : α) : op (-x) = -op x :=
  rfl
#align mul_opposite.op_neg MulOpposite.op_neg
-/

#print MulOpposite.unop_neg /-
@[simp]
theorem unop_neg [Neg α] (x : αᵐᵒᵖ) : unop (-x) = -unop x :=
  rfl
#align mul_opposite.unop_neg MulOpposite.unop_neg
-/

#print MulOpposite.op_mul /-
@[simp, to_additive]
theorem op_mul [Mul α] (x y : α) : op (x * y) = op y * op x :=
  rfl
#align mul_opposite.op_mul MulOpposite.op_mul
#align add_opposite.op_add AddOpposite.op_add
-/

#print MulOpposite.unop_mul /-
@[simp, to_additive]
theorem unop_mul [Mul α] (x y : αᵐᵒᵖ) : unop (x * y) = unop y * unop x :=
  rfl
#align mul_opposite.unop_mul MulOpposite.unop_mul
#align add_opposite.unop_add AddOpposite.unop_add
-/

#print MulOpposite.op_inv /-
@[simp, to_additive]
theorem op_inv [Inv α] (x : α) : op x⁻¹ = (op x)⁻¹ :=
  rfl
#align mul_opposite.op_inv MulOpposite.op_inv
#align add_opposite.op_neg AddOpposite.op_neg
-/

#print MulOpposite.unop_inv /-
@[simp, to_additive]
theorem unop_inv [Inv α] (x : αᵐᵒᵖ) : unop x⁻¹ = (unop x)⁻¹ :=
  rfl
#align mul_opposite.unop_inv MulOpposite.unop_inv
#align add_opposite.unop_neg AddOpposite.unop_neg
-/

#print MulOpposite.op_sub /-
@[simp]
theorem op_sub [Sub α] (x y : α) : op (x - y) = op x - op y :=
  rfl
#align mul_opposite.op_sub MulOpposite.op_sub
-/

#print MulOpposite.unop_sub /-
@[simp]
theorem unop_sub [Sub α] (x y : αᵐᵒᵖ) : unop (x - y) = unop x - unop y :=
  rfl
#align mul_opposite.unop_sub MulOpposite.unop_sub
-/

#print MulOpposite.op_smul /-
@[simp, to_additive]
theorem op_smul {R : Type _} [SMul R α] (c : R) (a : α) : op (c • a) = c • op a :=
  rfl
#align mul_opposite.op_smul MulOpposite.op_smul
#align add_opposite.op_vadd AddOpposite.op_vadd
-/

#print MulOpposite.unop_smul /-
@[simp, to_additive]
theorem unop_smul {R : Type _} [SMul R α] (c : R) (a : αᵐᵒᵖ) : unop (c • a) = c • unop a :=
  rfl
#align mul_opposite.unop_smul MulOpposite.unop_smul
#align add_opposite.unop_vadd AddOpposite.unop_vadd
-/

end

variable {α}

#print MulOpposite.unop_eq_zero_iff /-
@[simp]
theorem unop_eq_zero_iff [Zero α] (a : αᵐᵒᵖ) : a.unop = (0 : α) ↔ a = (0 : αᵐᵒᵖ) :=
  unop_injective.eq_iff' rfl
#align mul_opposite.unop_eq_zero_iff MulOpposite.unop_eq_zero_iff
-/

#print MulOpposite.op_eq_zero_iff /-
@[simp]
theorem op_eq_zero_iff [Zero α] (a : α) : op a = (0 : αᵐᵒᵖ) ↔ a = (0 : α) :=
  op_injective.eq_iff' rfl
#align mul_opposite.op_eq_zero_iff MulOpposite.op_eq_zero_iff
-/

#print MulOpposite.unop_ne_zero_iff /-
theorem unop_ne_zero_iff [Zero α] (a : αᵐᵒᵖ) : a.unop ≠ (0 : α) ↔ a ≠ (0 : αᵐᵒᵖ) :=
  not_congr <| unop_eq_zero_iff a
#align mul_opposite.unop_ne_zero_iff MulOpposite.unop_ne_zero_iff
-/

#print MulOpposite.op_ne_zero_iff /-
theorem op_ne_zero_iff [Zero α] (a : α) : op a ≠ (0 : αᵐᵒᵖ) ↔ a ≠ (0 : α) :=
  not_congr <| op_eq_zero_iff a
#align mul_opposite.op_ne_zero_iff MulOpposite.op_ne_zero_iff
-/

#print MulOpposite.unop_eq_one_iff /-
@[simp, to_additive]
theorem unop_eq_one_iff [One α] (a : αᵐᵒᵖ) : a.unop = 1 ↔ a = 1 :=
  unop_injective.eq_iff' rfl
#align mul_opposite.unop_eq_one_iff MulOpposite.unop_eq_one_iff
#align add_opposite.unop_eq_zero_iff AddOpposite.unop_eq_zero_iff
-/

#print MulOpposite.op_eq_one_iff /-
@[simp, to_additive]
theorem op_eq_one_iff [One α] (a : α) : op a = 1 ↔ a = 1 :=
  op_injective.eq_iff' rfl
#align mul_opposite.op_eq_one_iff MulOpposite.op_eq_one_iff
#align add_opposite.op_eq_zero_iff AddOpposite.op_eq_zero_iff
-/

end MulOpposite

namespace AddOpposite

instance [One α] : One αᵃᵒᵖ where one := op 1

#print AddOpposite.op_one /-
@[simp]
theorem op_one [One α] : op (1 : α) = 1 :=
  rfl
#align add_opposite.op_one AddOpposite.op_one
-/

#print AddOpposite.unop_one /-
@[simp]
theorem unop_one [One α] : unop 1 = (1 : α) :=
  rfl
#align add_opposite.unop_one AddOpposite.unop_one
-/

#print AddOpposite.op_eq_one_iff /-
@[simp]
theorem op_eq_one_iff [One α] {a : α} : op a = 1 ↔ a = 1 :=
  op_injective.eq_iff' op_one
#align add_opposite.op_eq_one_iff AddOpposite.op_eq_one_iff
-/

#print AddOpposite.unop_eq_one_iff /-
@[simp]
theorem unop_eq_one_iff [One α] {a : αᵃᵒᵖ} : unop a = 1 ↔ a = 1 :=
  unop_injective.eq_iff' unop_one
#align add_opposite.unop_eq_one_iff AddOpposite.unop_eq_one_iff
-/

instance [Mul α] : Mul αᵃᵒᵖ where mul a b := op (unop a * unop b)

#print AddOpposite.op_mul /-
@[simp]
theorem op_mul [Mul α] (a b : α) : op (a * b) = op a * op b :=
  rfl
#align add_opposite.op_mul AddOpposite.op_mul
-/

#print AddOpposite.unop_mul /-
@[simp]
theorem unop_mul [Mul α] (a b : αᵃᵒᵖ) : unop (a * b) = unop a * unop b :=
  rfl
#align add_opposite.unop_mul AddOpposite.unop_mul
-/

instance [Inv α] : Inv αᵃᵒᵖ where inv a := op (unop a)⁻¹

instance [InvolutiveInv α] : InvolutiveInv αᵃᵒᵖ :=
  { AddOpposite.hasInv with inv_inv := fun a => unop_injective <| inv_inv _ }

#print AddOpposite.op_inv /-
@[simp]
theorem op_inv [Inv α] (a : α) : op a⁻¹ = (op a)⁻¹ :=
  rfl
#align add_opposite.op_inv AddOpposite.op_inv
-/

#print AddOpposite.unop_inv /-
@[simp]
theorem unop_inv [Inv α] (a : αᵃᵒᵖ) : unop a⁻¹ = (unop a)⁻¹ :=
  rfl
#align add_opposite.unop_inv AddOpposite.unop_inv
-/

instance [Div α] : Div αᵃᵒᵖ where div a b := op (unop a / unop b)

#print AddOpposite.op_div /-
@[simp]
theorem op_div [Div α] (a b : α) : op (a / b) = op a / op b :=
  rfl
#align add_opposite.op_div AddOpposite.op_div
-/

#print AddOpposite.unop_div /-
@[simp]
theorem unop_div [Div α] (a b : αᵃᵒᵖ) : unop (a / b) = unop a / unop b :=
  rfl
#align add_opposite.unop_div AddOpposite.unop_div
-/

end AddOpposite


/-
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, Mario Carneiro, Patrick Massot
-/
import topology.order
import topology.nhds_set

/-!
# Specific classes of maps between topological spaces

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

This file introduces the following properties of a map `f : X → Y` between topological spaces:

* `is_open_map f` means the image of an open set under `f` is open.
* `is_closed_map f` means the image of a closed set under `f` is closed.

(Open and closed maps need not be continuous.)

* `inducing f` means the topology on `X` is the one induced via `f` from the topology on `Y`.
  These behave like embeddings except they need not be injective. Instead, points of `X` which
  are identified by `f` are also inseparable in the topology on `X`.
* `embedding f` means `f` is inducing and also injective. Equivalently, `f` identifies `X` with
  a subspace of `Y`.
* `open_embedding f` means `f` is an embedding with open image, so it identifies `X` with an
  open subspace of `Y`. Equivalently, `f` is an embedding and an open map.
* `closed_embedding f` similarly means `f` is an embedding with closed image, so it identifies
  `X` with a closed subspace of `Y`. Equivalently, `f` is an embedding and a closed map.

* `quotient_map f` is the dual condition to `embedding f`: `f` is surjective and the topology
  on `Y` is the one coinduced via `f` from the topology on `X`. Equivalently, `f` identifies
  `Y` with a quotient of `X`. Quotient maps are also sometimes known as identification maps.

## References

* <https://en.wikipedia.org/wiki/Open_and_closed_maps>
* <https://en.wikipedia.org/wiki/Embedding#General_topology>
* <https://en.wikipedia.org/wiki/Quotient_space_(topology)#Quotient_map>

## Tags

open map, closed map, embedding, quotient map, identification map

-/

open set filter function
open_locale topology filter

variables {α : Type*} {β : Type*} {γ : Type*} {δ : Type*}

section inducing

/-- A function `f : α → β` between topological spaces is inducing if the topology on `α` is induced
by the topology on `β` through `f`, meaning that a set `s : set α` is open iff it is the preimage
under `f` of some open set `t : set β`. -/
@[mk_iff]
structure inducing [tα : topological_space α] [tβ : topological_space β] (f : α → β) : Prop :=
(induced : tα = tβ.induced f)

variables [topological_space α] [topological_space β] [topological_space γ] [topological_space δ]

lemma inducing_id : inducing (@id α) :=
⟨induced_id.symm⟩

protected lemma inducing.comp {g : β → γ} {f : α → β} (hg : inducing g) (hf : inducing f) :
  inducing (g ∘ f) :=
⟨by rw [hf.induced, hg.induced, induced_compose]⟩

lemma inducing_of_inducing_compose {f : α → β} {g : β → γ} (hf : continuous f) (hg : continuous g)
  (hgf : inducing (g ∘ f)) : inducing f :=
⟨le_antisymm
    (by rwa ← continuous_iff_le_induced)
    (by { rw [hgf.induced, ← continuous_iff_le_induced], apply hg.comp continuous_induced_dom })⟩

lemma inducing_iff_nhds {f : α → β} : inducing f ↔ ∀ a, 𝓝 a = comap f (𝓝 (f a)) :=
(inducing_iff _).trans (induced_iff_nhds_eq f)

lemma inducing.nhds_eq_comap {f : α → β} (hf : inducing f) :
  ∀ (a : α), 𝓝 a = comap f (𝓝 $ f a) :=
inducing_iff_nhds.1 hf

lemma inducing.nhds_set_eq_comap {f : α → β} (hf : inducing f) (s : set α) :
  𝓝ˢ s = comap f (𝓝ˢ (f '' s)) :=
by simp only [nhds_set, Sup_image, comap_supr, hf.nhds_eq_comap, supr_image]

lemma inducing.map_nhds_eq {f : α → β} (hf : inducing f) (a : α) :
  (𝓝 a).map f = 𝓝[range f] (f a) :=
hf.induced.symm ▸ map_nhds_induced_eq a

lemma inducing.map_nhds_of_mem {f : α → β} (hf : inducing f) (a : α) (h : range f ∈ 𝓝 (f a)) :
  (𝓝 a).map f = 𝓝 (f a) :=
hf.induced.symm ▸ map_nhds_induced_of_mem h

lemma inducing.image_mem_nhds_within {f : α → β} (hf : inducing f) {a : α} {s : set α}
  (hs : s ∈ 𝓝 a) : f '' s ∈ 𝓝[range f] (f a) :=
hf.map_nhds_eq a ▸ image_mem_map hs

lemma inducing.tendsto_nhds_iff {ι : Type*}
  {f : ι → β} {g : β → γ} {a : filter ι} {b : β} (hg : inducing g) :
  tendsto f a (𝓝 b) ↔ tendsto (g ∘ f) a (𝓝 (g b)) :=
by rw [hg.nhds_eq_comap, tendsto_comap_iff]

lemma inducing.continuous_at_iff {f : α → β} {g : β → γ} (hg : inducing g) {x : α} :
  continuous_at f x ↔ continuous_at (g ∘ f) x :=
by simp_rw [continuous_at, inducing.tendsto_nhds_iff hg]

lemma inducing.continuous_iff {f : α → β} {g : β → γ} (hg : inducing g) :
  continuous f ↔ continuous (g ∘ f) :=
by simp_rw [continuous_iff_continuous_at, hg.continuous_at_iff]

lemma inducing.continuous_at_iff' {f : α → β} {g : β → γ} (hf : inducing f) {x : α}
  (h : range f ∈ 𝓝 (f x)) : continuous_at (g ∘ f) x ↔ continuous_at g (f x) :=
by { simp_rw [continuous_at, filter.tendsto, ← hf.map_nhds_of_mem _ h, filter.map_map] }

protected lemma inducing.continuous {f : α → β} (hf : inducing f) : continuous f :=
hf.continuous_iff.mp continuous_id

protected lemma inducing.inducing_iff {f : α → β} {g : β → γ} (hg : inducing g) :
  inducing f ↔ inducing (g ∘ f) :=
begin
  refine ⟨λ h, hg.comp h, λ hgf, inducing_of_inducing_compose _ hg.continuous hgf⟩,
  rw hg.continuous_iff,
  exact hgf.continuous
end

lemma inducing.closure_eq_preimage_closure_image {f : α → β} (hf : inducing f) (s : set α) :
  closure s = f ⁻¹' closure (f '' s) :=
by { ext x, rw [set.mem_preimage, ← closure_induced, hf.induced] }

lemma inducing.is_closed_iff {f : α → β} (hf : inducing f) {s : set α} :
  is_closed s ↔ ∃ t, is_closed t ∧ f ⁻¹' t = s :=
by rw [hf.induced, is_closed_induced_iff]

lemma inducing.is_closed_iff' {f : α → β} (hf : inducing f) {s : set α} :
  is_closed s ↔ ∀ x, f x ∈ closure (f '' s) → x ∈ s :=
by rw [hf.induced, is_closed_induced_iff']

lemma inducing.is_closed_preimage {f : α → β} (h : inducing f) (s : set β) (hs : is_closed s) :
  is_closed (f ⁻¹' s) :=
(inducing.is_closed_iff h).mpr ⟨s, hs, rfl⟩

lemma inducing.is_open_iff {f : α → β} (hf : inducing f) {s : set α} :
  is_open s ↔ ∃ t, is_open t ∧ f ⁻¹' t = s :=
by rw [hf.induced, is_open_induced_iff]

lemma inducing.dense_iff {f : α → β} (hf : inducing f) {s : set α} :
  dense s ↔ ∀ x, f x ∈ closure (f '' s) :=
by simp only [dense, hf.closure_eq_preimage_closure_image, mem_preimage]

end inducing

section embedding

/-- A function between topological spaces is an embedding if it is injective,
  and for all `s : set α`, `s` is open iff it is the preimage of an open set. -/
@[mk_iff] structure embedding [tα : topological_space α] [tβ : topological_space β] (f : α → β)
  extends inducing f : Prop :=
(inj : injective f)

lemma function.injective.embedding_induced [t : topological_space β]
  {f : α → β} (hf : injective f) :
  @_root_.embedding α β (t.induced f) t f :=
{ induced := rfl,
  inj := hf }

variables [topological_space α] [topological_space β] [topological_space γ]

lemma embedding.mk' (f : α → β) (inj : injective f)
  (induced : ∀ a, comap f (𝓝 (f a)) = 𝓝 a) : embedding f :=
⟨inducing_iff_nhds.2 (λ a, (induced a).symm), inj⟩

lemma embedding_id : embedding (@id α) :=
⟨inducing_id, assume a₁ a₂ h, h⟩

lemma embedding.comp {g : β → γ} {f : α → β} (hg : embedding g) (hf : embedding f) :
  embedding (g ∘ f) :=
{ inj:= assume a₁ a₂ h, hf.inj $ hg.inj h,
  ..hg.to_inducing.comp hf.to_inducing }

lemma embedding_of_embedding_compose {f : α → β} {g : β → γ} (hf : continuous f) (hg : continuous g)
  (hgf : embedding (g ∘ f)) : embedding f :=
{ induced := (inducing_of_inducing_compose hf hg hgf.to_inducing).induced,
  inj := assume a₁ a₂ h, hgf.inj $ by simp [h, (∘)] }

protected lemma function.left_inverse.embedding {f : α → β} {g : β → α}
  (h : left_inverse f g) (hf : continuous f) (hg : continuous g) :
  embedding g :=
embedding_of_embedding_compose hg hf $ h.comp_eq_id.symm ▸ embedding_id

lemma embedding.map_nhds_eq {f : α → β} (hf : embedding f) (a : α) :
  (𝓝 a).map f = 𝓝[range f] (f a) :=
hf.1.map_nhds_eq a

lemma embedding.map_nhds_of_mem {f : α → β}
  (hf : embedding f) (a : α) (h : range f ∈ 𝓝 (f a)) : (𝓝 a).map f = 𝓝 (f a) :=
hf.1.map_nhds_of_mem a h

lemma embedding.tendsto_nhds_iff {ι : Type*}
  {f : ι → β} {g : β → γ} {a : filter ι} {b : β} (hg : embedding g) :
  tendsto f a (𝓝 b) ↔ tendsto (g ∘ f) a (𝓝 (g b)) :=
hg.to_inducing.tendsto_nhds_iff

lemma embedding.continuous_iff {f : α → β} {g : β → γ} (hg : embedding g) :
  continuous f ↔ continuous (g ∘ f) :=
inducing.continuous_iff hg.1

lemma embedding.continuous {f : α → β} (hf : embedding f) : continuous f :=
inducing.continuous hf.1

lemma embedding.closure_eq_preimage_closure_image {e : α → β} (he : embedding e) (s : set α) :
  closure s = e ⁻¹' closure (e '' s) :=
he.1.closure_eq_preimage_closure_image s

/-- The topology induced under an inclusion `f : X → Y` from the discrete topological space `Y`
is the discrete topology on `X`. -/
lemma embedding.discrete_topology {X Y : Type*} [topological_space X] [tY : topological_space Y]
  [discrete_topology Y] {f : X → Y} (hf : embedding f) : discrete_topology X :=
discrete_topology_iff_nhds.2 $ λ x, by rw [hf.nhds_eq_comap, nhds_discrete, comap_pure,
  ← image_singleton, hf.inj.preimage_image, principal_singleton]

end embedding

/-- A function between topological spaces is a quotient map if it is surjective,
  and for all `s : set β`, `s` is open iff its preimage is an open set. -/
def quotient_map {α : Type*} {β : Type*} [tα : topological_space α] [tβ : topological_space β]
  (f : α → β) : Prop :=
surjective f ∧ tβ = tα.coinduced f

lemma quotient_map_iff {α β : Type*} [topological_space α] [topological_space β] {f : α → β} :
  quotient_map f ↔ surjective f ∧ ∀ s : set β, is_open s ↔ is_open (f ⁻¹' s) :=
and_congr iff.rfl topological_space_eq_iff

namespace quotient_map

variables [topological_space α] [topological_space β] [topological_space γ] [topological_space δ]
  {g : β → γ} {f : α → β}

protected lemma id : quotient_map (@id α) :=
⟨assume a, ⟨a, rfl⟩, coinduced_id.symm⟩

protected lemma comp (hg : quotient_map g) (hf : quotient_map f) :
  quotient_map (g ∘ f) :=
⟨hg.left.comp hf.left, by rw [hg.right, hf.right, coinduced_compose]⟩

protected lemma of_quotient_map_compose (hf : continuous f) (hg : continuous g)
  (hgf : quotient_map (g ∘ f)) : quotient_map g :=
⟨hgf.1.of_comp,
  le_antisymm
    (by { rw [hgf.right, ← continuous_iff_coinduced_le], apply continuous_coinduced_rng.comp hf })
    (by rwa ← continuous_iff_coinduced_le)⟩

lemma of_inverse {g : β → α} (hf : continuous f) (hg : continuous g) (h : left_inverse g f) :
  quotient_map g :=
quotient_map.of_quotient_map_compose hf hg $ h.comp_eq_id.symm ▸ quotient_map.id

protected lemma continuous_iff (hf : quotient_map f) :
  continuous g ↔ continuous (g ∘ f) :=
by rw [continuous_iff_coinduced_le, continuous_iff_coinduced_le, hf.right, coinduced_compose]

protected lemma continuous (hf : quotient_map f) : continuous f :=
hf.continuous_iff.mp continuous_id

protected lemma surjective (hf : quotient_map f) : surjective f := hf.1

protected lemma is_open_preimage (hf : quotient_map f) {s : set β} :
  is_open (f ⁻¹' s) ↔ is_open s :=
((quotient_map_iff.1 hf).2 s).symm

protected

end quotient_map

/-- A map `f : α → β` is said to be an *open map*, if the image of any open `U : set α`
is open in `β`. -/
def is_open_map [topological_space α] [topological_space β] (f : α → β) :=
∀ U : set α, is_open U → is_open (f '' U)

namespace is_open_map
variables [topological_space α] [topological_space β] [topological_space γ] {f : α → β}

protected lemma id : is_open_map (@id α) := assume s hs, by rwa [image_id]

protected lemma comp
  {g : β → γ} {f : α → β} (hg : is_open_map g) (hf : is_open_map f) : is_open_map (g ∘ f) :=
by intros s hs; rw [image_comp]; exact hg _ (hf _ hs)

lemma is_open_range (hf : is_open_map f) : is_open (range f) :=
by { rw ← image_univ, exact hf _ is_open_univ }

lemma image_mem_nhds (hf : is_open_map f) {x : α} {s : set α} (hx : s ∈ 𝓝 x) :
  f '' s ∈ 𝓝 (f x) :=
let ⟨t, hts, ht, hxt⟩ := mem_nhds_iff.1 hx in
mem_of_superset (is_open.mem_nhds (hf t ht) (mem_image_of_mem _ hxt)) (image_subset _ hts)

lemma range_mem_nhds (hf : is_open_map f) (x : α) : range f ∈ 𝓝 (f x) :=
hf.is_open_range.mem_nhds $ mem_range_self _

lemma maps_to_interior (hf : is_open_map f) {s : set α} {t : set β} (h : maps_to f s t) :
  maps_to f (interior s) (interior t) :=
maps_to'.2 $ interior_maximal (h.mono interior_subset subset.rfl).image_subset
  (hf _ is_open_interior)

lemma image_interior_subset (hf : is_open_map f) (s : set α) :
  f '' interior s ⊆ interior (f '' s) :=
(hf.maps_to_interior (maps_to_image f s)).image_subset

lemma nhds_le (hf : is_open_map f) (a : α) : 𝓝 (f a) ≤ (𝓝 a).map f :=
le_map $ λ s, hf.image_mem_nhds

lemma of_nhds_le (hf : ∀ a, 𝓝 (f a) ≤ map f (𝓝 a)) : is_open_map f :=
λ s hs, is_open_iff_mem_nhds.2 $ λ b ⟨a, has, hab⟩,
  hab ▸ hf _ (image_mem_map $ is_open.mem_nhds hs has)

lemma of_sections {f : α → β}
  (h : ∀ x, ∃ g : β → α, continuous_at g (f x) ∧ g (f x) = x ∧ right_inverse g f) :
  is_open_map f :=
of_nhds_le $ λ x, let ⟨g, hgc, hgx, hgf⟩ := h x in
calc 𝓝 (f x) = map f (map g (𝓝 (f x))) : by rw [map_map, hgf.comp_eq_id, map_id]
... ≤ map f (𝓝 (g (f x))) : map_mono hgc
... = map f (𝓝 x) : by rw hgx

lemma of_inverse {f : α → β} {f' : β → α}
  (h : continuous f') (l_inv : left_inverse f f') (r_inv : right_inverse f f') :
  is_open_map f :=
of_sections $ λ x, ⟨f', h.continuous_at, r_inv _, l_inv⟩

/-- A continuous surjective open map is a quotient map. -/
lemma to_quotient_map {f : α → β}
  (open_map : is_open_map f) (cont : continuous f) (surj : surjective f) :
  quotient_map f :=
quotient_map_iff.2 ⟨surj, λ s, ⟨λ h, h.preimage cont, λ h, surj.image_preimage s ▸ open_map _ h⟩⟩

lemma interior_preimage_subset_preimage_interior (hf : is_open_map f) {s : set β} :
  interior (f⁻¹' s) ⊆ f⁻¹' (interior s) :=
hf.maps_to_interior (maps_to_preimage _ _)

lemma preimage_interior_eq_interior_preimage (hf₁ : is_open_map f) (hf₂ : continuous f)
  (s : set β) :
  f⁻¹' (interior s) = interior (f⁻¹' s) :=
subset.antisymm
  (preimage_interior_subset_interior_preimage hf₂)
  (interior_preimage_subset_preimage_interior hf₁)

lemma preimage_closure_subset_closure_preimage (hf : is_open_map f) {s : set β} :
  f ⁻¹' (closure s) ⊆ closure (f ⁻¹' s) :=
begin
  rw ← compl_subset_compl,
  simp only [← interior_compl, ← preimage_compl, hf.interior_preimage_subset_preimage_interior]
end

lemma preimage_closure_eq_closure_preimage (hf : is_open_map f) (hfc : continuous f) (s : set β) :
  f ⁻¹' (closure s) = closure (f ⁻¹' s) :=
hf.preimage_closure_subset_closure_preimage.antisymm (hfc.closure_preimage_subset s)

lemma preimage_frontier_subset_frontier_preimage (hf : is_open_map f) {s : set β} :
  f ⁻¹' (frontier s) ⊆ frontier (f ⁻¹' s) :=
by simpa only [frontier_eq_closure_inter_closure, preimage_inter]
  using inter_subset_inter hf.preimage_closure_subset_closure_preimage
    hf.preimage_closure_subset_closure_preimage

lemma preimage_frontier_eq_frontier_preimage (hf : is_open_map f) (hfc : continuous f) (s : set β) :
  f ⁻¹' (frontier s) = frontier (f ⁻¹' s) :=
by simp only [frontier_eq_closure_inter_closure, preimage_inter, preimage_compl,
  hf.preimage_closure_eq_closure_preimage hfc]

end is_open_map

lemma is_open_map_iff_nhds_le [topological_space α] [topological_space β] {f : α → β} :
  is_open_map f ↔ ∀(a:α), 𝓝 (f a) ≤ (𝓝 a).map f :=
⟨λ hf, hf.nhds_le, is_open_map.of_nhds_le⟩

lemma is_open_map_iff_interior [topological_space α] [topological_space β] {f : α → β} :
  is_open_map f ↔ ∀ s, f '' (interior s) ⊆ interior (f '' s) :=
⟨is_open_map.image_interior_subset, λ hs u hu, subset_interior_iff_is_open.mp $
  calc f '' u = f '' (interior u) : by rw hu.interior_eq
          ... ⊆ interior (f '' u) : hs u⟩

/-- An inducing map with an open range is an open map. -/
protected lemma inducing.is_open_map [topological_space α] [topological_space β] {f : α → β}
  (hi : inducing f) (ho : is_open (range f)) :
  is_open_map f :=
is_open_map.of_nhds_le $ λ x, (hi.map_nhds_of_mem _ $ is_open.mem_nhds ho $ mem_range_self _).ge

section is_closed_map
variables [topological_space α] [topological_space β]

/-- A map `f : α → β` is said to be a *closed map*, if the image of any closed `U : set α`
is closed in `β`. -/
def is_closed_map (f : α → β) := ∀ U : set α, is_closed U → is_closed (f '' U)

end is_closed_map

namespace is_closed_map

variables [topological_space α] [topological_space β] [topological_space γ]
open function

protected lemma id : is_closed_map (@id α) := assume s hs, by rwa image_id

protected lemma comp {g : β → γ} {f : α → β} (hg : is_closed_map g) (hf : is_closed_map f) :
  is_closed_map (g ∘ f) :=
by { intros s hs, rw image_comp, exact hg _ (hf _ hs) }

lemma closure_image_subset {f : α → β} (hf : is_closed_map f) (s : set α) :
  closure (f '' s) ⊆ f '' closure s :=
closure_minimal (image_subset _ subset_closure) (hf _ is_closed_closure)

lemma of_inverse {f : α → β} {f' : β → α}
  (h : continuous f') (l_inv : left_inverse f f') (r_inv : right_inverse f f') :
  is_closed_map f :=
assume s hs,
have f' ⁻¹' s = f '' s, by ext x; simp [mem_image_iff_of_inverse r_inv l_inv],
this ▸ hs.preimage h

lemma of_nonempty {f : α → β} (h : ∀ s, is_closed s → s.nonempty → is_closed (f '' s)) :
  is_closed_map f :=
begin
  intros s hs, cases eq_empty_or_nonempty s with h2s h2s,
  { simp_rw [h2s, image_empty, is_closed_empty] },
  { exact h s hs h2s }
end

lemma closed_range {f : α → β} (hf : is_closed_map f) : is_closed (range f) :=
@image_univ _ _ f ▸ hf _ is_closed_univ

end is_closed_map

lemma inducing.is_closed_map [topological_space α] [topological_space β]
  {f : α → β} (hf : inducing f) (h : is_closed (range f)) : is_closed_map f :=
begin
  intros s hs,
  rcases hf.is_closed_iff.1 hs with ⟨t, ht, rfl⟩,
  rw image_preimage_eq_inter_range,
  exact ht.inter h
end

lemma is_closed_map_iff_closure_image [topological_space α] [topological_space β] {f : α → β} :
  is_closed_map f ↔ ∀ s, closure (f '' s) ⊆ f '' closure s :=
⟨is_closed_map.closure_image_subset, λ hs c hc, is_closed_of_closure_subset $
  calc closure (f '' c) ⊆ f '' (closure c) : hs c
                    ... = f '' c : by rw hc.closure_eq⟩

section open_embedding
variables [topological_space α] [topological_space β] [topological_space γ]

/-- An open embedding is an embedding with open image. -/
@[mk_iff]
structure open_embedding (f : α → β) extends _root_.embedding f : Prop :=
(open_range : is_open $ range f)

lemma open_embedding.is_open_map {f : α → β} (hf : open_embedding f) : is_open_map f :=
hf.to_embedding.to_inducing.is_open_map hf.open_range

lemma open_embedding.map_nhds_eq {f : α → β} (hf : open_embedding f) (a : α) :
  map f (𝓝 a) = 𝓝 (f a) :=
hf.to_embedding.map_nhds_of_mem _ $ hf.open_range.mem_nhds $ mem_range_self _

lemma open_embedding.open_iff_image_open {f : α → β} (hf : open_embedding f)
  {s : set α} : is_open s ↔ is_open (f '' s) :=
⟨hf.is_open_map s,
 λ h, begin
   convert ← h.preimage hf.to_embedding.continuous,
   apply preimage_image_eq _ hf.inj
 end⟩

lemma open_embedding.tendsto_nhds_iff {ι : Type*}
  {f : ι → β} {g : β → γ} {a : filter ι} {b : β} (hg : open_embedding g) :
  tendsto f a (𝓝 b) ↔ tendsto (g ∘ f) a (𝓝 (g b)) :=
hg.to_embedding.tendsto_nhds_iff

lemma open_embedding.continuous {f : α → β} (hf : open_embedding f) : continuous f :=
hf.to_embedding.continuous

lemma open_embedding.open_iff_preimage_open {f : α → β} (hf : open_embedding f)
  {s : set β} (hs : s ⊆ range f) : is_open s ↔ is_open (f ⁻¹' s) :=
begin
  convert ←hf.open_iff_image_open.symm,
  rwa [image_preimage_eq_inter_range, inter_eq_self_of_subset_left]
end

lemma open_embedding_of_embedding_open {f : α → β} (h₁ : embedding f)
  (h₂ : is_open_map f) : open_embedding f :=
⟨h₁, h₂.is_open_range⟩

lemma open_embedding_iff_embedding_open {f : α → β} :
  open_embedding f ↔ embedding f ∧ is_open_map f :=
⟨λ h, ⟨h.1, h.is_open_map⟩, λ h, open_embedding_of_embedding_open h.1 h.2⟩

lemma open_embedding_of_continuous_injective_open {f : α → β} (h₁ : continuous f)
  (h₂ : injective f) (h₃ : is_open_map f) : open_embedding f :=
begin
  simp only [open_embedding_iff_embedding_open, embedding_iff, inducing_iff_nhds, *, and_true],
  exact λ a, le_antisymm (h₁.tendsto _).le_comap
    (@comap_map _ _ (𝓝 a) _ h₂ ▸ comap_mono (h₃.nhds_le _))
end

lemma open_embedding_iff_continuous_injective_open {f : α → β} :
  open_embedding f ↔ continuous f ∧ injective f ∧ is_open_map f :=
⟨λ h, ⟨h.continuous, h.inj, h.is_open_map⟩,
  λ h, open_embedding_of_continuous_injective_open h.1 h.2.1 h.2.2⟩

lemma open_embedding_id : open_embedding (@id α) :=
⟨embedding_id, is_open_map.id.is_open_range⟩

lemma open_embedding.comp {g : β → γ} {f : α → β}
  (hg : open_embedding g) (hf : open_embedding f) : open_embedding (g ∘ f) :=
⟨hg.1.comp hf.1, (hg.is_open_map.comp hf.is_open_map).is_open_range⟩

lemma open_embedding.is_open_map_iff {g : β → γ} {f : α → β} (hg : open_embedding g) :
  is_open_map f ↔ is_open_map (g ∘ f) :=
by simp only [is_open_map_iff_nhds_le, ← @map_map _ _ _ _ f g, ← hg.map_nhds_eq,
  map_le_map_iff hg.inj]

lemma open_embedding.of_comp_iff (f : α → β) {g : β → γ} (hg : open_embedding g) :
  open_embedding (g ∘ f) ↔ open_embedding f :=
by simp only [open_embedding_iff_continuous_injective_open, ← hg.is_open_map_iff,
  ← hg.1.continuous_iff, hg.inj.of_comp_iff]

lemma open_embedding.of_comp (f : α → β) {g : β → γ} (hg : open_embedding g)
  (h : open_embedding (g ∘ f)) : open_embedding f :=
(open_embedding.of_comp_iff f hg).1 h

end open_embedding

section closed_embedding
variables [topological_space α] [topological_space β] [topological_space γ]

/-- A closed embedding is an embedding with closed image. -/
@[mk_iff]
structure closed_embedding (f : α → β) extends _root_.embedding f : Prop :=
(closed_range : is_closed $ range f)

variables {f : α → β}

lemma closed_embedding.tendsto_nhds_iff {ι : Type*}
  {g : ι → α} {a : filter ι} {b : α} (hf : closed_embedding f) :
  tendsto g a (𝓝 b) ↔ tendsto (f ∘ g) a (𝓝 (f b)) :=
hf.to_embedding.tendsto_nhds_iff

lemma closed_embedding.continuous (hf : closed_embedding f) : continuous f :=
hf.to_embedding.continuous

lemma closed_embedding.is_closed_map (hf : closed_embedding f) : is_closed_map f :=
hf.to_embedding.to_inducing.is_closed_map hf.closed_range

lemma closed_embedding.closed_iff_image_closed (hf : closed_embedding f)
  {s : set α} : is_closed s ↔ is_closed (f '' s) :=
⟨hf.is_closed_map s,
 λ h, begin
   convert ←continuous_iff_is_closed.mp hf.continuous _ h,
   apply preimage_image_eq _ hf.inj
 end⟩

lemma closed_embedding.closed_iff_preimage_closed (hf : closed_embedding f)
  {s : set β} (hs : s ⊆ range f) : is_closed s ↔ is_closed (f ⁻¹' s) :=
begin
  convert ←hf.closed_iff_image_closed.symm,
  rwa [image_preimage_eq_inter_range, inter_eq_self_of_subset_left]
end

lemma closed_embedding_of_embedding_closed (h₁ : embedding f)
  (h₂ : is_closed_map f) : closed_embedding f :=
⟨h₁, by convert h₂ univ is_closed_univ; simp⟩

lemma closed_embedding_of_continuous_injective_closed (h₁ : continuous f)
  (h₂ : injective f) (h₃ : is_closed_map f) : closed_embedding f :=
begin
  refine closed_embedding_of_embedding_closed ⟨⟨_⟩, h₂⟩ h₃,
  apply le_antisymm (continuous_iff_le_induced.mp h₁) _,
  intro s',
  change is_open _ ≤ is_open _,
  rw [←is_closed_compl_iff, ←is_closed_compl_iff],
  generalize : s'ᶜ = s,
  rw is_closed_induced_iff,
  refine λ hs, ⟨f '' s, h₃ s hs, _⟩,
  rw preimage_image_eq _ h₂
end

lemma closed_embedding_id : closed_embedding (@id α) :=
⟨embedding_id, by convert is_closed_univ; apply range_id⟩

lemma closed_embedding.comp {g : β → γ} {f : α → β}
  (hg : closed_embedding g) (hf : closed_embedding f) : closed_embedding (g ∘ f) :=
⟨hg.to_embedding.comp hf.to_embedding, show is_closed (range (g ∘ f)),
 by rw [range_comp, ←hg.closed_iff_image_closed]; exact hf.closed_range⟩

lemma closed_embedding.closure_image_eq {f : α → β} (hf : closed_embedding f) (s : set α) :
  closure (f '' s) = f '' closure s :=
(hf.is_closed_map.closure_image_subset _).antisymm
  (image_closure_subset_closure_image hf.continuous)

end closed_embedding


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

! This file was ported from Lean 3 source module representation_theory.basic
! leanprover-community/mathlib commit c04bc6e93e23aa0182aba53661a2211e80b6feac
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Algebra.Module.Basic
import Mathbin.Algebra.Module.LinearMap
import Mathbin.Algebra.MonoidAlgebra.Basic
import Mathbin.LinearAlgebra.Dual
import Mathbin.LinearAlgebra.Contraction
import Mathbin.RingTheory.TensorProduct

/-!
# Monoid representations

This file introduces monoid representations and their characters and defines a few ways to construct
representations.

## Main definitions

  * representation.representation
  * representation.character
  * representation.tprod
  * representation.lin_hom
  * represensation.dual

## Implementation notes

Representations of a monoid `G` on a `k`-module `V` are implemented as
homomorphisms `G →* (V →ₗ[k] V)`.
-/

open MonoidAlgebra (lift of)

open LinearMap

section

variable (k G V : Type _) [CommSemiring k] [Monoid G] [AddCommMonoid V] [Module k V]

/-- A representation of `G` on the `k`-module `V` is an homomorphism `G →* (V →ₗ[k] V)`.
-/
abbrev Representation :=
  G →* V →ₗ[k] V
#align representation Representation

end

namespace Representation

section trivial

variable (k : Type _) {G V : Type _} [CommSemiring k] [Monoid G] [AddCommMonoid V] [Module k V]

/-- The trivial representation of `G` on a `k`-module V.
-/
def trivial : Representation k G V :=
  1
#align representation.trivial Representation.trivial

@[simp]
theorem trivial_def (g : G) (v : V) : trivial k g v = v :=
  rfl
#align representation.trivial_def Representation.trivial_def

end trivial

section MonoidAlgebra

variable {k G V : Type _} [CommSemiring k] [Monoid G] [AddCommMonoid V] [Module k V]

variable (ρ : Representation k G V)

/-- A `k`-linear representation of `G` on `V` can be thought of as
an algebra map from `monoid_algebra k G` into the `k`-linear endomorphisms of `V`.
-/
noncomputable def asAlgebraHom : MonoidAlgebra k G →ₐ[k] Module.End k V :=
  (lift k G _) ρ
#align representation.as_algebra_hom Representation.asAlgebraHom

theorem asAlgebraHom_def : asAlgebraHom ρ = (lift k G _) ρ :=
  rfl
#align representation.as_algebra_hom_def Representation.asAlgebraHom_def

@[simp]
theorem asAlgebraHom_single (g : G) (r : k) : asAlgebraHom ρ (Finsupp.single g r) = r • ρ g := by
  simp only [as_algebra_hom_def, MonoidAlgebra.lift_single]
#align representation.as_algebra_hom_single Representation.asAlgebraHom_single

theorem asAlgebraHom_single_one (g : G) : asAlgebraHom ρ (Finsupp.single g 1) = ρ g := by simp
#align representation.as_algebra_hom_single_one Representation.asAlgebraHom_single_one

theorem asAlgebraHom_of (g : G) : asAlgebraHom ρ (of k G g) = ρ g := by
  simp only [MonoidAlgebra.of_apply, as_algebra_hom_single, one_smul]
#align representation.as_algebra_hom_of Representation.asAlgebraHom_of

/- ./././Mathport/Syntax/Translate/Command.lean:42:9: unsupported derive handler module[module] (module.End[module.End] k V) -/
/-- If `ρ : representation k G V`, then `ρ.as_module` is a type synonym for `V`,
which we equip with an instance `module (monoid_algebra k G) ρ.as_module`.

You should use `as_module_equiv : ρ.as_module ≃+ V` to translate terms.
-/
@[nolint unused_arguments]
def AsModule (ρ : Representation k G V) :=
  V deriving AddCommMonoid,
  «./././Mathport/Syntax/Translate/Command.lean:42:9: unsupported derive handler module[module] (module.End[module.End] k V)»
#align representation.as_module Representation.AsModule

instance : Inhabited ρ.AsModule :=
  ⟨0⟩

/-- A `k`-linear representation of `G` on `V` can be thought of as
a module over `monoid_algebra k G`.
-/
noncomputable instance asModuleModule : Module (MonoidAlgebra k G) ρ.AsModule :=
  Module.compHom V (asAlgebraHom ρ).toRingHom
#align representation.as_module_module Representation.asModuleModule

/-- The additive equivalence from the `module (monoid_algebra k G)` to the original vector space
of the representative.

This is just the identity, but it is helpful for typechecking and keeping track of instances.
-/
def asModuleEquiv : ρ.AsModule ≃+ V :=
  AddEquiv.refl _
#align representation.as_module_equiv Representation.asModuleEquiv

@[simp]
theorem asModuleEquiv_map_smul (r : MonoidAlgebra k G) (x : ρ.AsModule) :
    ρ.asModuleEquiv (r • x) = ρ.asAlgebraHom r (ρ.asModuleEquiv x) :=
  rfl
#align representation.as_module_equiv_map_smul Representation.asModuleEquiv_map_smul

@[simp]
theorem asModuleEquiv_symm_map_smul (r : k) (x : V) :
    ρ.asModuleEquiv.symm (r • x) = algebraMap k (MonoidAlgebra k G) r • ρ.asModuleEquiv.symm x :=
  by
  apply_fun ρ.as_module_equiv
  simp
#align representation.as_module_equiv_symm_map_smul Representation.asModuleEquiv_symm_map_smul

@[simp]
theorem asModuleEquiv_symm_map_rho (g : G) (x : V) :
    ρ.asModuleEquiv.symm (ρ g x) = MonoidAlgebra.of k G g • ρ.asModuleEquiv.symm x :=
  by
  apply_fun ρ.as_module_equiv
  simp
#align representation.as_module_equiv_symm_map_rho Representation.asModuleEquiv_symm_map_rho

/-- Build a `representation k G M` from a `[module (monoid_algebra k G) M]`.

This version is not always what we want, as it relies on an existing `[module k M]`
instance, along with a `[is_scalar_tower k (monoid_algebra k G) M]` instance.

We remedy this below in `of_module`
(with the tradeoff that the representation is defined
only on a type synonym of the original module.)
-/
noncomputable def ofModule' (M : Type _) [AddCommMonoid M] [Module k M]
    [Module (MonoidAlgebra k G) M] [IsScalarTower k (MonoidAlgebra k G) M] : Representation k G M :=
  (MonoidAlgebra.lift k G (M →ₗ[k] M)).symm (Algebra.lsmul k M)
#align representation.of_module' Representation.ofModule'

section

variable (k G) (M : Type _) [AddCommMonoid M] [Module (MonoidAlgebra k G) M]

/-- Build a `representation` from a `[module (monoid_algebra k G) M]`.

Note that the representation is built on `restrict_scalars k (monoid_algebra k G) M`,
rather than on `M` itself.
-/
noncomputable def ofModule : Representation k G (RestrictScalars k (MonoidAlgebra k G) M) :=
  (MonoidAlgebra.lift k G
        (RestrictScalars k (MonoidAlgebra k G) M →ₗ[k]
          RestrictScalars k (MonoidAlgebra k G) M)).symm
    (RestrictScalars.lsmul k (MonoidAlgebra k G) M)
#align representation.of_module Representation.ofModule

/-!
## `of_module` and `as_module` are inverses.

This requires a little care in both directions:
this is a categorical equivalence, not an isomorphism.

See `Rep.equivalence_Module_monoid_algebra` for the full statement.

Starting with `ρ : representation k G V`, converting to a module and back again
we have a `representation k G (restrict_scalars k (monoid_algebra k G) ρ.as_module)`.
To compare these, we use the composition of `restrict_scalars_add_equiv` and `ρ.as_module_equiv`.

Similarly, starting with `module (monoid_algebra k G) M`,
after we convert to a representation and back to a module,
we have `module (monoid_algebra k G) (restrict_scalars k (monoid_algebra k G) M)`.
-/

@[simp]
theorem ofModule_asAlgebraHom_apply_apply (r : MonoidAlgebra k G)
    (m : RestrictScalars k (MonoidAlgebra k G) M) :
    ((ofModule k G M).asAlgebraHom r) m =
      (RestrictScalars.addEquiv _ _ _).symm (r • RestrictScalars.addEquiv _ _ _ m) :=
  by
  apply MonoidAlgebra.induction_on r
  · intro g
    simp only [one_smul, MonoidAlgebra.lift_symm_apply, MonoidAlgebra.of_apply,
      Representation.asAlgebraHom_single, Representation.ofModule, AddEquiv.apply_eq_iff_eq,
      RestrictScalars.lsmul_apply_apply]
  · intro f g fw gw
    simp only [fw, gw, map_add, add_smul, LinearMap.add_apply]
  · intro r f w
    simp only [w, AlgHom.map_smul, LinearMap.smul_apply,
      RestrictScalars.addEquiv_symm_map_smul_smul]
#align representation.of_module_as_algebra_hom_apply_apply Representation.ofModule_asAlgebraHom_apply_apply

@[simp]
theorem ofModule_asModule_act (g : G) (x : RestrictScalars k (MonoidAlgebra k G) ρ.AsModule) :
    ofModule k G ρ.AsModule g x =
      (RestrictScalars.addEquiv _ _ _).symm
        (ρ.asModuleEquiv.symm (ρ g (ρ.asModuleEquiv (RestrictScalars.addEquiv _ _ _ x)))) :=
  by
  apply_fun RestrictScalars.addEquiv _ _ ρ.as_module using
    (RestrictScalars.addEquiv _ _ _).Injective
  dsimp [of_module, RestrictScalars.lsmul_apply_apply]
  simp
#align representation.of_module_as_module_act Representation.ofModule_asModule_act

theorem smul_ofModule_asModule (r : MonoidAlgebra k G) (m : (ofModule k G M).AsModule) :
    (RestrictScalars.addEquiv _ _ _) ((ofModule k G M).asModuleEquiv (r • m)) =
      r • (RestrictScalars.addEquiv _ _ _) ((ofModule k G M).asModuleEquiv m) :=
  by
  dsimp
  simp only [AddEquiv.apply_symm_apply, of_module_as_algebra_hom_apply_apply]
#align representation.smul_of_module_as_module Representation.smul_ofModule_asModule

end

end MonoidAlgebra

section AddCommGroup

variable {k G V : Type _} [CommRing k] [Monoid G] [I : AddCommGroup V] [Module k V]

variable (ρ : Representation k G V)

instance : AddCommGroup ρ.AsModule :=
  I

end AddCommGroup

section MulAction

variable (k : Type _) [CommSemiring k] (G : Type _) [Monoid G] (H : Type _) [MulAction G H]

/-- A `G`-action on `H` induces a representation `G →* End(k[H])` in the natural way. -/
noncomputable def ofMulAction : Representation k G (H →₀ k)
    where
  toFun g := Finsupp.lmapDomain k k ((· • ·) g)
  map_one' := by
    ext (x y)
    dsimp
    simp
  map_mul' x y := by
    ext (z w)
    simp [mul_smul]
#align representation.of_mul_action Representation.ofMulAction

variable {k G H}

theorem ofMulAction_def (g : G) : ofMulAction k G H g = Finsupp.lmapDomain k k ((· • ·) g) :=
  rfl
#align representation.of_mul_action_def Representation.ofMulAction_def

theorem ofMulAction_single (g : G) (x : H) (r : k) :
    ofMulAction k G H g (Finsupp.single x r) = Finsupp.single (g • x) r :=
  Finsupp.mapDomain_single
#align representation.of_mul_action_single Representation.ofMulAction_single

end MulAction

section Group

variable {k G V : Type _} [CommSemiring k] [Group G] [AddCommMonoid V] [Module k V]

variable (ρ : Representation k G V)

@[simp]
theorem ofMulAction_apply {H : Type _} [MulAction G H] (g : G) (f : H →₀ k) (h : H) :
    ofMulAction k G H g f h = f (g⁻¹ • h) :=
  by
  conv_lhs => rw [← smul_inv_smul g h]
  let h' := g⁻¹ • h
  change of_mul_action k G H g f (g • h') = f h'
  have hg : Function.Injective ((· • ·) g : H → H) :=
    by
    intro h₁ h₂
    simp
  simp only [of_mul_action_def, Finsupp.lmapDomain_apply, Finsupp.mapDomain_apply, hg]
#align representation.of_mul_action_apply Representation.ofMulAction_apply

theorem ofMulAction_self_smul_eq_mul (x : MonoidAlgebra k G) (y : (ofMulAction k G G).AsModule) :
    x • y = (x * y : MonoidAlgebra k G) :=
  x.inductionOn (fun g => by show as_algebra_hom _ _ _ = _ <;> ext <;> simp)
    (fun x y hx hy => by simp only [hx, hy, add_mul, add_smul]) fun r x hx => by
    show as_algebra_hom _ _ _ = _ <;> simpa [← hx]
#align representation.of_mul_action_self_smul_eq_mul Representation.ofMulAction_self_smul_eq_mul

/-- If we equip `k[G]` with the `k`-linear `G`-representation induced by the left regular action of
`G` on itself, the resulting object is isomorphic as a `k[G]`-module to `k[G]` with its natural
`k[G]`-module structure. -/
@[simps]
noncomputable def ofMulActionSelfAsModuleEquiv :
    (ofMulAction k G G).AsModule ≃ₗ[MonoidAlgebra k G] MonoidAlgebra k G :=
  { asModuleEquiv _ with map_smul' := ofMulAction_self_smul_eq_mul }
#align representation.of_mul_action_self_as_module_equiv Representation.ofMulActionSelfAsModuleEquiv

/-- When `G` is a group, a `k`-linear representation of `G` on `V` can be thought of as
a group homomorphism from `G` into the invertible `k`-linear endomorphisms of `V`.
-/
def asGroupHom : G →* Units (V →ₗ[k] V) :=
  MonoidHom.toHomUnits ρ
#align representation.as_group_hom Representation.asGroupHom

theorem asGroupHom_apply (g : G) : ↑(asGroupHom ρ g) = ρ g := by
  simp only [as_group_hom, MonoidHom.coe_toHomUnits]
#align representation.as_group_hom_apply Representation.asGroupHom_apply

end Group

section TensorProduct

variable {k G V W : Type _} [CommSemiring k] [Monoid G]

variable [AddCommMonoid V] [Module k V] [AddCommMonoid W] [Module k W]

variable (ρV : Representation k G V) (ρW : Representation k G W)

open TensorProduct

/-- Given representations of `G` on `V` and `W`, there is a natural representation of `G` on their
tensor product `V ⊗[k] W`.
-/
def tprod : Representation k G (V ⊗[k] W)
    where
  toFun g := TensorProduct.map (ρV g) (ρW g)
  map_one' := by simp only [map_one, TensorProduct.map_one]
  map_mul' g h := by simp only [map_mul, TensorProduct.map_mul]
#align representation.tprod Representation.tprod

-- mathport name: «expr ⊗ »
local notation ρV " ⊗ " ρW => tprod ρV ρW

@[simp]
theorem tprod_apply (g : G) : (ρV ⊗ ρW) g = TensorProduct.map (ρV g) (ρW g) :=
  rfl
#align representation.tprod_apply Representation.tprod_apply

theorem smul_tprod_one_asModule (r : MonoidAlgebra k G) (x : V) (y : W) :
    (r • x ⊗ₜ y : (ρV.tprod 1).AsModule) = (r • x : ρV.AsModule) ⊗ₜ y :=
  by
  show as_algebra_hom _ _ _ = as_algebra_hom _ _ _ ⊗ₜ _
  simp only [as_algebra_hom_def, MonoidAlgebra.lift_apply, tprod_apply, MonoidHom.one_apply,
    LinearMap.finsupp_sum_apply, LinearMap.smul_apply, TensorProduct.map_tmul, LinearMap.one_apply]
  simp only [Finsupp.sum, TensorProduct.sum_tmul]
  rfl
#align representation.smul_tprod_one_as_module Representation.smul_tprod_one_asModule

theorem smul_one_tprod_asModule (r : MonoidAlgebra k G) (x : V) (y : W) :
    (r • x ⊗ₜ y : ((1 : Representation k G V).tprod ρW).AsModule) = x ⊗ₜ (r • y : ρW.AsModule) :=
  by
  show as_algebra_hom _ _ _ = _ ⊗ₜ as_algebra_hom _ _ _
  simp only [as_algebra_hom_def, MonoidAlgebra.lift_apply, tprod_apply, MonoidHom.one_apply,
    LinearMap.finsupp_sum_apply, LinearMap.smul_apply, TensorProduct.map_tmul, LinearMap.one_apply]
  simp only [Finsupp.sum, TensorProduct.tmul_sum, TensorProduct.tmul_smul]
#align representation.smul_one_tprod_as_module Representation.smul_one_tprod_asModule

end TensorProduct

section LinearHom

variable {k G V W : Type _} [CommSemiring k] [Group G]

variable [AddCommMonoid V] [Module k V] [AddCommMonoid W] [Module k W]

variable (ρV : Representation k G V) (ρW : Representation k G W)

/-- Given representations of `G` on `V` and `W`, there is a natural representation of `G` on the
module `V →ₗ[k] W`, where `G` acts by conjugation.
-/
def linHom : Representation k G (V →ₗ[k] W)
    where
  toFun g :=
    { toFun := fun f => ρW g ∘ₗ f ∘ₗ ρV g⁻¹
      map_add' := fun f₁ f₂ => by simp_rw [add_comp, comp_add]
      map_smul' := fun r f => by simp_rw [RingHom.id_apply, smul_comp, comp_smul] }
  map_one' :=
    LinearMap.ext fun x => by
      simp_rw [coe_mk, inv_one, map_one, one_apply, one_eq_id, comp_id, id_comp]
  map_mul' g h :=
    LinearMap.ext fun x => by
      simp_rw [coe_mul, coe_mk, Function.comp_apply, mul_inv_rev, map_mul, mul_eq_comp, comp_assoc]
#align representation.lin_hom Representation.linHom

@[simp]
theorem linHom_apply (g : G) (f : V →ₗ[k] W) : (linHom ρV ρW) g f = ρW g ∘ₗ f ∘ₗ ρV g⁻¹ :=
  rfl
#align representation.lin_hom_apply Representation.linHom_apply

/-- The dual of a representation `ρ` of `G` on a module `V`, given by `(dual ρ) g f = f ∘ₗ (ρ g⁻¹)`,
where `f : module.dual k V`.
-/
def dual : Representation k G (Module.Dual k V)
    where
  toFun g :=
    { toFun := fun f => f ∘ₗ ρV g⁻¹
      map_add' := fun f₁ f₂ => by simp only [add_comp]
      map_smul' := fun r f => by
        ext
        simp only [coe_comp, Function.comp_apply, smul_apply, RingHom.id_apply] }
  map_one' := by
    ext
    simp only [coe_comp, Function.comp_apply, map_one, inv_one, coe_mk, one_apply]
  map_mul' g h := by
    ext
    simp only [coe_comp, Function.comp_apply, mul_inv_rev, map_mul, coe_mk, mul_apply]
#align representation.dual Representation.dual

@[simp]
theorem dual_apply (g : G) : (dual ρV) g = Module.Dual.transpose (ρV g⁻¹) :=
  rfl
#align representation.dual_apply Representation.dual_apply

/-- Given $k$-modules $V, W$, there is a homomorphism $φ : V^* ⊗ W → Hom_k(V, W)$
(implemented by `linear_algebra.contraction.dual_tensor_hom`).
Given representations of $G$ on $V$ and $W$,there are representations of $G$ on  $V^* ⊗ W$ and on
$Hom_k(V, W)$.
This lemma says that $φ$ is $G$-linear.
-/
theorem dualTensorHom_comm (g : G) :
    dualTensorHom k V W ∘ₗ TensorProduct.map (ρV.dual g) (ρW g) =
      (linHom ρV ρW) g ∘ₗ dualTensorHom k V W :=
  by ext; simp [Module.Dual.transpose_apply]
#align representation.dual_tensor_hom_comm Representation.dualTensorHom_comm

end LinearHom

end Representation


import data.nat.basic
import data.nat.pow
import data.int.basic
import data.real.basic
import data.list

-- ### Exercise 2

namespace exercise_2

-- Functions
def double : ℕ → ℕ := λ x, x + x
def square : ℕ → ℕ := λ x, x * x
def do_twice : (ℕ → ℕ) → (ℕ → ℕ) := λ f x, f (f x)
def Do_Twice : ((ℕ → ℕ) → (ℕ → ℕ)) → (ℕ → ℕ) → (ℕ → ℕ) := λ f g, (f (f g))
#reduce Do_Twice do_twice double 2

def curry {α β γ : Type*} (f : α × β → γ) : α → β → γ := λ a b, f (a, b)
def uncurry {α β γ : Type*} (f : α → β → γ) : α × β → γ := λ ⟨a, b⟩, f a b

-- "Vector" type former & related dependent functions
universe u
constant vec : Type u → ℕ → Type u

namespace vec
  constant empty : Π (α : Type u), vec α 0
  constant cons : Π {α : Type u} {n : ℕ}, α → vec α n → vec α (n + 1)
  constant append : Π {α : Type u} {n m : ℕ}, vec α m → vec α n → vec α (n + m)
  constant add : Π {α : Type u} {n : ℕ}, vec α n → vec α n → vec α n
  constant reverse : Π {α : Type u} {n : ℕ}, vec α n → vec α n
end vec

#check vec.empty ℕ
noncomputable def v0 := vec.empty ℕ -- TODO: what is `noncomputable`?
#check v0
noncomputable def v1 := vec.cons 0 v0
#check v1
variable v3 : vec ℕ 3
#check vec.add v3 v3
#check vec.reverse v3

-- "Matrix" type former & related dependent functions
constant matrix : Type u → ℕ → ℕ → Type u

namespace matrix
  constant add : Π {α : Type u} {n m : ℕ}, matrix α m n → matrix α m n → matrix α m n
  constant mul : Π {α : Type u} {n m k : ℕ}, matrix α m k → matrix α k n → matrix α m n
  constant mulvec : Π {α : Type u} {n m : ℕ}, matrix α m n → vec α n → vec α m
end matrix

#check matrix ℕ 5 3
variable m : matrix ℕ 5 3
variable m' : matrix ℕ 3 2
#check matrix.mulvec m v3
#check matrix.mul m m'

end exercise_2

-- ### Exercise 3

namespace exercise_3_1

variables p q r : Prop

-- Commutativity of ∧ and ∨
example : p ∧ q ↔ q ∧ p :=
  iff.intro
    (λ ⟨hp, hq⟩, ⟨hq, hp⟩)
    (λ ⟨hq, hp⟩, ⟨hp, hq⟩)
example : p ∨ q ↔ q ∨ p :=
  iff.intro
    (λ h, or.elim h (λ hp, or.inr hp) (λ hq, or.inl hq))
    (λ h, or.elim h (λ hq, or.inr hq) (λ hp, or.inl hp))

-- Associativity of ∧ and ∨
example : (p ∧ q) ∧ r ↔ p ∧ (q ∧ r) :=
  iff.intro
    (λ ⟨⟨hp, hq⟩, hr⟩, ⟨hp, ⟨hq, hr⟩⟩)
    (λ ⟨hp, ⟨hq, hr⟩⟩, ⟨⟨hp, hq⟩, hr⟩)
example : (p ∨ q) ∨ r ↔ p ∨ (q ∨ r) :=
  iff.intro
    (λ h, h.elim
      (λ hpq, hpq.elim or.inl (or.inr ∘ or.inl))
      (or.inr ∘ or.inr))
    (λ h, h.elim
      (or.inl ∘ or.inl)
      (λ hqr, hqr.elim (or.inl ∘ or.inr) or.inr))

-- Distributivity
example : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) :=
  iff.intro
    (λ ⟨hp, hqr⟩, hqr.elim (λ hq, or.inl ⟨hp, hq⟩) (λ hr, or.inr ⟨hp, hr⟩))
    (λ h, h.elim (λ ⟨hp, hq⟩, ⟨hp, or.inl hq⟩) (λ ⟨hp, hr⟩, ⟨hp, or.inr hr⟩))
example : p ∨ (q ∧ r) ↔ (p ∨ q) ∧ (p ∨ r) :=
  iff.intro
    (λ h, h.elim (λ hp, ⟨or.inl hp, or.inl hp⟩) (λ ⟨hq, hr⟩, ⟨or.inr hq, or.inr hr⟩))
    (λ ⟨hpq, hpr⟩, hpq.elim or.inl (λ hq, hpr.elim or.inl (λ hr, or.inr ⟨hq, hr⟩)))

-- Other properties
example : (p → (q → r)) ↔ (p ∧ q → r) :=
  iff.intro
    (λ f ⟨hp, hq⟩, f hp hq)
    (λ f hp hq, f ⟨hp, hq⟩)
example : ((p ∨ q) → r) ↔ (p → r) ∧ (q → r) :=
  iff.intro
    (λ h, ⟨h ∘ or.inl, h ∘ or.inr⟩)
    (λ ⟨hpr, hqr⟩ hpq, hpq.elim hpr hqr)
example : ¬(p ∨ q) ↔ ¬p ∧ ¬q :=
  iff.intro
    (λ h, ⟨h ∘ or.inl, h ∘ or.inr⟩)
    (λ ⟨hnp, hnq⟩, (λ h, h.elim hnp hnq))
example : ¬p ∨ ¬q → ¬(p ∧ q) :=
  (λ h ⟨hp, hq⟩, h.elim (λ hnp, hnp hp) (λ hnq, hnq hq))
example : ¬(p ∧ ¬p) := (λ ⟨hp, hnp⟩, hnp hp)
example : p ∧ ¬q → ¬(p → q) := (λ ⟨hp, hnq⟩ hpq, hnq (hpq hp))
example : ¬p → (p → q) := (λ hnp hp, (hnp hp).elim)
example : (¬p ∨ q) → (p → q) := (λ h, h.elim (λ hnp hp, (hnp hp).elim) (λ hq _, hq))
example : p ∨ false ↔ p := iff.intro (λ h, h.elim id false.elim) or.inl
example : p ∧ false ↔ false := iff.intro and.right false.elim
example : (p → q) → (¬q → ¬p) := (λ hpq hnq hp, hnq (hpq hp))

end exercise_3_1
namespace exercise_3_2

variables p q r s : Prop

-- These require classical reasoning.
example : (p → r ∨ s) → ((p → r) ∨ (p → s)) :=
  (λ h, (em p).elim
    (λ hp, (h hp).elim (λ hr, or.inl (λ _, hr)) (λ hs, or.inr (λ _, hs)))
    (λ hnp, or.inl (λ hp, (hnp hp).elim)))
example : ¬(p ∧ q) → ¬p ∨ ¬q :=
  (λ h, (em p).elim
    (λ hp, or.inr (λ hq, h ⟨hp, hq⟩))
    or.inl)
example : ¬(p → q) → p ∧ ¬q :=
  (λ h, and.intro
    ((em q).elim
      (λ hq, (h (λ _, hq)).elim)
      (λ hnq, (em p).elim id (λ hnp, (h (λ hp, (hnp hp).elim)).elim)))
    (λ hq, h (λ _, hq)))
example : (p → q) → (¬p ∨ q) :=
  (λ hpq, (em p).elim (or.inr ∘ hpq) or.inl)
example : (¬q → ¬p) → (p → q) :=
  (λ h, (em q).elim (λ hq _, hq) (λ hnq hp, (h hnq hp).elim))
example : p ∨ ¬p := em p
example : (((p → q) → p) → p) :=
  (em p).elim
    (λ hp _, hp)
    (λ hnp, (em q).elim
      (λ hq h, h (λ _, hq))
      (λ hnq h, h (λ hp, (hnp hp).elim)))

-- Prove ¬(p ↔ ¬p) without using classical logic.
example : ¬(p ↔ ¬p) :=
  (λ ⟨h₁, h₂⟩,
    (λ hp, h₁ hp hp) -- "Don't eliminate cuts!" (will make the proof longer)
      (h₂ (λ hp, h₁ hp hp)))

end exercise_3_2

-- ### Exercise 4

namespace exercise_4

section
  variables (α : Type*) (p q : α → Prop)

  example : (∀ x, p x ∧ q x) ↔ (∀ x, p x) ∧ (∀ x, q x) :=
    iff.intro
      (λ h, ⟨(λ x, (h x).left), (λ x, (h x).right)⟩)
      (λ ⟨h₁, h₂⟩ x, ⟨h₁ x, h₂ x⟩)
  example : (∀ x, p x → q x) → (∀ x, p x) → (∀ x, q x) :=
    λ h₁ h₂ x, h₁ x (h₂ x)
  example : (∀ x, p x) ∨ (∀ x, q x) → ∀ x, p x ∨ q x :=
    λ h x, h.elim (λ h₁, or.inl (h₁ x)) (λ h₁, or.inr (h₁ x))
end

section
  variables (α : Type*) (p q : α → Prop)
  variable r : Prop

  -- One direction of the second of these requires classical logic
  example : α → ((∀ x : α, r) ↔ r) :=
    λ x, (iff.intro (λ h, h x) (λ h _, h))
  example : (∀ x, p x ∨ r) ↔ (∀ x, p x) ∨ r :=
    iff.intro
      (λ h, (classical.em r).elim (or.inr) (λ hnr, or.inl (λ x, (h x).elim id (λ hr, (hnr hr).elim))))
      (λ h, h.elim (λ h₁ x, or.inl (h₁ x)) (λ h₂ x, or.inr h₂))
  example : (∀ x, r → p x) ↔ (r → ∀ x, p x) :=
    iff.intro
      (λ h hr x, h x hr)
      (λ h x hr, h hr x)
end

section
  variables (men : Type*) (barber : men)
  variable  (shaves : men → men → Prop)

  example (h : ∀ x : men, shaves barber x ↔ ¬ shaves x x) : false :=
    (h barber).elim
      (λ (h₁ : shaves barber barber → ¬shaves barber barber) h₂,
        (λ hs, h₁ hs hs) (h₂ (λ hs, h₁ hs hs)))
end

-- See: https://discord.com/channels/679792285910827018/707609591940382830/733322519112712362
section
  def prime (n : ℕ) : Prop :=
    ∀ (x : ℕ), (∃ y, n = x * y) → x = 1 ∨ x = n

  def infinitely_many_primes : Prop :=
    ∀ (x : ℕ), prime x → (∃ y, prime y ∧ x < y)

  def Fermat_prime (n : ℕ) : Prop :=
    prime n ∧ ∃ (x : ℕ), n = 2 ^ (2 ^ x) + 1

  def infinitely_many_Fermat_primes : Prop :=
    ∀ (x : ℕ), Fermat_prime x → (∃ y, Fermat_prime y ∧ x < y)

  def goldbach_conjecture : Prop :=
    ∀ (n : ℕ), even n → (2 < n) → (∃ p₁ p₂, prime p₁ ∧ prime p₂ ∧ n = p₁ + p₂)

  def Goldbach's_weak_conjecture : Prop :=
    ∀ (n : ℕ), odd n → (5 < n) → (∃ p₁ p₂ p₃, prime p₁ ∧ prime p₂ ∧ prime p₃ ∧ n = p₁ + p₂ + p₃)

  def Fermat's_last_theorem : Prop :=
    ∀ (n : ℕ), (2 < n) → ∀ (a b c : ℕ), 0 < a → 0 < b → a ^ n + b ^ n ≠ c ^ n
end

section
  variables (α : Type*) (p q : α → Prop)
  variable r : Prop

  example : (∃ x : α, r) → r :=
    λ ⟨x, hx⟩, hx
  example (a : α) : r → (∃ x : α, r) :=
    λ h, ⟨a, h⟩
  example : (∃ x, p x ∧ r) ↔ (∃ x, p x) ∧ r :=
    iff.intro
      (λ ⟨x, hx, hr⟩, ⟨⟨x, hx⟩, hr⟩)
      (λ ⟨⟨x, hx⟩, hr⟩, ⟨x, ⟨hx, hr⟩⟩)
  example : (∃ x, p x ∨ q x) ↔ (∃ x, p x) ∨ (∃ x, q x) :=
    iff.intro
      (λ ⟨x, hx⟩, hx.elim (λ hpx, or.inl ⟨x, hpx⟩) (λ hqx, or.inr ⟨x, hqx⟩))
      (λ h, h.elim (λ ⟨x, hx⟩, ⟨x, or.inl hx⟩) (λ ⟨x, hx⟩, ⟨x, or.inr hx⟩))

  example : (∀ x, p x) ↔ ¬ (∃ x, ¬ p x) :=
    iff.intro
      (λ h ⟨x, hx⟩, hx (h x))
      (λ h x, by_contra (λ hx, h ⟨x, hx⟩))
  example : (∃ x, p x) ↔ ¬ (∀ x, ¬ p x) :=
    iff.intro
      (λ ⟨x, hx⟩ h, h x hx)
      (λ h, by_contra (λ h₁, h (λ x hx, h₁ ⟨x, hx⟩)))
  example : (¬ ∃ x, p x) ↔ (∀ x, ¬ p x) :=
    iff.intro
      (λ h x hx, h ⟨x, hx⟩)
      (λ h ⟨x, hx⟩, h x hx)
  example : (¬ ∀ x, p x) ↔ (∃ x, ¬ p x) :=
    iff.intro
      (λ h, by_contra (λ h₁, h (λ x, by_contra (λ hx, h₁ ⟨x, hx⟩))))
      (λ ⟨x, hx⟩ h, hx (h x))

  example : (∀ x, p x → r) ↔ (∃ x, p x) → r :=
    iff.intro
      (λ h ⟨x, hx⟩, h x hx)
      (λ h x hx, h ⟨x, hx⟩)
  example (a : α) : (∃ x, p x → r) ↔ (∀ x, p x) → r :=
    iff.intro
      (λ ⟨x, hx⟩ h, hx (h x))
      (λ h, (em (∀ x, p x)).elim
        (λ h₁, ⟨a, λ _, h h₁⟩)
        (λ h₁,
          have h₂ : (∃ (x : α), ¬(p x)),
          from by_contra
            (λ h₂, h₁ (λ x, by_contra
              (λ h₃, h₂ ⟨x, h₃⟩))),
          let ⟨w, hw⟩ := h₂ in
            ⟨w, (λ hw₁, (hw hw₁).elim)⟩))
  example (a : α) : (∃ x, r → p x) ↔ (r → ∃ x, p x) :=
    iff.intro
      (λ ⟨x, hx⟩ h, ⟨x, hx h⟩)
      (λ h, (em r).elim
        (λ hr, exists.elim (h hr) (λ a ha, ⟨a, (λ _, ha)⟩))
        (λ hnr, ⟨a, (λ hr, (hnr hr).elim)⟩))
end

section
  variables log exp    : ℝ → ℝ
  variable  log_exp_eq : ∀ x, log (exp x) = x
  variable  exp_log_eq : ∀ {x}, x > 0 → exp (log x) = x
  variable  exp_pos    : ∀ x, exp x > 0
  variable  exp_add    : ∀ x y, exp (x + y) = exp x * exp y

  -- this ensures the assumptions are available in tactic proofs
  include log_exp_eq exp_log_eq exp_pos exp_add

  example (x y z : ℝ) : exp (x + y + z) = exp x * exp y * exp z :=
  by rw [exp_add, exp_add]

  example (y : real) (h : y > 0) : exp (log y) = y :=
    exp_log_eq h

  theorem log_mul {x y : real} (hx : x > 0) (hy : y > 0) :
    log (x * y) = log x + log y :=
  by calc log (x * y)
        = log (exp (log x) * exp (log y)) : by rw [exp_log_eq hx, exp_log_eq hy]
    ... = log (exp (log x + log y)) : by rw exp_add
    ... = log x + log y : by rw log_exp_eq
end

section
  example (x : ℤ) : x * 0 = 0 :=
  by calc x * 0
        = x * 0 + x * 0 + -(x * 0) : by rw [add_assoc, add_neg_self, add_zero]
    ... = x * (0 * 1 + 0 * 1) + -(x * 0) : by rw [mul_one, mul_add]
    ... = x * 0 + -(x * 0) : by rw [mul_one, add_zero]
    ... = 0 : by rw add_neg_self
end

end exercise_4

-- ### Exercise 5

namespace exercise_5_1

variables p q r : Prop

-- Commutativity of ∧ and ∨
example : p ∧ q ↔ q ∧ p :=
begin
  split,
  { intros h, exact let ⟨hp, hq⟩ := h in ⟨hq, hp⟩, },
  { intros h, exact let ⟨hq, hp⟩ := h in ⟨hp, hq⟩, },
end
example : p ∨ q ↔ q ∨ p :=
begin
  apply iff.intro,
  { exact (λ h, h.elim or.inr or.inl), },
  { exact (λ h, h.elim or.inr or.inl), },
end

-- Associativity of ∧ and ∨
example : (p ∧ q) ∧ r ↔ p ∧ (q ∧ r) := by {
  split,
  { rintros ⟨⟨hp, hq⟩, hr⟩, exact ⟨hp, hq, hr⟩, },
  { rintros ⟨hp, hq, hr⟩, exact ⟨⟨hp, hq⟩, hr⟩, },
}
example : (p ∨ q) ∨ r ↔ p ∨ (q ∨ r) := by {
  split,
  { assume h,
      cases h, cases h,
        exacts [or.inl h, or.inr (or.inl h), or.inr (or.inr h)], },
  { assume h,
      cases h, swap, cases h,
        exacts [or.inl (or.inr h), or.inr h, or.inl (or.inl h)], },
}

-- Distributivity
example : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) := by {
  split,
  { rintros ⟨hp, hq | hr⟩,
      left, exact ⟨hp, hq⟩,
      right, exact ⟨hp, hr⟩, },
  { rintros (⟨hp, hq⟩ | ⟨hp, hr⟩),
      exact ⟨hp, or.inl hq⟩,
      exact ⟨hp, or.inr hr⟩, },
}
example : p ∨ (q ∧ r) ↔ (p ∨ q) ∧ (p ∨ r) := by {
  split,
  { rintros (hp | ⟨hq, hr⟩),
      exacts [⟨or.inl hp, or.inl hp⟩, ⟨or.inr hq, or.inr hr⟩], },
  { rintros ⟨hp | hq, hp | hr⟩,
      exacts [or.inl hp, or.inl hp, or.inl hp, or.inr ⟨hq, hr⟩], },
}

-- Other properties
example : (p → (q → r)) ↔ (p ∧ q → r) := by {
  split,
  { rintros f ⟨hp, hq⟩, exact f hp hq, },
  { rintros f hp hq, exact f ⟨hp, hq⟩, },
}
example : ((p ∨ q) → r) ↔ (p → r) ∧ (q → r) := by {
  split,
  { intros f, split, exacts [f ∘ or.inl, f ∘ or.inr], },
  { rintros ⟨f, g⟩ (hp | hq), exacts [f hp, g hq], },
}
example : ¬(p ∨ q) ↔ ¬p ∧ ¬q := by sorry -- More examples to experiment on later
example : ¬p ∨ ¬q → ¬(p ∧ q) := by sorry
example : ¬(p ∧ ¬p) := by sorry
example : p ∧ ¬q → ¬(p → q) := by sorry
example : ¬p → (p → q) := by sorry
example : (¬p ∨ q) → (p → q) := by sorry
example : p ∨ false ↔ p := by sorry
example : p ∧ false ↔ false := by sorry
example : (p → q) → (¬q → ¬p) := by sorry

end exercise_5_1
namespace exercise_5_2

variables p q r s : Prop

-- These require classical reasoning.
example : (p → r ∨ s) → ((p → r) ∨ (p → s)) := by sorry
example : ¬(p ∧ q) → ¬p ∨ ¬q := by sorry
example : ¬(p → q) → p ∧ ¬q := by sorry
example : (p → q) → (¬p ∨ q) := by sorry
example : (¬q → ¬p) → (p → q) := by sorry
example : p ∨ ¬p := by sorry
example : (((p → q) → p) → p) := by sorry

-- Prove ¬(p ↔ ¬p) without using classical logic.
example : ¬(p ↔ ¬p) := by {
  rintros ⟨h₁, h₂⟩,
  apply h₁,
  { apply h₂, intros hp, exact h₁ hp hp, },
  { apply h₂, intros hp, exact h₁ hp hp, },
}

end exercise_5_2
namespace exercise_5_3

section
  variables (α : Type*) (p q : α → Prop)

  example : (∀ x, p x ∧ q x) ↔ (∀ x, p x) ∧ (∀ x, q x) := by sorry -- More examples to experiment on later
  example : (∀ x, p x → q x) → (∀ x, p x) → (∀ x, q x) := by sorry
  example : (∀ x, p x) ∨ (∀ x, q x) → ∀ x, p x ∨ q x := by sorry
end

section
  variables (α : Type*) (p q : α → Prop)
  variable r : Prop

  -- One direction of the second of these requires classical logic
  example : α → ((∀ x : α, r) ↔ r) := by sorry
  example : (∀ x, p x ∨ r) ↔ (∀ x, p x) ∨ r := by sorry
  example : (∀ x, r → p x) ↔ (r → ∀ x, p x) := by sorry
end

section
  variables (α : Type*) (p q : α → Prop)
  variable r : Prop

  example : (∃ x : α, r) → r := by sorry
  example (a : α) : r → (∃ x : α, r) := by sorry
  example : (∃ x, p x ∧ r) ↔ (∃ x, p x) ∧ r := by sorry
  example : (∃ x, p x ∨ q x) ↔ (∃ x, p x) ∨ (∃ x, q x) := by sorry

  example : (∀ x, p x) ↔ ¬ (∃ x, ¬ p x) := by sorry
  example : (∃ x, p x) ↔ ¬ (∀ x, ¬ p x) := by sorry
  example : (¬ ∃ x, p x) ↔ (∀ x, ¬ p x) := by sorry
  example : (¬ ∀ x, p x) ↔ (∃ x, ¬ p x) := by sorry

  example : (∀ x, p x → r) ↔ (∃ x, p x) → r := by sorry
  example (a : α) : (∃ x, p x → r) ↔ (∀ x, p x) → r := by sorry
  example (a : α) : (∃ x, r → p x) ↔ (r → ∃ x, p x) := by sorry
end

end exercise_5_3

/-
Note:
  `tauto` (and `tauto!` for classical) seems to use sequent calculus (elim then intro);
  `finish` seems to use SMT? (TODO: what is SMT...)
  Both are complete for propositional logic...?
-/

-- ### Exercise 7

namespace exercise_7

inductive myempty : Type

inductive myunit : Type
| star : myunit

inductive mybool : Type
| ff : mybool
| tt : mybool

namespace mybool

  def bnot : mybool → mybool :=
    @mybool.rec (λ _, mybool) tt ff

  def band : mybool → mybool → mybool :=
    λ a b, @mybool.rec (λ _, mybool) ff b a

  def bor : mybool → mybool → mybool :=
    λ a b, @mybool.rec (λ _, mybool) b tt a

end mybool

universe u

-- Standard definition for equality
inductive myeq {α : Sort u} (l : α) : α → Prop
| refl [] : myeq l

namespace myeq

  lemma symm : Π {α : Sort u} {x y : α} (h : myeq x y), myeq y x :=
    λ α x y h, @myeq.rec α x -- The subject is `myeq {α} x`
      (λ r, myeq r x) -- Make a more general claim: given `myeq x r` then `myeq r x`
      (myeq.refl x)   -- Now given `myeq x x` (implicit), prove `myeq x x`
        y h           -- Then we could specialise `r` to `y`, and give a `myeq x y` to make `myeq y x`

  lemma trans : Π {α : Sort u} {x y z : α} (h₁ : myeq x y) (h₂ : myeq y z), myeq x z :=
    λ α x y z h₁ h₂, @myeq.rec α y -- The subject is `myeq {α} y`
      (λ r, myeq x r) -- Make a more general claim: given `myeq y r` then `myeq x r`
      h₁              -- Now given `myeq y y` (implicit), prove `myeq x y`
        z h₂          -- Then we could specialise `r` to `z`, and give a `myeq y z` to make `myeq x z`

  lemma congr : Π {α β : Sort u} {x y : α} (f : α → β) (h : myeq x y), myeq (f x) (f y) :=
    λ α β x y f h, @myeq.rec α x
      (λ r, myeq (f x) (f r))
      (myeq.refl (f x))
        y h

  lemma subst : Π {α : Sort u} {x y : α} (p : α → Prop) (h₁ : myeq x y) (h₂ : p x), p y :=
    λ α x y p h₁ h₂, @myeq.rec α x
      (λ r, p r)
      h₂
        y h₁

  -- Simplify by removing `@` and abbreviating arguments
  lemma symm' {α : Sort u} {x y : α} (h : myeq x y) : myeq y x :=
    myeq.rec (myeq.refl x) h
  lemma trans' {α : Sort u} {x y z : α} (h₁ : myeq x y) (h₂ : myeq y z) : myeq x z :=
    myeq.rec h₁ h₂
  lemma congr' {α β : Sort u} {x y : α} (f : α → β) (h : myeq x y) : myeq (f x) (f y) :=
    myeq.rec (myeq.refl (f x)) h
  lemma subst' {α : Sort u} {x y : α} (p : α → Prop) (h₁ : myeq x y) (h₂ : p x) : p y :=
    myeq.rec h₂ h₁

end myeq

namespace mybool

  lemma em : Π (a : mybool), myeq (bor a (bnot a)) tt :=
    @mybool.rec
      (λ x, myeq (bor x (bnot x)) tt)
      (myeq.refl tt) -- Lean refers to definitions and does the calculation automatically
      (myeq.refl tt) -- So `myeq (bor tt (bnot tt)) tt` unifies with `myeq tt tt`!

  lemma de_morgan : Π (a b : mybool), myeq (bnot (band a b)) (bor (bnot a) (bnot b)) :=
    λ a b, @mybool.rec -- By cases on `a`
      (λ a', myeq (bnot (band a' b)) (bor (bnot a') (bnot b)))
      (@mybool.rec     -- By cases on `b`
        (λ b', myeq (bnot (band ff b')) (bor (bnot ff) (bnot b')))
        (myeq.refl tt)
        (myeq.refl tt)
          b)
      (@mybool.rec     -- By cases on `b`
        (λ b', myeq (bnot (band tt b')) (bor (bnot tt) (bnot b')))
        (myeq.refl tt)
        (myeq.refl ff)
          b)
        a

  -- Simplify by removing `@` and abbreviating arguments
  lemma de_morgan' (a b : mybool) : myeq (bnot (band a b)) (bor (bnot a) (bnot b)) :=
    mybool.rec_on b
      (mybool.rec_on a (myeq.refl _) (myeq.refl _))
      (mybool.rec_on a (myeq.refl _) (myeq.refl _))

end mybool

inductive maybe (α : Type u) : Type u
| nothing :     maybe
| just    : α → maybe

inductive inhabited (α : Type u) : Type u
| mk : α → inhabited

namespace maybe

  -- The "monad operation"
  def bind : Π {α β : Type u}, maybe α → (α → maybe β) → maybe β :=
    λ α β ma f, @maybe.rec α
      (λ _, maybe β)
      (nothing)
      (λ a, f a)
        ma

  -- Partial function composition
  def compose : Π {α β γ : Type u}, (β → maybe γ) → (α → maybe β) → α → maybe γ :=
    λ α β γ g f a, @maybe.rec β
      (λ _, maybe γ)
      (nothing)
      (λ b, g b)
        (f a)

  -- Simplified versions
  def bind' {α β : Type u} (ma : maybe α) (f : α → maybe β) : maybe β :=
    maybe.rec nothing f ma
  def compose' {α β γ : Type u} (g : β → maybe γ) (f : α → maybe β) (a : α) : maybe γ :=
    maybe.rec nothing g (f a)
  def compose'' {α β γ : Type u} (g : β → maybe γ) (f : α → maybe β) (a : α) : maybe γ :=
    bind' (f a) g

end maybe

section
  def is_even (x : ℕ) : bool := nat.rec_on x tt (λ _, @bool.rec (λ _, bool) bool.tt bool.ff)
  def filter (x : ℕ) : maybe ℕ := bool.cases_on (is_even x) (maybe.just x.succ) maybe.nothing
  #reduce filter 3
  #reduce (maybe.compose'' filter filter) 3
end

section
  #check inhabited      -- The type former
  #check @inhabited.mk  -- The constructor

  def inhabited_bool : inhabited bool := inhabited.mk tt
  def inhabited_nat  : inhabited nat  := inhabited.mk 0

  def inhabited_prod_of_inhabited : Π {α β : Type u}
    (i₁ : inhabited α) (i₂ : inhabited β),
    inhabited (prod α β) :=
      λ α β i₁ i₂, inhabited.mk (inhabited.rec id i₁, inhabited.rec id i₂)

  def inhabited_of_function_to_inhabited : Π {α β : Type u}
    (i : inhabited β),
    inhabited (α → β) :=
      λ α β i, inhabited.mk (λ _, inhabited.rec id i)

  def inhabited_of_function_to_inhabited' : Π {α : Type u} {τ : α → Type u}
    (i : Π (a : α), inhabited (τ a)),
    inhabited (Π (a : α), τ a) :=
      λ α τ i, inhabited.mk (λ a, inhabited.rec id (i a))
end

inductive mylist (α : Type u) : Type u
| nil  :              mylist
| cons : α → mylist → mylist

namespace mylist
  variable {α : Type u}

  def append (s t : mylist α) : mylist α :=
    mylist.rec_on s t (λ a _, λ st', cons a st')

  local notation h :: t := cons h t -- ?
  local notation s ++ t := append s t -- ?
  local notation `[` l:(foldr `,` (h t, cons h t) nil) `]` := l -- ?????

  section
    open nat
    #check [1, 2, 3, 4, 5]
    #check ([1, 2, 3, 4, 5] : mylist int)
  end

  theorem nil_append (t : mylist α) : nil ++ t = t := eq.refl t
  theorem cons_append (x : α) (s t : mylist α) : x::s ++ t = x::(s ++ t) := eq.refl _

  -- Manual equational rewriting!
  theorem append_nil (t : mylist α) : t ++ nil = t :=
    @mylist.rec_on α
      (λ l, l ++ nil = l) t
      (eq.refl nil)
      (λ a as, λ ih, (@eq.subst _ (λ x, a::as ++ nil = a::x) _ _ ih (eq.refl _)))

  -- Tip: have a notepad open, keep track of the current "state"...
  theorem append_assoc (r s t : mylist α) : r ++ s ++ t = r ++ (s ++ t) :=
    @mylist.rec_on α
      (λ l, (l ++ s) ++ t = l ++ (s ++ t)) r
      (eq.refl (s ++ t))
      (λ a as, λ ih,
        (@eq.subst _ (λ x, (a::as ++ s) ++ t = a::x) _ _ ih
          (@eq.subst _ (λ x, (a::as ++ s) ++ t = x) _ _ (cons_append a (as ++ s) t)
            (@eq.subst _ (λ x, (a::as ++ s) ++ t = x ++ t) _ _ (cons_append a as s)
              (eq.refl ((a::as ++ s) ++ t)))))) -- Read bottom up

  -- Simplify by ignoring steps that can be completed by `rfl` (`eq.refl _`)
  theorem append_assoc' (r s t : mylist α) : r ++ s ++ t = r ++ (s ++ t) :=
    @mylist.rec_on α
      (λ l, (l ++ s) ++ t = l ++ (s ++ t)) r
      rfl
      (λ a as, λ ih, (@eq.subst _ (λ x, (a::as ++ s) ++ t = a::x) _ _ ih rfl))

end mylist

inductive mynat : Type
| zero :                mynat
| succ : Π (n : mynat), mynat

namespace mynat
  def one   : mynat := zero.succ
  def two   : mynat := zero.succ.succ
  def three : mynat := zero.succ.succ.succ

  -- `mynat.rec` is primitive recursion!

  def add : mynat → mynat → mynat :=
    λ a, @mynat.rec
      (λ r, mynat)
      a
      (λ r, λ ar, succ ar)

  #reduce add one two

  def mul : mynat → mynat → mynat :=
    λ a, @mynat.rec
      (λ r, mynat)
      zero
      (λ r, λ ar, add ar a)

  #reduce mul two three
  #reduce mul three two

  def pred : mynat → mynat :=
    @mynat.rec
      (λ r, mynat)
      zero
      (λ r, λ predr, r)

  #reduce pred zero
  #reduce pred one
  #reduce pred two
  #reduce pred three

  def monus : mynat → mynat → mynat :=
    λ a, @mynat.rec
      (λ r, mynat)
      a
      (λ r, λ ar, pred ar)

  #reduce monus three two
  #reduce monus two three

  def pow : mynat → mynat → mynat :=
    λ a, @mynat.rec
      (λ _, mynat)
      one
      (λ r, λ ar, mul ar a)

  #reduce pow two three
  #reduce pow three two

  lemma add_zero : Π (a : mynat), add a zero = a :=
    λ a, rfl

  lemma add_succ : Π (a b : mynat), add a (succ b) = succ (add a b) :=
    λ a b, rfl

  -- More manual equational rewriting...
  lemma succ_add : Π (a b : mynat), add (succ a) b = succ (add a b) :=
    λ a b, @mynat.rec_on
      (λ n, add (succ a) n = succ (add a n)) b
      rfl
      (λ n, λ ih,
        (@eq.subst _ (λ x, add (succ a) (succ n) = succ x) _ _ ih
          (eq.refl (add (succ a) (succ n)))))

  lemma zero_add : Π (a : mynat), add zero a = a :=
    λ a, @mynat.rec_on
      (λ n, add zero n = n) a
      rfl
      (λ n, λ ih,
        (@eq.subst _ (λ x, succ (add zero n) = succ x) _ _ ih
          (eq.refl (succ (add zero n)))))

  lemma add_comm : Π (a b : mynat), add a b = add b a :=
    λ a b, @mynat.rec_on
      (λ n, add a n = add n a) b
      (eq.symm (zero_add a))
      (λ n, λ ih,
        (@eq.subst _ (λ x, succ (add a n) = x) _ _ (eq.symm (succ_add n a))
          (@eq.subst _ (λ x, succ (add a n) = succ x) _ _ ih
            (eq.refl (succ (add a n))))))

  -- See: https://leanprover.zulipchat.com/#narrow/streams/public/search/stupid.20triangle
  #print notation ▸

  -- `rw` is incredible...!
  lemma add_comm' : Π (a b : mynat), add a b = add b a :=
  begin
    intros a b,
    induction b with b ih,
    { rw zero_add, refl, },
    { rw [succ_add, ← ih], refl, },
  end

  -- This will lead to the Natural Number Game...
  -- https://github.com/ImperialCollegeLondon/natural_number_game/

  -- Also see: Part 7 of *Logic and Structures* (van Dalen)
end mynat

namespace mylist
  variable {α : Type u}

  local notation h :: t := cons h t -- ?
  local notation s ++ t := append s t -- ?
  local notation `[` l:(foldr `,` (h t, cons h t) nil) `]` := l -- ?????

  def length : mylist α → ℕ :=
    λ l, mylist.rec 0 (λ _ _, λ (l' : ℕ), l'.succ) l

  def reverse : mylist α → mylist α :=
    λ l, mylist.rec nil (λ a l, λ l', l' ++ (cons a nil)) l

  #reduce length [1, 2, 3, 4, 5]
  #reduce reverse [1, 2, 3, 4, 5]

  -- Even more manual equational rewriting...
  -- (TODO: make clear about Lean's heuristics for second-order unification?)
  lemma length_append : Π (s t : mylist α), length (s ++ t) = length s + length t :=
    λ s t, @mylist.rec_on α
      (λ l, length (l ++ t) = length l + length t) s
      (eq.symm (nat.zero_add t.length))
      (λ a l, λ ih,
        (@eq.subst _ (λ x, length (a::l ++ t) = x) _ _ (eq.symm (nat.succ_add (length l) (length t)))
          (@eq.subst _ (λ x, length (a::l ++ t) = nat.succ x) _ _ ih
            (@eq.subst _ (λ x, length (a::l ++ t) = length x) _ _ (cons_append a l t)
              (eq.refl (a::l ++ t).length)))))

  lemma length_reverse : Π (t : mylist α), length (reverse t) = length t :=
    λ t, @mylist.rec_on α
      (λ l, length (reverse l) = length l) t
      rfl
      (λ a l, λ ih,
        (@eq.subst _ (λ x, x = (a::l).length) _ _ (eq.symm (length_append l.reverse [a]))
          (@eq.subst _ (λ x, nat.succ x = (a::l).length) _ _ (eq.symm ih)
            (eq.refl (a::l).length))))

  lemma append_reverse_eq_reverse_append : Π (s t : mylist α), reverse (s ++ t) = reverse t ++ reverse s :=
    λ s t, @mylist.rec_on α
      (λ x, reverse (x ++ t) = reverse t ++ reverse x) s
      (@eq.subst _ (λ x, reverse t = x) _ _ (eq.symm (append_nil t.reverse)) (eq.refl t.reverse))
      (λ a l, λ ih,
        (@eq.subst _ (λ x, reverse (a::l ++ t) = x) _ _ (append_assoc t.reverse l.reverse [a])
          (@eq.subst _ (λ x, reverse (a::l ++ t) = x ++ [a]) _ _ ih
            (@eq.subst _ (λ x, reverse (a::l ++ t) = reverse x) _ _ (cons_append a l t)
              (eq.refl (a::l ++ t).reverse)))))

  lemma reverse_reverse_eq_self : Π (t : mylist α), reverse (reverse t) = t :=
    λ t, @mylist.rec_on α
      (λ x, reverse (reverse x) = x) t
      rfl
      (λ a l, λ ih,
        (@eq.subst _ (λ x, (a::l).reverse.reverse = [a] ++ x) _ _ ih
          (@eq.subst _ (λ x, (a::l).reverse.reverse = x) _ _ (append_reverse_eq_reverse_append l.reverse [a])
            (eq.refl (a::l).reverse.reverse))))

end mylist

inductive arith_expr : Type
| const : ℕ →                       arith_expr
| var   : ℕ →                       arith_expr
| plus  : arith_expr → arith_expr → arith_expr
| times : arith_expr → arith_expr → arith_expr

namespace arith_expr
  #check const 2
  #check var 5
  #check plus (const 2) (var 0)

  def eval : (ℕ → ℕ) → arith_expr → ℕ :=
    λ as e, @arith_expr.rec_on (λ _, ℕ) e
      id
      as
      (λ e1 e2, λ v1 v2, v1 + v2)
      (λ e1 e2, λ v1 v2, v1 * v2)

  #reduce eval id (plus (const 2) (var 0))

end arith_expr

inductive boolean_expr : Type
| const : bool →                        boolean_expr
| var   : ℕ →                           boolean_expr
| not   : boolean_expr →                boolean_expr
| and   : boolean_expr → boolean_expr → boolean_expr
| or    : boolean_expr → boolean_expr → boolean_expr

namespace boolean_expr
  #check const tt
  #check var 5
  #check and (const ff) (var 0)

  def eval : (ℕ → bool) → boolean_expr → bool :=
    λ as e, @boolean_expr.rec_on (λ _, bool) e
      id
      as
      (λ e', λ v', bnot v')
      (λ e1 e2, λ v1 v2, band v1 v2)
      (λ e1 e2, λ v1 v2, bor v1 v2)

  def size : boolean_expr → ℕ :=
    λ e, @boolean_expr.rec_on (λ _, ℕ) e
      (λ _, 1)
      (λ _, 1)
      (λ e', λ ne', ne' + 1)
      (λ e1 e2, λ ne1 ne2, ne1 + ne2 + 1)
      (λ e1 e2, λ ne1 ne2, ne1 + ne2 + 1)

  def depth : boolean_expr → ℕ :=
    λ e, @boolean_expr.rec_on (λ _, ℕ) e
      (λ _, 1)
      (λ _, 1)
      (λ e', λ ne', ne' + 1)
      (λ e1 e2, λ ne1 ne2, max ne1 ne2 + 1)
      (λ e1 e2, λ ne1 ne2, max ne1 ne2 + 1)

  #reduce size  (and (or (const tt) (not (var 0))) (not (var 2)))
  #reduce depth (and (or (const tt) (not (var 0))) (not (var 2)))

  def replace_all : boolean_expr → ℕ → boolean_expr → boolean_expr :=
    λ sub ind e, @boolean_expr.rec_on (λ _, boolean_expr) e
      (λ b, const b)
      (λ v, ite (v = ind) sub (var v))
      (λ _, λ e', not e')
      (λ _ _, λ e1' e2', and e1' e2')
      (λ _ _, λ e1' e2', or e1' e2')

  #reduce replace_all
    (const ff)
    2
    (and (or (const tt) (not (var 0))) (not (var 2)))

end boolean_expr

inductive even_odd : bool → ℕ → Prop
| even_zero :                            even_odd ff 0
| even_succ : Π {n : ℕ}, even_odd ff n → even_odd tt n.succ
| odd_succ  : Π {n : ℕ}, even_odd tt n → even_odd ff n.succ

namespace even_odd
  #check even_zero
  #check even_succ even_zero
  -- Does not typecheck
  -- #check even_succ (even_succ even_zero)
  -- Typechecks
  #check odd_succ (even_succ even_zero)
  #check (even_succ ∘ odd_succ ∘ even_succ ∘ odd_succ ∘ even_succ) even_zero
end even_odd

end exercise_7

-- ### Exercise 8

namespace exercise_8

section
  open function
  #print surjective

  universes u v w
  variables {α : Type u} {β : Type v} {γ : Type w}
  open function

  lemma surjective_comp {g : β → γ} {f : α → β}
    (hg : surjective g) (hf : surjective f) : surjective (g ∘ f) :=
    λ c,
      match hg c with (Exists.intro b hb) :=
        match hf b with (Exists.intro a ha) :=
          ⟨a, (ha.symm ▸ hb : g (f a) = c)⟩ end end
end

namespace hidden
  open nat

  def add : ℕ → ℕ → ℕ
  | a 0        := a
  | a (succ b) := succ (add a b)

  lemma zero_add : Π (a : ℕ), add 0 a = a
  | 0        := rfl
  | (succ b) := congr_arg succ (zero_add b)

  lemma succ_add : Π (a b : ℕ), add (succ a) b = succ (add a b)
  | a 0        := rfl
  | a (succ b) := congr_arg succ (succ_add a b)

  lemma add_comm : Π (a b : ℕ), add a b = add b a
  | a 0        := (zero_add a).symm
  | a (succ b) := eq.trans (@@congr_arg succ (add_comm a b)) (succ_add b a).symm

  lemma add_assoc : Π (a b c : ℕ), add a (add b c) = add (add a b) c
  | a b 0        := rfl
  | a b (succ c) := congr_arg succ (add_assoc a b c)

end hidden

namespace hidden
  open list
  variable {α : Type*}

  def length : list α → ℕ
  | []        := 0
  | (x :: xs) := length xs + 1

  def reverse : list α → list α
  | []        := []
  | (x :: xs) := reverse xs ++ [x]

  lemma length_append : Π (s t : list α), length (s ++ t) = length s + length t
  | []        t := (nat.zero_add _).symm
  | (x :: xs) t :=
    show length (xs ++ t) + 1 = length xs + 1 + length t,
      by rw [nat.add_assoc, nat.add_comm 1, ← nat.add_assoc, length_append]

  lemma length_reverse : Π (t : list α), length (reverse t) = length t
  | []        := rfl
  | (x :: xs) :=
    show length (reverse xs ++ [x]) = length xs + 1,
      by rw [length_append, length_reverse]; refl

  lemma append_reverse_eq_reverse_append : Π (s t : list α), reverse (s ++ t) = reverse t ++ reverse s
  | [] t        := (append_nil (reverse t)).symm
  | (x :: xs) t :=
    show reverse (xs ++ t) ++ [x] = reverse t ++ (reverse xs ++ [x]),
      by rw [append_reverse_eq_reverse_append, append_assoc]

  lemma reverse_reverse_eq_self : Π (t : list α), reverse (reverse t) = t
  | []        := rfl
  | (x :: xs) :=
    show reverse (reverse xs ++ [x]) = x :: xs,
      by rw [append_reverse_eq_reverse_append, reverse_reverse_eq_self]; refl

end hidden

section
  #check @well_founded.fix

  -- Structural recursion on `acc r`, given element `hwf.apply x`.
  def well_founded.fix' {α : Sort*} {C : α → Sort*} {r : α → α → Prop}
    (hwf : well_founded r) (R : Π (x : α), (Π (y : α), r y x → C y) → C x) :
      Π (x : α), C x :=
        λ x, @acc.rec_on α r C x (hwf.apply x) (λ x' hx', R x')
end

inductive vector (α : Type) : ℕ → Type
| vnil  :                           vector 0
| vcons : Π {n : ℕ}, α → vector n → vector n.succ

namespace vector
  def vec1 := vcons 1 (vcons 2 (vcons 3 vnil))
  def vec2 := vcons 4 (vcons 5 vnil)

  -- Using EC
  def tail {α : Type} {n : ℕ} : vector α (n + 1) → vector α n
  | (vcons a as) := as

  -- Using recursor
  -- (See: https://leanprover.github.io/theorem_proving_in_lean/induction_and_recursion.html#dependent-pattern-matching)
  def tail_aux {α : Type*} {n n' : ℕ} (v : vector α n') : n' = n + 1 → vector α n :=
    vector.cases_on v -- Cases on `v` first so you could use that equality
      (λ (h : 0 = n + 1), nat.no_confusion h)
      (λ n' (a : α) (as : vector α n') (h : n' + 1 = n + 1),
        nat.no_confusion h (λ h₁ : n' = n, eq.rec_on h₁ as))

  def tail' {α : Type*} {n : ℕ} (v : vector α (n + 1)) : vector α n :=
    tail_aux v rfl

  -- Using EC
  def append {α : Type} : Π {m : ℕ} (u : vector α m) {n : ℕ} (v : vector α n), vector α (n + m)
  | nat.zero      vnil         n v := v
  | (nat.succ m') (vcons a as) n v := vcons a (append as v)

  #reduce append vec1 vec2

  -- Using recursor (without using an auxiliary function???)
  def append' {α : Type} : Π {m : ℕ} (u : vector α m) {n : ℕ} (v : vector α n), vector α (n + m) :=
    λ m u n v,
      @vector.rec_on _ (λ m' _, vector α (n + m')) _ u
        v
        (λ m' a as acc, vcons a acc)

  #reduce append' vec1 vec2

  -- (TODO: the real challenge???)
  -- (See: https://leanprover.zulipchat.com/#narrow/stream/113489-new-members/topic/more.20more.20basics/near/190957263)
end vector

section
  inductive aexpr : Type
  | const : ℕ             → aexpr
  | var   : ℕ             → aexpr
  | plus  : aexpr → aexpr → aexpr
  | times : aexpr → aexpr → aexpr

  open aexpr

  def sample_aexpr : aexpr := plus (times (var 0) (const 7)) (times (const 2) (var 1))

  def aeval (v : ℕ → ℕ) : aexpr → ℕ
  | (const n)     := n
  | (var n)       := v n
  | (plus e₁ e₂)  := aeval e₁ + aeval e₂
  | (times e₁ e₂) := aeval e₁ * aeval e₂

  def sample_val : ℕ → ℕ
  | 0 := 5
  | 1 := 6
  | _ := 0

  #eval aeval sample_val sample_aexpr

  def simp_const : aexpr → aexpr
  | (plus (const n₁) (const n₂))  := const (n₁ + n₂)
  | (times (const n₁) (const n₂)) := const (n₁ * n₂)
  | e                             := e

  def fuse : aexpr → aexpr
  | (const n)     := (const n)
  | (var n)       := (var n)
  | (plus e₁ e₂)  := simp_const (plus (fuse e₁) (fuse e₂))
  | (times e₁ e₂) := simp_const (times (fuse e₁) (fuse e₂))

  theorem simp_const_eq (v : ℕ → ℕ) : ∀ e : aexpr, aeval v (simp_const e) = aeval v e
  | (const n)     := rfl
  | (var n)       := rfl
  | (plus e₁ e₂)  := by { cases e₁; cases e₂; refl }
  | (times e₁ e₂) := by { cases e₁; cases e₂; refl }

  theorem fuse_eq (v : ℕ → ℕ) : ∀ e : aexpr, aeval v (fuse e) = aeval v e
  | (const n)     := rfl
  | (var n)       := rfl
  | (plus e₁ e₂)  :=
    show aeval v (simp_const (plus (fuse e₁) (fuse e₂))) = aeval v (plus e₁ e₂),
      by { rw simp_const_eq, unfold aeval, rw [fuse_eq, fuse_eq] }
  | (times e₁ e₂) :=
    show aeval v (simp_const (times (fuse e₁) (fuse e₂))) = aeval v (times e₁ e₂),
      by { rw simp_const_eq, unfold aeval, rw [fuse_eq, fuse_eq] }

end

end exercise_8


/-
Copyright (c) 2019 Sébastien Gouëzel. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sébastien Gouëzel
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.topology.metric_space.baire
import Mathlib.analysis.normed_space.operator_norm
import Mathlib.PostPort

universes u_1 u_2 u_3

namespace Mathlib

/-!
# Banach open mapping theorem

This file contains the Banach open mapping theorem, i.e., the fact that a bijective
bounded linear map between Banach spaces has a bounded inverse.
-/

/--
First step of the proof of the Banach open mapping theorem (using completeness of `F`):
by Baire's theorem, there exists a ball in `E` whose image closure has nonempty interior.
Rescaling everything, it follows that any `y ∈ F` is arbitrarily well approached by
images of elements of norm at most `C * ∥y∥`.
For further use, we will only need such an element whose image
is within distance `∥y∥/2` of `y`, to apply an iterative process. -/
theorem exists_approx_preimage_norm_le {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F] (f : continuous_linear_map 𝕜 E F) [complete_space F] (surj : function.surjective ⇑f) : ∃ (C : ℝ), ∃ (H : C ≥ 0), ∀ (y : F), ∃ (x : E), dist (coe_fn f x) y ≤ 1 / bit0 1 * norm y ∧ norm x ≤ C * norm y := sorry

/-- The Banach open mapping theorem: if a bounded linear map between Banach spaces is onto, then
any point has a preimage with controlled norm. -/
theorem exists_preimage_norm_le {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F] (f : continuous_linear_map 𝕜 E F) [complete_space F] [complete_space E] (surj : function.surjective ⇑f) : ∃ (C : ℝ), ∃ (H : C > 0), ∀ (y : F), ∃ (x : E), coe_fn f x = y ∧ norm x ≤ C * norm y := sorry

/-- The Banach open mapping theorem: a surjective bounded linear map between Banach spaces is
open. -/
theorem open_mapping {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F] (f : continuous_linear_map 𝕜 E F) [complete_space F] [complete_space E] (surj : function.surjective ⇑f) : is_open_map ⇑f := sorry

namespace linear_equiv

/-- If a bounded linear map is a bijection, then its inverse is also a bounded linear map. -/
theorem continuous_symm {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F] [complete_space F] [complete_space E] (e : linear_equiv 𝕜 E F) (h : continuous ⇑e) : continuous ⇑(symm e) := sorry

/-- Associating to a linear equivalence between Banach spaces a continuous linear equivalence when
the direct map is continuous, thanks to the Banach open mapping theorem that ensures that the
inverse map is also continuous. -/
def to_continuous_linear_equiv_of_continuous {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F] [complete_space F] [complete_space E] (e : linear_equiv 𝕜 E F) (h : continuous ⇑e) : continuous_linear_equiv 𝕜 E F :=
  continuous_linear_equiv.mk (mk (to_fun e) sorry sorry (inv_fun e) sorry sorry)

@[simp] theorem coe_fn_to_continuous_linear_equiv_of_continuous {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F] [complete_space F] [complete_space E] (e : linear_equiv 𝕜 E F) (h : continuous ⇑e) : ⇑(to_continuous_linear_equiv_of_continuous e h) = ⇑e :=
  rfl

@[simp] theorem coe_fn_to_continuous_linear_equiv_of_continuous_symm {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F] [complete_space F] [complete_space E] (e : linear_equiv 𝕜 E F) (h : continuous ⇑e) : ⇑(continuous_linear_equiv.symm (to_continuous_linear_equiv_of_continuous e h)) = ⇑(symm e) :=
  rfl

end linear_equiv

namespace continuous_linear_equiv

/-- Convert a bijective continuous linear map `f : E →L[𝕜] F` between two Banach spaces
to a continuous linear equivalence. -/
def of_bijective {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F] [complete_space F] [complete_space E] (f : continuous_linear_map 𝕜 E F) (hinj : continuous_linear_map.ker f = ⊥) (hsurj : continuous_linear_map.range f = ⊤) : continuous_linear_equiv 𝕜 E F :=
  linear_equiv.to_continuous_linear_equiv_of_continuous (linear_equiv.of_bijective (↑f) hinj hsurj) sorry

@[simp] theorem coe_fn_of_bijective {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F] [complete_space F] [complete_space E] (f : continuous_linear_map 𝕜 E F) (hinj : continuous_linear_map.ker f = ⊥) (hsurj : continuous_linear_map.range f = ⊤) : ⇑(of_bijective f hinj hsurj) = ⇑f :=
  rfl

@[simp] theorem of_bijective_symm_apply_apply {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F] [complete_space F] [complete_space E] (f : continuous_linear_map 𝕜 E F) (hinj : continuous_linear_map.ker f = ⊥) (hsurj : continuous_linear_map.range f = ⊤) (x : E) : coe_fn (continuous_linear_equiv.symm (of_bijective f hinj hsurj)) (coe_fn f x) = x :=
  symm_apply_apply (of_bijective f hinj hsurj) x

@[simp] theorem of_bijective_apply_symm_apply {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F] [complete_space F] [complete_space E] (f : continuous_linear_map 𝕜 E F) (hinj : continuous_linear_map.ker f = ⊥) (hsurj : continuous_linear_map.range f = ⊤) (y : F) : coe_fn f (coe_fn (continuous_linear_equiv.symm (of_bijective f hinj hsurj)) y) = y :=
  apply_symm_apply (of_bijective f hinj hsurj) y