import analysis.calculus.cont_diff
import linear_algebra.dual

import notations
import to_mathlib.analysis.normed_space.operator_norm
import to_mathlib.analysis.calculus
import to_mathlib.linear_algebra.basic

/-! # Dual pairs

Convex integration improves maps by successively modify their derivative in a direction of a
vector field (almost) without changing their derivative along some complementary hyperplace.

In this file we introduce the linear algebra part of the story of such modifications.
The main gadget here are dual pairs on a real topological vector space `E`.
Each `p : dual_pair E` is made of a (continuous) linear form `p.π : E →L[ℝ] ℝ` and a vector
`p.v : E` such that `p.π p.v = 1`.

Those pairs are used to build the updating operation turning `φ : E →L[ℝ] F` into
`p.update φ w` which equals `φ` on `ker p.π` and sends `v` to the given `w : F`.

After formalizing the above definitions, we first record a number of trivial algebraic lemmas.
Then we prove a lemma which is geometrically obvious but not so easy to formalize:
`injective_update_iff` states, starting with an injective `φ`, that the updated
map `p.update φ w` is injective if and only if `w` isn't in the image of `ker p.π` under `φ`.
This is crucial in order to apply convex integration to immersions.

Then we prove continuity and smoothness lemmas for this operation.
-/

noncomputable theory

open function continuous_linear_map linear_map (ker)

/-! ## General theory -/

section no_norm
variables (E : Type*) {E' F G : Type*}
variables [add_comm_group E] [module ℝ E] [topological_space E]
variables [add_comm_group E'] [module ℝ E'] [topological_space E']
variables [normed_add_comm_group F] [normed_space ℝ F] [normed_add_comm_group G] [normed_space ℝ G]

/-- A continuous linear form `π` and a vector `v` that pair to one. In particular `ker π` is a
hyperplane and `v` spans a complement of this hyperplane. -/
structure dual_pair :=
(π : E →L[ℝ] ℝ)
(v : E)
(pairing : π v = 1)

namespace dual_pair
variables {E F}

local attribute [simp] continuous_linear_map.to_span_singleton_apply

lemma ker_pi_ne_top (p : dual_pair E) : ker p.π ≠ ⊤ :=
begin
  intro H,
  have : p.π.to_linear_map p.v = 1 := p.pairing,
  simpa [linear_map.ker_eq_top.mp H]
end

lemma pi_ne_zero (p : dual_pair E) : p.π ≠ 0 :=
begin
  intro H,
  simpa [H] using p.pairing
end

lemma v_ne_zero (p : dual_pair E) : p.v ≠ 0 :=
begin
  intro H,
  simpa [H] using p.pairing
end

/-- Given a dual pair `p`, `p.span_v` is the line spanned by `p.v`. -/
def span_v (p : dual_pair E) : submodule ℝ E := submodule.span ℝ {p.v}

lemma mem_span_v (p : dual_pair E) {u : E} : u ∈ p.span_v ↔ ∃ t : ℝ, u = t • p.v :=
by simp [dual_pair.span_v, submodule.mem_span_singleton, eq_comm]

/-- Update a continuous linear map `φ : E →L[ℝ] F` using a dual pair `p` on `E` and a
vector `w : F`. The new map coincides with `φ` on `ker p.π` and sends `p.v` to `w`. -/
def update (p : dual_pair E) (φ : E →L[ℝ] F) (w : F) : E →L[ℝ] F :=
φ + (w - φ p.v) ⬝ p.π

@[simp]
lemma update_ker_pi (p : dual_pair E) (φ : E →L[ℝ] F) (w : F) {u} (hu : u ∈ ker p.π) :
  p.update φ w u = φ u :=
begin
  rw linear_map.mem_ker at hu,
  simp only [update, hu, continuous_linear_map.to_span_singleton_apply, add_zero,
             continuous_linear_map.coe_comp', comp_app, zero_smul, continuous_linear_map.add_apply]
end

@[simp]
lemma update_v (p : dual_pair E) (φ : E →L[ℝ] F) (w : F) :
  p.update φ w p.v = w :=
by simp only [update, p.pairing, continuous_linear_map.to_span_singleton_apply,
              continuous_linear_map.coe_comp', add_sub_cancel'_right, one_smul, comp_app,
              continuous_linear_map.add_apply]

@[simp]
lemma update_self (p : dual_pair E) (φ : E →L[ℝ] F)  :
  p.update φ (φ p.v) = φ :=
by simp only [update, add_zero, continuous_linear_map.to_span_singleton_zero,
              continuous_linear_map.zero_comp, sub_self]

@[simp]
lemma update_update (p : dual_pair E) (φ : E →L[ℝ] F) (w w' : F) :
  p.update (p.update φ w') w = p.update φ w :=
by simp_rw [update, add_apply, coe_comp', comp_app, to_span_singleton_apply, p.pairing,
  one_smul, add_sub_cancel'_right, add_assoc, ← continuous_linear_map.add_comp,
  ← to_span_singleton_add, sub_add_eq_add_sub, add_sub_cancel'_right]

lemma inf_eq_bot (p : dual_pair E) : ker p.π ⊓ p.span_v = ⊥ :=
begin
  rw eq_bot_iff,
  intros x hx,
  have : p.π x = 0 ∧ ∃ a : ℝ, a • p.v = x,
    by simpa [dual_pair.span_v, submodule.mem_span_singleton] using hx,
  rcases this with ⟨H, t, rfl⟩,
  rw [p.π.map_smul, p.pairing, algebra.id.smul_eq_mul, mul_one] at H,
  simp [H]
end

lemma sup_eq_top (p : dual_pair E) : ker p.π ⊔ p.span_v = ⊤ :=
begin
  rw submodule.sup_eq_top_iff,
  intro x,
  refine ⟨x - p.π x • p.v, _, p.π x • p.v, _, _⟩;
  simp [dual_pair.span_v, submodule.mem_span_singleton, p.pairing]
end

lemma decomp (p : dual_pair E) (e : E) : ∃ u ∈ ker p.π, ∃ t : ℝ, e = u + t•p.v :=
begin
  have : e ∈ ker p.π ⊔ p.span_v,
  { rw p.sup_eq_top,
    exact submodule.mem_top },
  simp_rw [submodule.mem_sup, dual_pair.mem_span_v] at this,
  rcases this with ⟨u, hu, -, ⟨t, rfl⟩, rfl⟩,
  use [u, hu, t, rfl]
end

-- useful with `dual_pair.decomp`
lemma update_apply (p : dual_pair E) (φ : E →L[ℝ] F) {w : F} {t : ℝ} {u} (hu : u ∈ ker p.π) :
  p.update φ w (u + t • p.v) = φ u + t • w :=
by rw [map_add, map_smul, p.update_v, p.update_ker_pi _ _ hu]

/-- Map a dual pair under a linear equivalence. -/
@[simps] def map (p : dual_pair E) (L : E ≃L[ℝ] E') : dual_pair E' :=
⟨p.π ∘L ↑L.symm, L p.v, (congr_arg p.π $ L.symm_apply_apply p.v).trans p.pairing⟩

lemma update_comp_left (p : dual_pair E) (ψ : F →L[ℝ] G) (φ : E →L[ℝ] F) (w : F) :
  p.update (ψ ∘L φ) (ψ w) = ψ ∘L p.update φ w :=
begin
  ext1 u,
  simp only [update, add_apply, continuous_linear_map.comp_apply, to_span_singleton_apply,
    ψ.map_add, ψ.map_smul, ψ.map_sub],
end

lemma map_update_comp_right (p : dual_pair E) (ψ : E →L[ℝ] F) (φ : E ≃L[ℝ] E') (w : F) :
  (p.map φ).update (ψ ∘L ↑φ.symm) w = p.update ψ w ∘L ↑φ.symm :=
begin
  ext1 u,
  simp only [update, add_apply, continuous_linear_map.comp_apply, to_span_singleton_apply, map,
    continuous_linear_equiv.coe_coe, φ.symm_apply_apply],
end

/-! ## Injectivity criterion -/

@[simp] lemma injective_update_iff
  (p : dual_pair E) {φ : E →L[ℝ] F} (hφ : injective φ) {w : F} :
  injective (p.update φ w) ↔ w ∉ (ker p.π).map φ :=
begin
  split,
  { rintros h ⟨u, hu, rfl⟩,
    have : p.update φ (φ u) p.v = φ u,
    exact p.update_v φ (φ u),
    conv_rhs at this { rw ←  p.update_ker_pi φ (φ u) hu },
    rw ← h this at hu,
    simp only [set_like.mem_coe, linear_map.mem_ker] at hu,
    rw p.pairing at hu,
    linarith },
  { intros hw,
    apply (injective_iff_map_eq_zero (p.update φ w)).mpr (λ x hx, _),
    rcases p.decomp x with ⟨u, hu, t, rfl⟩,
    rw [map_add, map_smul, update_v, p.update_ker_pi _ _ hu] at hx,
    have ht : t = 0,
    { by_contra ht,
      apply hw,
      refine ⟨-t⁻¹ • u, (ker p.π).smul_mem _ hu, _⟩,
      rw map_smul,
      have : -t⁻¹ • (φ u + t • w) + w = -t⁻¹ • 0 + w := congr_arg (λ u : F, -t⁻¹ • u + w) hx,
      rwa [smul_add, neg_smul, neg_smul, inv_smul_smul₀ ht, smul_zero, zero_add,
           neg_add_cancel_right, ← neg_smul] at this },
    rw [ht, zero_smul, add_zero] at hx ⊢,
    exact (injective_iff_map_eq_zero φ).mp hφ u hx }
end

end dual_pair
end no_norm

/-! ## Smoothness and continuity -/

namespace dual_pair
variables {E : Type*} [normed_add_comm_group E] [normed_space ℝ E]
          {F : Type*} [normed_add_comm_group F] [normed_space ℝ F]

/- In the next two lemmas, finite dimensionality of `E` is clearly uneeded, but allows
to use `cont_diff_clm_apply_iff` and `continuous_clm_apply`. -/

lemma smooth_update [finite_dimensional ℝ E] (p : dual_pair E)
  {G : Type*} [normed_add_comm_group G] [normed_space ℝ G]
  {φ : G → (E →L[ℝ] F)} (hφ : 𝒞 ∞ φ) {w : G → F} (hw : 𝒞 ∞ w) :
  𝒞 ∞ (λ g, p.update (φ g) (w g)) :=
begin
  apply hφ.add,
  rw cont_diff_clm_apply_iff,
  intro y,
  exact (hw.sub (cont_diff_clm_apply_iff.mp hφ p.v)).const_smul _,
end

lemma continuous_update [finite_dimensional ℝ E] (p : dual_pair E)
  {X : Type*} [topological_space X]
  {φ : X → (E →L[ℝ] F)} (hφ : continuous φ) {w : X → F} (hw : continuous w) :
  continuous (λ g, p.update (φ g) (w g)) :=
begin
  apply hφ.add,
  rw continuous_clm_apply,
  intro y,
  exact (hw.sub (continuous_clm_apply.mp hφ p.v)).const_smul _
end

end dual_pair

variables {E : Type*} [normed_add_comm_group E] [normed_space ℝ E]
          {F : Type*} [normed_add_comm_group F] [normed_space ℝ F]

/-- Given a finite basis `e : basis ι ℝ E`, and `i : ι`, `e.dual_pair i`
is given by the `i`th basis element and its dual. -/
def basis.dual_pair [finite_dimensional ℝ E] {ι : Type*} [fintype ι] [decidable_eq ι]
  (e : basis ι ℝ E) (i : ι) : dual_pair E :=
{ π := (e.dual_basis i).to_continuous_linear_map,
  v := e i,
  pairing := by simp only [basis.coord_apply, finsupp.single_eq_same, basis.repr_self,
                           linear_map.coe_to_continuous_linear_map', basis.coe_dual_basis] }


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

example : (∀ x, p x ∧ q x) ↔ (∀ x, p x) ∧ (∀ x, q x) :=
  have (∀ x, p x ∧ q x) → (∀ x, p x) ∧ (∀ x, q x),
    from assume h: (∀ x, p x ∧ q x),
    have (∀ x, p x), from assume x, (h x).left,
    have (∀ x, q x), from assume x, (h x).right,
    by from and.intro (by assumption) this,
  have (∀ x, p x) ∧ (∀ x, q x) → (∀ x, p x ∧ q x),
    from assume h: (∀ x, p x) ∧ (∀ x, q x),
    assume x,
    ⟨h.left x, h.right x⟩,
  iff.intro (by assumption) (by assumption).
example : (∀ x, p x → q x) → (∀ x, p x) → (∀ x, q x) :=
  assume Hpq: ∀ x, p x → q x,
  assume Hp: ∀ x, p x,
  assume x,
  show q x, from ((Hpq x) (Hp x)).
example : (∀ x, p x) ∨ (∀ x, q x) → ∀ x, p x ∨ q x :=
  have (∀ x, p x) → ∀ x, p x ∨ q x,
    from assume Hp x, or.intro_left _ (Hp x),
  have (∀ x, q x) → ∀ x, p x ∨ q x,
    from assume Hq x, or.intro_right _ (Hq x),
  assume h, or.elim h (by assumption) (by assumption).

end sec_4_6_1

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

/- Without Classic -/
example : α → ((∀ x : α, r) ↔ r) :=
  assume a: α,
  have (∀ x : α, r) → r,
    from assume Hr, Hr a,
  have r → (∀ x : α, r),
    from assume r x, r,
  iff.intro ‹(∀ x : α, r) → r› ‹r → (∀ x : α, r)›.
theorem thm2 : (∀ x, p x) ∨ r → (∀ x, p x ∨ r) :=
  have (∀ x, p x) → (∀ x, p x ∨ r),
    from assume Hp x, or.intro_left _ (Hp x),
  have r → (∀ x, p x ∨ r),
    from assume Hr x, or.intro_right _ Hr,
  assume Hpr,
  or.elim Hpr (by assumption) (by assumption).
example : (∀ x, r → p x) ↔ (r → ∀ x, p x) :=
  have (∀ x, r → p x) → (r → ∀ x, p x),
    from assume Hrp r x, Hrp x r,
  have (r → ∀ x, p x) → (∀ x, r → p x),
    from assume Hrp x r, Hrp r x,
  iff.intro (by assumption) (by assumption).

/- With Classic -/
open classical
example : (∀ x, p x ∨ r) ↔ (∀ x, p x) ∨ r :=
  have (∀ x, p x ∨ r) → (∀ x, p x) ∨ r,
    from assume Hpr,
      have r → (∀ x, p x) ∨ r,
        from assume Hr, or.intro_right _ Hr,
      have ¬ r → (∀ x, p x),
        from assume Hnr x,
          have p x ∨ r, from Hpr x,
        sorry,
    sorry,
  sorry.

end sec_4_6_2


import ..fglib
import ..basic

namespace FG

/- ## Vector

  Defined as a function from a finite natural number to the value on that dimension.
  Note that study complex numbers are studied here since
  they are the most commonly used field in physics. -/

def vec (n : ℕ) := (fin (n + 1) → ℂ)

namespace vec

variables {n : ℕ}

@[ext] theorem ext (v w : vec n) :
  (∀(i : fin (n + 1)), v i = w i) → v = w :=
funext

@[simp] def zero : vec n :=
  λ_, 0

@[simp] def map (f : ℂ → ℂ) (v : vec n) : vec n :=
  λi, f (v i)

@[simp] def map₂ (f : ℂ → ℂ → ℂ) (v w : vec n) : vec n :=
  λi, f (v i) (w i)

@[simp] def add (v w : vec n) : vec n :=
  map₂ (+) v w

@[simp] lemma mapped₂_nth (f : ℂ → ℂ → ℂ) (v w : vec n) :
  ∀(i : fin (n + 1)), (map₂ f v w) i = f (v i) (w i) :=
by intro i; simp

/- We can then show that `vec n` is an `add_comm_monoid`. -/
@[simps] instance : add_comm_monoid (vec n) :=
{ zero := zero,
  add := add,
  add_assoc :=
  begin
    intros a b c,
    apply ext,
    intro i,
    calc (a.add b).add c i = a.add b i + c i
        : by apply mapped₂_nth
      ... = a i + b i + c i
        : by rw add; rw mapped₂_nth (+) a b
      ... = a i + b.add c i
        : by rw [add, mapped₂_nth (+) b c, add_assoc]
      ... = (a.add (b.add c)) i
        : (mapped₂_nth (+) a (b.add c) i).symm
  end,
  zero_add :=
  begin
    intro a,
    apply ext,
    intro i,
    simp,
  end,
  add_zero :=
  begin
    intro a,
    apply ext,
    intro i,
    simp,
  end,
  add_comm :=
  begin
    intros a b,
    apply ext,
    intro i,
    calc a.add b i = a i + b i
        : by apply mapped₂_nth
      ... = b i + a i
        : by rw add_comm
      ... = b.add a i
        : by rw [add, mapped₂_nth (+) b a]
  end }

def smul (c : ℂ) (v : vec n) : vec n :=
  map (λx, c * x) v

/- and a module over `ℂ`. -/
@[simps] instance : module ℂ (vec n) :=
{ smul := smul,
  smul_zero :=
  begin
    intro a,
    apply ext,
    simp [smul]
  end,
  smul_add :=
  begin
    intros a v w,
    apply ext,
    intro i,
    simp [smul],
    ring
  end,
  zero_smul :=
  begin
    intro v,
    apply ext,
    simp [smul]
  end,
  one_smul :=
  begin
    intro v,
    apply ext,
    simp [smul]
  end,
  add_smul :=
  begin
    intros a v w,
    apply ext,
    intro i,
    simp [smul],
    ring
  end,
  mul_smul :=
  begin
    intros a v w,
    apply ext,
    intro i,
    simp [smul],
    ring
  end }

end vec

end FG


import data.rat
import tactic
/-
# Week 4: Proving things in Lean

This week the goal is to begin talking about the process of proving theorems in Lean.

## Lean is a programming language

Lean is like any other programming language, thought it may not seem like it from our use-cases
so far. You can write totally functional. You can use `lean --run` in the terminal which will run
the `main` function in any Lean file. Check out `hello_world.lean` and `food_recommender.lean` to
get a sense of the syntax, and see what the language is capable of (in principle anything, though
Lean 3 has a very limited standard library, so it may not be suitable for intricate tasks).

If Lean is just a programming language, then what are we actually doing when we prove theorems?
The foundation for proof formalization in Lean is Lean's highly expressive **type system**. In Lean
everything has a fixed _type_. When we declare variables, we give their names and also provide a
typing judgement with `(x : t)` signifying that the variable `x` has type `t`. Everything's type
can be checked with `#check`.
-/
variables (a₁ : ℕ) (a₂ : nat) (b₁ : bool) (b₂ : bool)

#check a₁
#check a₁ + a₂
#check b₁ || b₂
-- #check b₁ + b₂

#check 3
#check 4.7

/-
As you can see above, Lean has some weird behaviour around thinking all numbers are natural numbers
An expression can be evaluated using `#eval`
-/
#eval 4.7

/-
That's not the answer we were expecting! To get the right answer we can force any expression of
one type into another type (**coercion**) . This will only work if Lean has some way of
-/
#check (4.7 : ℚ)
#eval (4.7 : ℚ)

-- #check -3

#check (-3 : ℤ)

/-
When I say everything has a type, I really mean EVERYTHING! Including functions! In fact, even
if we don't tell Lean explicitly what the type of everything is, it will still try to determine it
(and throw an error if it is unable to).
-/
def f := ( + 3)
#check f
#eval f 3

def g := (+ (3 : ℚ))
#check g
#eval g (3/2)
#eval g 9

/-
Functions with multiple inputs are handled via currying. Very briefly, the basic idea is that a
function `f₁ : X × Y → Z` is equivalent to a function `f₂ : X → (Y → Z)` via
`f₁(x, y) = (f₂(x))(y)` (though Lean can do the same with a lot less parentheses).

Finally, notice the notation `f : X → Y` is playing a double role, one as being the usual
mathematician's notation for functions from `X` to `Y`, but also it is a typing judgement
that says `f` has type `X → Y`)
-/
variable h : ℕ → ℕ → ℕ
#check h

def func₁ : ℕ → ℕ → ℕ := λ x y, x + 2*y + x + y
def func₂ (x : ℕ) (y : ℕ) : ℕ := 2*x + 3*y

#check func₁
#check func₂

#check func₁ 2
#check func₂ 2 3
#reduce func₁

/-
But our interest in Lean is in its ability to prove things! Be it about mathematical objects, or
simply just reason about functions defined in Lean itself.
-/
theorem func₁_eq_func₂ : func₁ = func₂ :=
begin
ext x y,
/-
Here we see a new tactic, called `ext`. If the work has been done in defining things appropriately,
`ext` reduces the goal of showing two functions are equal to the goal that the functions are equal
on all inputs.
-/
unfold func₁,
/-
Here we see a new tactic, called `unfold`. If we are trying to prove something about some named
object, we can unfold its definitions to get it in simpler terms. (Note this doesn't work on
structures, classes, and stuff. Just things defined with `def` (or `lemma` or `theorem`)!
-/
unfold func₂,
/-
At this point our goal is something very easily achievable. We could do this by hand, but we're
tired of the natural number game, so lets just use the `ring` tactic.
-/
ring
end

/-
So what were we actually doing in the above proof? Fundamentally, constructing a proof in Lean is
just a very fancy version of building a function with the appropriate inputs (hypotheses), and
output.

The precise version of all of this is called the Curry-Howard correspondence
<https://en.wikipedia.org/wiki/Curry%E2%80%93Howard_correspondence>.
-/

/-
Tactics mode is also nothing special! It's just a useful tool built into Lean for constructing
functions. For example, we could
-/
meta def max_of_list : list ℕ → ℕ
| L :=
begin
cases L with head tail,
exact 0,
exact max head (max_of_list tail)
end

/-
What is the type of
-/
#check func₁_eq_func₂
#check func₁ = func₂
#check 3 = 3
#check (=) -- Meta variables appear when Lean can't specify the type of a given expression.
#check (=) (3 : ℚ )
#check @eq -- show all the implicit arguments of an expression by including an `@`.

/-
So if we're building functions from types to types, what are all the kinds of types that are
possible?

Lean's builtin type system has only a few (infinitely many) built in types. There is one for every
natural number `n`, these are all refered to as `Sort n`.
-/
#check Prop
#check Type
#check Sort 1
#check Sort 2

/-
There are a few synonyms, like `Sort 0 = Sort = Prop`
-/
example : Sort   = Sort 0 := by refl
example : Prop   = Sort   := by refl

/-
And `Sort 1 = Type = Type 0`
-/
example : Type   = Sort 1 := by refl
example : Type 0 = Sort 1 := by refl

/-
and `Sort k = Type (k - 1)`
-/
example : Type 1 = Sort 2 := by refl
example : Type 2 = Sort 3 := by refl
-- and so on...

/-
The fact that `Type 0 : Type 1` is similar to the issue about why there is no set of all sets.
A convenient way of making definitions and proofs as general as possible is by introducing
_universe variables_ and defining objects independent of universe.
-/
universes u v
variables (A : Type u) (B : Type v)

#check (A → B) -- The type is a function type is always `max u v`.

#check add_comm_group -- `u_1` is playing the role of a metavariable for universe level.
#check add_comm_group B
#check add_comm_group.add
#check @add_comm_group.add

/-
`Prop` i.e. `Sort 0` has the property that any two instances of a `Prop` are definitionally equal.
This is called the proof irrelevance, and is a particular choice that Lean has made.
 This is not required, and alternatives like Homotopy Type Theory exist.
-/
example {α : Type} {a b : α} (h : a = b) (h' : a = b) : h = h' := by refl

/-
With this in mind, it's easy to see exactly how we can compare proofs of propositions with
definitions of functions
-/
variables (P Q R : Prop)

example : (P → Q) → ((P → Q) → R) → R :=
begin
sorry
end

example : P → (P → Q) → (Q → R) → R := sorry

/-
The final pieces of the puzzle under the hood of Lean is Lean's support for **dependent types**,
which are expressions that look like `Π (x : A), B`.
-/
#check @list.append
#check @eq
#check @eq ℕ
#check @list.append string
#check Π T : Type, T → T

/-
Everything else that you will deal with is built on top of the Lean kernel as something called an
**inductive data type**. We've seen a few examples of these before
-/
#check list
#check nat
inductive z2 : Type
| e : z2
| s : z2

/-
Even things like structures and classes are inductive data types under the hood (though there's
a lot more going on under the hood as well)
-/
#check and
#check or

#print prefix and -- `print prefix <thing>` prints everything known in the `<thing>` namespace

/-
You can handle inductive data types via tactics like `cases`, `induction`, `split`, ...

For example, we have seen the existential quantifier `∃` in the natural number game, and it may
have seemed strange that we dealt with it via `cases` in the NNG. Well if we dig a little deeper,
we see that it is in fact implemented as an inductive data type!
-/
#check Exists
#check @Exists
#print prefix Exists

#check (∧)
#check (∨)

#check and.intro
#check exists.intro

example {n : ℕ} (h : even n) : odd (n + 1) ∧ even (n + 2) :=
begin
apply and.intro,
{
apply exists.elim h,
intros,
apply exists.intro a,
sorry,
},
apply exists.elim h,
intros,
apply exists.intro a,
sorry,
end

example {n : ℕ} (h : even n) : odd (n + 1) ∧ even (n + 2) :=
begin
split,
cases h with k hk,
use k,
sorry,
sorry,
end

example {n : ℕ} (h : even n) : odd (n + 1) ∧ even (n + 2) :=
 and.intro (exists.elim h (λa h, exists.intro a (by rw h)))
           (exists.elim h (λa h, exists.intro (a + 1) (by {rw h, ring})))

set_option pp.beta true

/-
Even equality is an inductive data type!
-/
#check @eq

/-
What about things like rewriting expressions? The `rewrite` or `rw` tactic is actually just a
smart tactic someone wrote that trys to apply the following operation on equalities
-/
#check eq.subst
#check @eq.subst

/-
An alternative notation for this function is.
-/
#check (▸)

/-
For example consider the following proof
-/

variables (a b c : ℕ) (hyp : b = a)

-- #check @eq.subst ℕ (λk, k + c = b + c) b a hyp

example (hyp : a = b) : a + c = b + c :=
begin
sorry
end

/-
There's also some tools built into lean that allow you to be even more precise about how you rewrite
terms using the conversion tactic. Conversion uses a way of navigating expressions with things like
`to_lhs`, `to_rhs` to enter on the left or right of a relation. `congr` to deal with all of the
arguments, and `funext` to enter inside the arguments of a function.
-/
example : a + c = c + b :=
begin
-- conv
--   begin
--   end
sorry
end

/-
Finally, there's the final small issue about computability, and the axiom of choice. Lean has
skirted this issue by allowing the user to choose whether or not they want to work in a computable
world
-/
#print axioms

/-
The axiom of choice is about exactly what you'd expect
-/
#check @classical.choice

/-
You may also see a `quot.sound` entry in the list of axioms, and this is an important axiom
that we'll talk about soon. In essense, another basic construction built into Lean to deal with
quotients is `quot`
-/
#check @quot
#print prefix quot -- You can see all of what is known about quot, the key elements ar
#check quot.mk
#check quot.map
#check quot.lift

/-
Of course the whole point of Lean is to be a proof **assistant**, not a hinderance. This talk was
just meant to be an illustration of what's happening under the hood, not how one interacts with the
proof assistant on a day to day basis.
-/

/-
The whole point of Lean is that it does a lot for you! Implicit class instantiation, tactics,
pattern matching, automatic methods created by structure and class, etc etc...
-/


/-
Copyright (c) 2014 Jeremy Avigad. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jeremy Avigad, Leonardo de Moura
Ported by: Winston Yin

! This file was ported from Lean 3 source module data.set.image
! leanprover-community/mathlib commit 4550138052d0a416b700c27056d492e2ef53214e
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Data.Set.Basic

/-!
# Images and preimages of sets

## Main definitions

* `preimage f t : Set α` : the preimage f⁻¹(t) (written `f ⁻¹' t` in Lean) of a subset of β.

* `range f : Set β` : the image of `univ` under `f`.
  Also works for `{p : Prop} (f : p → α)` (unlike `image`)

## Notation

* `f ⁻¹' t` for `Set.preimage f t`

* `f '' s` for `Set.image f s`

## Tags

set, sets, image, preimage, pre-image, range

-/

universe u v

open Function Set

namespace Set

variable {α β γ : Type _} {ι ι' : Sort _}

/-! ### Inverse image -/

/-- The preimage of `s : Set β` by `f : α → β`, written `f ⁻¹' s`,
  is the set of `x : α` such that `f x ∈ s`. -/
def preimage {α : Type u} {β : Type v} (f : α → β) (s : Set β) : Set α :=
  { x | f x ∈ s }
#align set.preimage Set.preimage

/-- `f ⁻¹' t` denotes the preimage of `t : Set β` under the function `f : α → β`. -/
infixl:80 " ⁻¹' " => preimage

section Preimage

variable {f : α → β} {g : β → γ}

@[simp]
theorem preimage_empty : f ⁻¹' ∅ = ∅ :=
  rfl
#align set.preimage_empty Set.preimage_empty

@[simp]
theorem mem_preimage {s : Set β} {a : α} : a ∈ f ⁻¹' s ↔ f a ∈ s :=
  Iff.rfl
#align set.mem_preimage Set.mem_preimage

theorem preimage_congr {f g : α → β} {s : Set β} (h : ∀ x : α, f x = g x) : f ⁻¹' s = g ⁻¹' s := by
  congr with x
  simp [h]
#align set.preimage_congr Set.preimage_congr

theorem preimage_mono {s t : Set β} (h : s ⊆ t) : f ⁻¹' s ⊆ f ⁻¹' t := fun _ hx => h hx
#align set.preimage_mono Set.preimage_mono

@[simp]
theorem preimage_univ : f ⁻¹' univ = univ :=
  rfl
#align set.preimage_univ Set.preimage_univ

theorem subset_preimage_univ {s : Set α} : s ⊆ f ⁻¹' univ :=
  subset_univ _
#align set.subset_preimage_univ Set.subset_preimage_univ

@[simp]
theorem preimage_inter {s t : Set β} : f ⁻¹' (s ∩ t) = f ⁻¹' s ∩ f ⁻¹' t :=
  rfl
#align set.preimage_inter Set.preimage_inter

@[simp]
theorem preimage_union {s t : Set β} : f ⁻¹' (s ∪ t) = f ⁻¹' s ∪ f ⁻¹' t :=
  rfl
#align set.preimage_union Set.preimage_union

@[simp]
theorem preimage_compl {s : Set β} : f ⁻¹' sᶜ = (f ⁻¹' s)ᶜ :=
  rfl
#align set.preimage_compl Set.preimage_compl

@[simp]
theorem preimage_diff (f : α → β) (s t : Set β) : f ⁻¹' (s \ t) = f ⁻¹' s \ f ⁻¹' t :=
  rfl
#align set.preimage_diff Set.preimage_diff

@[simp]
theorem preimage_ite (f : α → β) (s t₁ t₂ : Set β) :
    f ⁻¹' s.ite t₁ t₂ = (f ⁻¹' s).ite (f ⁻¹' t₁) (f ⁻¹' t₂) :=
  rfl
#align set.preimage_ite Set.preimage_ite

@[simp]
theorem preimage_setOf_eq {p : α → Prop} {f : β → α} : f ⁻¹' { a | p a } = { a | p (f a) } :=
  rfl
#align set.preimage_set_of_eq Set.preimage_setOf_eq

@[simp]
theorem preimage_id_eq : preimage (id : α → α) = id :=
  rfl
#align set.preimage_id_eq Set.preimage_id_eq

theorem preimage_id {s : Set α} : id ⁻¹' s = s :=
  rfl
#align set.preimage_id Set.preimage_id

@[simp]
theorem preimage_id' {s : Set α} : (fun x => x) ⁻¹' s = s :=
  rfl
#align set.preimage_id' Set.preimage_id'

@[simp]
theorem preimage_const_of_mem {b : β} {s : Set β} (h : b ∈ s) : (fun _ : α => b) ⁻¹' s = univ :=
  eq_univ_of_forall fun _ => h
#align set.preimage_const_of_mem Set.preimage_const_of_mem

@[simp]
theorem preimage_const_of_not_mem {b : β} {s : Set β} (h : b ∉ s) : (fun _ : α => b) ⁻¹' s = ∅ :=
  eq_empty_of_subset_empty fun _ hx => h hx
#align set.preimage_const_of_not_mem Set.preimage_const_of_not_mem

theorem preimage_const (b : β) (s : Set β) [Decidable (b ∈ s)] :
    (fun _ : α => b) ⁻¹' s = if b ∈ s then univ else ∅ := by
  split_ifs with hb
  exacts[preimage_const_of_mem hb, preimage_const_of_not_mem hb]
#align set.preimage_const Set.preimage_const

theorem preimage_comp {s : Set γ} : g ∘ f ⁻¹' s = f ⁻¹' (g ⁻¹' s) :=
  rfl
#align set.preimage_comp Set.preimage_comp

theorem preimage_comp_eq : preimage (g ∘ f) = preimage f ∘ preimage g :=
  rfl
#align set.preimage_comp_eq Set.preimage_comp_eq

@[simp]
theorem preimage_iterate_eq {f : α → α} {n : ℕ} : Set.preimage (f^[n]) = Set.preimage f^[n] := by
  induction' n with n ih; · simp
  rw [iterate_succ, iterate_succ', Set.preimage_comp_eq, ih]
#align set.preimage_iterate_eq Set.preimage_iterate_eq

theorem preimage_preimage {g : β → γ} {f : α → β} {s : Set γ} :
    f ⁻¹' (g ⁻¹' s) = (fun x => g (f x)) ⁻¹' s :=
  preimage_comp.symm
#align set.preimage_preimage Set.preimage_preimage

theorem eq_preimage_subtype_val_iff {p : α → Prop} {s : Set (Subtype p)} {t : Set α} :
    s = Subtype.val ⁻¹' t ↔ ∀ (x) (h : p x), (⟨x, h⟩ : Subtype p) ∈ s ↔ x ∈ t :=
  ⟨fun s_eq x h => by
    rw [s_eq]
    simp, fun h => ext fun ⟨x, hx⟩ => by simp [h]⟩
#align set.eq_preimage_subtype_val_iff Set.eq_preimage_subtype_val_iff

theorem nonempty_of_nonempty_preimage {s : Set β} {f : α → β} (hf : (f ⁻¹' s).Nonempty) :
    s.Nonempty :=
  let ⟨x, hx⟩ := hf
  ⟨f x, hx⟩
#align set.nonempty_of_nonempty_preimage Set.nonempty_of_nonempty_preimage

theorem preimage_subtype_coe_eq_compl {α : Type _} {s u v : Set α} (hsuv : s ⊆ u ∪ v)
    (H : s ∩ (u ∩ v) = ∅) : ((↑) : s → α) ⁻¹' u = ((↑) ⁻¹' v)ᶜ := by
  ext ⟨x, x_in_s⟩
  constructor
  · intro x_in_u x_in_v
    exact eq_empty_iff_forall_not_mem.mp H x ⟨x_in_s, ⟨x_in_u, x_in_v⟩⟩
  · intro hx
    exact Or.elim (hsuv x_in_s) id fun hx' => hx.elim hx'
#align set.preimage_subtype_coe_eq_compl Set.preimage_subtype_coe_eq_compl

end Preimage

/-! ### Image of a set under a function -/

section Image

variable {f : α → β} {s t : Set α}

-- Porting note: `Set.image` is already defined in `Init.Set`
#align set.image Set.image

/-- `f '' s` denotes the image of `s : Set α` under the function `f : α → β`. -/
infixl:80 " '' " => image

theorem mem_image_iff_bex {f : α → β} {s : Set α} {y : β} :
    y ∈ f '' s ↔ ∃ (x : _)(_ : x ∈ s), f x = y :=
  bex_def.symm
#align set.mem_image_iff_bex Set.mem_image_iff_bex

@[simp]
theorem mem_image (f : α → β) (s : Set α) (y : β) : y ∈ f '' s ↔ ∃ x, x ∈ s ∧ f x = y :=
  Iff.rfl
#align set.mem_image Set.mem_image

theorem image_eta (f : α → β) : f '' s = (fun x => f x) '' s :=
  rfl
#align set.image_eta Set.image_eta

theorem mem_image_of_mem (f : α → β) {x : α} {a : Set α} (h : x ∈ a) : f x ∈ f '' a :=
  ⟨_, h, rfl⟩
#align set.mem_image_of_mem Set.mem_image_of_mem

theorem _root_.Function.Injective.mem_set_image {f : α → β} (hf : Injective f) {s : Set α} {a : α} :
    f a ∈ f '' s ↔ a ∈ s :=
  ⟨fun ⟨_, hb, Eq⟩ => hf Eq ▸ hb, mem_image_of_mem f⟩
#align function.injective.mem_set_image Function.Injective.mem_set_image

theorem ball_image_iff {f : α → β} {s : Set α} {p : β → Prop} :
    (∀ y ∈ f '' s, p y) ↔ ∀ x ∈ s, p (f x) := by simp
#align set.ball_image_iff Set.ball_image_iff

theorem ball_image_of_ball {f : α → β} {s : Set α} {p : β → Prop} (h : ∀ x ∈ s, p (f x)) :
    ∀ y ∈ f '' s, p y :=
  ball_image_iff.2 h
#align set.ball_image_of_ball Set.ball_image_of_ball

theorem bex_image_iff {f : α → β} {s : Set α} {p : β → Prop} :
    (∃ y ∈ f '' s, p y) ↔ ∃ x ∈ s, p (f x) := by simp
#align set.bex_image_iff Set.bex_image_iff

theorem mem_image_elim {f : α → β} {s : Set α} {C : β → Prop} (h : ∀ x : α, x ∈ s → C (f x)) :
    ∀ {y : β}, y ∈ f '' s → C y
  | _, ⟨a, a_in, rfl⟩ => h a a_in
#align set.mem_image_elim Set.mem_image_elim

theorem mem_image_elim_on {f : α → β} {s : Set α} {C : β → Prop} {y : β} (h_y : y ∈ f '' s)
    (h : ∀ x : α, x ∈ s → C (f x)) : C y :=
  mem_image_elim h h_y
#align set.mem_image_elim_on Set.mem_image_elim_on

-- Porting note: used to be `safe`
@[congr]
theorem image_congr {f g : α → β} {s : Set α} (h : ∀ a ∈ s, f a = g a) : f '' s = g '' s := by
  ext x
  rw [mem_image, mem_image]
  exact {
    mp := by
      rintro ⟨a, ha1, ha2⟩
      exact ⟨a, ⟨ha1, (h a ha1) ▸ ha2⟩⟩,
    mpr := by
      rintro ⟨a, ha1, ha2⟩
      exact ⟨a, ⟨ha1, (h a ha1) ▸ ha2⟩⟩
  }
  -- safe [ext_iff, iff_def]
#align set.image_congr Set.image_congr

/-- A common special case of `image_congr` -/
theorem image_congr' {f g : α → β} {s : Set α} (h : ∀ x : α, f x = g x) : f '' s = g '' s :=
  image_congr fun x _ => h x
#align set.image_congr' Set.image_congr'

theorem image_comp (f : β → γ) (g : α → β) (a : Set α) : f ∘ g '' a = f '' (g '' a) :=
  Subset.antisymm (ball_image_of_ball fun _ ha => mem_image_of_mem _ <| mem_image_of_mem _ ha)
    (ball_image_of_ball <| ball_image_of_ball fun _ ha => mem_image_of_mem _ ha)
#align set.image_comp Set.image_comp

/-- A variant of `image_comp`, useful for rewriting -/
theorem image_image (g : β → γ) (f : α → β) (s : Set α) : g '' (f '' s) = (fun x => g (f x)) '' s :=
  (image_comp g f s).symm
#align set.image_image Set.image_image

theorem image_comm {β'} {f : β → γ} {g : α → β} {f' : α → β'} {g' : β' → γ}
    (h_comm : ∀ a, f (g a) = g' (f' a)) : (s.image g).image f = (s.image f').image g' := by
  simp_rw [image_image, h_comm]
#align set.image_comm Set.image_comm

theorem _root_.Function.Semiconj.set_image {f : α → β} {ga : α → α} {gb : β → β}
    (h : Function.Semiconj f ga gb) : Function.Semiconj (image f) (image ga) (image gb) := fun _ =>
  image_comm h
#align function.semiconj.set_image Function.Semiconj.set_image

theorem _root_.Function.Commute.set_image {f g : α → α} (h : Function.Commute f g) :
    Function.Commute (image f) (image g) :=
  Function.Semiconj.set_image h
#align function.commute.set_image Function.Commute.set_image

/-- Image is monotone with respect to `⊆`. See `Set.monotone_image` for the statement in
terms of `≤`. -/
theorem image_subset {a b : Set α} (f : α → β) (h : a ⊆ b) : f '' a ⊆ f '' b := by
  simp only [subset_def, mem_image]
  exact fun x => fun ⟨w, h1, h2⟩ => ⟨w, h h1, h2⟩
#align set.image_subset Set.image_subset

/-- `Set.image` is monotone. See `Set.image_subset` for the statement in terms of `⊆`. -/
lemma monotone_image {f : α → β} : Monotone (image f) := fun _ _ => image_subset _
#align set.monotone_image Set.monotone_image

theorem image_union (f : α → β) (s t : Set α) : f '' (s ∪ t) = f '' s ∪ f '' t :=
  ext fun x =>
    ⟨by rintro ⟨a, h | h, rfl⟩ <;> [left, right] <;> exact ⟨_, h, rfl⟩, by
      rintro (⟨a, h, rfl⟩ | ⟨a, h, rfl⟩) <;> refine' ⟨_, _, rfl⟩ <;> [left, right] <;> exact h⟩
#align set.image_union Set.image_union

@[simp]
theorem image_empty (f : α → β) : f '' ∅ = ∅ := by
  ext
  simp
#align set.image_empty Set.image_empty

theorem image_inter_subset (f : α → β) (s t : Set α) : f '' (s ∩ t) ⊆ f '' s ∩ f '' t :=
  subset_inter (image_subset _ <| inter_subset_left _ _) (image_subset _ <| inter_subset_right _ _)
#align set.image_inter_subset Set.image_inter_subset

theorem image_inter_on {f : α → β} {s t : Set α} (h : ∀ x ∈ t, ∀ y ∈ s, f x = f y → x = y) :
    f '' (s ∩ t) = f '' s ∩ f '' t :=
  (image_inter_subset _ _ _).antisymm
    fun b ⟨⟨a₁, ha₁, h₁⟩, ⟨a₂, ha₂, h₂⟩⟩ ↦
      have : a₂ = a₁ := h _ ha₂ _ ha₁ (by simp [*])
      ⟨a₁, ⟨ha₁, this ▸ ha₂⟩, h₁⟩
#align set.image_inter_on Set.image_inter_on

theorem image_inter {f : α → β} {s t : Set α} (H : Injective f) : f '' (s ∩ t) = f '' s ∩ f '' t :=
  image_inter_on fun _ _ _ _ h => H h
#align set.image_inter Set.image_inter

theorem image_univ_of_surjective {ι : Type _} {f : ι → β} (H : Surjective f) : f '' univ = univ :=
  eq_univ_of_forall <| by simpa [image]
#align set.image_univ_of_surjective Set.image_univ_of_surjective

@[simp]
theorem image_singleton {f : α → β} {a : α} : f '' {a} = {f a} := by
  ext
  simp [image, eq_comm]
#align set.image_singleton Set.image_singleton

@[simp]
theorem Nonempty.image_const {s : Set α} (hs : s.Nonempty) (a : β) : (fun _ => a) '' s = {a} :=
  ext fun _ =>
    ⟨fun ⟨_, _, h⟩ => h ▸ mem_singleton _, fun h =>
      (eq_of_mem_singleton h).symm ▸ hs.imp fun _ hy => ⟨hy, rfl⟩⟩
#align set.nonempty.image_const Set.Nonempty.image_const

@[simp]
theorem image_eq_empty {α β} {f : α → β} {s : Set α} : f '' s = ∅ ↔ s = ∅ := by
  simp only [eq_empty_iff_forall_not_mem]
  exact ⟨fun H a ha => H _ ⟨_, ha, rfl⟩, fun H b ⟨_, ha, _⟩ => H _ ha⟩
#align set.image_eq_empty Set.image_eq_empty

-- Porting note: `compl` is already defined in `Init.Set`
theorem preimage_compl_eq_image_compl [BooleanAlgebra α] (S : Set α) :
  HasCompl.compl ⁻¹' S = HasCompl.compl '' S :=
  Set.ext fun x =>
    ⟨fun h => ⟨xᶜ, h, compl_compl x⟩, fun h =>
      Exists.elim h fun _ hy => (compl_eq_comm.mp hy.2).symm.subst hy.1⟩
#align set.preimage_compl_eq_image_compl Set.preimage_compl_eq_image_compl

theorem mem_compl_image [BooleanAlgebra α] (t : α) (S : Set α) :
  t ∈ HasCompl.compl '' S ↔ tᶜ ∈ S := by
  simp [← preimage_compl_eq_image_compl]
#align set.mem_compl_image Set.mem_compl_image

/-- A variant of `image_id` -/
@[simp]
theorem image_id' (s : Set α) : (fun x => x) '' s = s := by
  ext
  simp
#align set.image_id' Set.image_id'

theorem image_id (s : Set α) : id '' s = s := by simp
#align set.image_id Set.image_id

theorem compl_compl_image [BooleanAlgebra α] (S : Set α) :
  HasCompl.compl '' (HasCompl.compl '' S) = S := by
  rw [← image_comp, compl_comp_compl, image_id]
#align set.compl_compl_image Set.compl_compl_image

theorem image_insert_eq {f : α → β} {a : α} {s : Set α} :
    f '' insert a s = insert (f a) (f '' s) := by
  ext
  simp [and_or_left, exists_or, eq_comm, or_comm, and_comm]
#align set.image_insert_eq Set.image_insert_eq

theorem image_pair (f : α → β) (a b : α) : f '' {a, b} = {f a, f b} := by
  simp only [image_insert_eq, image_singleton]
#align set.image_pair Set.image_pair

theorem image_subset_preimage_of_inverse {f : α → β} {g : β → α} (I : LeftInverse g f) (s : Set α) :
    f '' s ⊆ g ⁻¹' s := fun _ ⟨a, h, e⟩ => e ▸ ((I a).symm ▸ h : g (f a) ∈ s)
#align set.image_subset_preimage_of_inverse Set.image_subset_preimage_of_inverse

theorem preimage_subset_image_of_inverse {f : α → β} {g : β → α} (I : LeftInverse g f) (s : Set β) :
    f ⁻¹' s ⊆ g '' s := fun b h => ⟨f b, h, I b⟩
#align set.preimage_subset_image_of_inverse Set.preimage_subset_image_of_inverse

theorem image_eq_preimage_of_inverse {f : α → β} {g : β → α} (h₁ : LeftInverse g f)
    (h₂ : RightInverse g f) : image f = preimage g :=
  funext fun s =>
    Subset.antisymm (image_subset_preimage_of_inverse h₁ s) (preimage_subset_image_of_inverse h₂ s)
#align set.image_eq_preimage_of_inverse Set.image_eq_preimage_of_inverse

theorem mem_image_iff_of_inverse {f : α → β} {g : β → α} {b : β} {s : Set α} (h₁ : LeftInverse g f)
    (h₂ : RightInverse g f) : b ∈ f '' s ↔ g b ∈ s := by
  rw [image_eq_preimage_of_inverse h₁ h₂]; rfl
#align set.mem_image_iff_of_inverse Set.mem_image_iff_of_inverse

theorem image_compl_subset {f : α → β} {s : Set α} (H : Injective f) : f '' sᶜ ⊆ (f '' s)ᶜ :=
  Disjoint.subset_compl_left <| by simp [disjoint_iff_inf_le, ←image_inter H]
#align set.image_compl_subset Set.image_compl_subset

theorem subset_image_compl {f : α → β} {s : Set α} (H : Surjective f) : (f '' s)ᶜ ⊆ f '' sᶜ :=
  compl_subset_iff_union.2 <| by
    rw [← image_union]
    simp [image_univ_of_surjective H]
#align set.subset_image_compl Set.subset_image_compl

theorem image_compl_eq {f : α → β} {s : Set α} (H : Bijective f) : f '' sᶜ = (f '' s)ᶜ :=
  Subset.antisymm (image_compl_subset H.1) (subset_image_compl H.2)
#align set.image_compl_eq Set.image_compl_eq

theorem subset_image_diff (f : α → β) (s t : Set α) : f '' s \ f '' t ⊆ f '' (s \ t) := by
  rw [diff_subset_iff, ← image_union, union_diff_self]
  exact image_subset f (subset_union_right t s)
#align set.subset_image_diff Set.subset_image_diff

theorem subset_image_symm_diff : (f '' s) ∆ (f '' t) ⊆ f '' s ∆ t :=
  (union_subset_union (subset_image_diff _ _ _) <| subset_image_diff _ _ _).trans
    (superset_of_eq (image_union _ _ _))
#align set.subset_image_symm_diff Set.subset_image_symm_diff

theorem image_diff {f : α → β} (hf : Injective f) (s t : Set α) : f '' (s \ t) = f '' s \ f '' t :=
  Subset.antisymm
    (Subset.trans (image_inter_subset _ _ _) <| inter_subset_inter_right _ <| image_compl_subset hf)
    (subset_image_diff f s t)
#align set.image_diff Set.image_diff

theorem image_symm_diff (hf : Injective f) (s t : Set α) : f '' s ∆ t = (f '' s) ∆ (f '' t) := by
  simp_rw [Set.symmDiff_def, image_union, image_diff hf]
#align set.image_symm_diff Set.image_symm_diff

theorem Nonempty.image (f : α → β) {s : Set α} : s.Nonempty → (f '' s).Nonempty
  | ⟨x, hx⟩ => ⟨f x, mem_image_of_mem f hx⟩
#align set.nonempty.image Set.Nonempty.image

theorem Nonempty.of_image {f : α → β} {s : Set α} : (f '' s).Nonempty → s.Nonempty
  | ⟨_, x, hx, _⟩ => ⟨x, hx⟩
#align set.nonempty.of_image Set.Nonempty.of_image

@[simp]
theorem nonempty_image_iff {f : α → β} {s : Set α} : (f '' s).Nonempty ↔ s.Nonempty :=
  ⟨Nonempty.of_image, fun h => h.image f⟩
#align set.nonempty_image_iff Set.nonempty_image_iff

theorem Nonempty.preimage {s : Set β} (hs : s.Nonempty) {f : α → β} (hf : Surjective f) :
    (f ⁻¹' s).Nonempty :=
  let ⟨y, hy⟩ := hs
  let ⟨x, hx⟩ := hf y
  ⟨x, mem_preimage.2 <| hx.symm ▸ hy⟩
#align set.nonempty.preimage Set.Nonempty.preimage

instance (f : α → β) (s : Set α) [Nonempty s] : Nonempty (f '' s) :=
  (Set.Nonempty.image f nonempty_of_nonempty_subtype).to_subtype

/-- image and preimage are a Galois connection -/
@[simp]
theorem image_subset_iff {s : Set α} {t : Set β} {f : α → β} : f '' s ⊆ t ↔ s ⊆ f ⁻¹' t :=
  ball_image_iff
#align set.image_subset_iff Set.image_subset_iff

theorem image_preimage_subset (f : α → β) (s : Set β) : f '' (f ⁻¹' s) ⊆ s :=
  image_subset_iff.2 Subset.rfl
#align set.image_preimage_subset Set.image_preimage_subset

theorem subset_preimage_image (f : α → β) (s : Set α) : s ⊆ f ⁻¹' (f '' s) := fun _ =>
  mem_image_of_mem f
#align set.subset_preimage_image Set.subset_preimage_image

theorem preimage_image_eq {f : α → β} (s : Set α) (h : Injective f) : f ⁻¹' (f '' s) = s :=
  Subset.antisymm (fun _ ⟨_, hy, e⟩ => h e ▸ hy) (subset_preimage_image f s)
#align set.preimage_image_eq Set.preimage_image_eq

theorem image_preimage_eq {f : α → β} (s : Set β) (h : Surjective f) : f '' (f ⁻¹' s) = s :=
  Subset.antisymm (image_preimage_subset f s) fun x hx =>
    let ⟨y, e⟩ := h x
    ⟨y, (e.symm ▸ hx : f y ∈ s), e⟩
#align set.image_preimage_eq Set.image_preimage_eq

theorem preimage_eq_preimage {f : β → α} (hf : Surjective f) : f ⁻¹' s = f ⁻¹' t ↔ s = t :=
  Iff.intro
    fun eq => by rw [← image_preimage_eq s hf, ← image_preimage_eq t hf, eq]
    fun eq => eq ▸ rfl
#align set.preimage_eq_preimage Set.preimage_eq_preimage

theorem image_inter_preimage (f : α → β) (s : Set α) (t : Set β) :
    f '' (s ∩ f ⁻¹' t) = f '' s ∩ t := by
  apply Subset.antisymm
  · calc
      f '' (s ∩ f ⁻¹' t) ⊆ f '' s ∩ f '' (f ⁻¹' t) := image_inter_subset _ _ _
      _ ⊆ f '' s ∩ t := inter_subset_inter_right _ (image_preimage_subset f t)

  · rintro _ ⟨⟨x, h', rfl⟩, h⟩
    exact ⟨x, ⟨h', h⟩, rfl⟩
#align set.image_inter_preimage Set.image_inter_preimage

theorem image_preimage_inter (f : α → β) (s : Set α) (t : Set β) :
    f '' (f ⁻¹' t ∩ s) = t ∩ f '' s := by simp only [inter_comm, image_inter_preimage]
#align set.image_preimage_inter Set.image_preimage_inter

@[simp]
theorem image_inter_nonempty_iff {f : α → β} {s : Set α} {t : Set β} :
    (f '' s ∩ t).Nonempty ↔ (s ∩ f ⁻¹' t).Nonempty := by
  rw [← image_inter_preimage, nonempty_image_iff]
#align set.image_inter_nonempty_iff Set.image_inter_nonempty_iff

theorem image_diff_preimage {f : α → β} {s : Set α} {t : Set β} : f '' (s \ f ⁻¹' t) = f '' s \ t :=
  by simp_rw [diff_eq, ← preimage_compl, image_inter_preimage]
#align set.image_diff_preimage Set.image_diff_preimage

theorem compl_image : image (compl : Set α → Set α) = preimage compl :=
  image_eq_preimage_of_inverse compl_compl compl_compl
#align set.compl_image Set.compl_image

theorem compl_image_set_of {p : Set α → Prop} : compl '' { s | p s } = { s | p (sᶜ) } :=
  congr_fun compl_image p
#align set.compl_image_set_of Set.compl_image_set_of

theorem inter_preimage_subset (s : Set α) (t : Set β) (f : α → β) :
    s ∩ f ⁻¹' t ⊆ f ⁻¹' (f '' s ∩ t) := fun _ h => ⟨mem_image_of_mem _ h.left, h.right⟩
#align set.inter_preimage_subset Set.inter_preimage_subset

theorem union_preimage_subset (s : Set α) (t : Set β) (f : α → β) :
    s ∪ f ⁻¹' t ⊆ f ⁻¹' (f '' s ∪ t) := fun _ h =>
  Or.elim h (fun l => Or.inl <| mem_image_of_mem _ l) fun r => Or.inr r
#align set.union_preimage_subset Set.union_preimage_subset

theorem subset_image_union (f : α → β) (s : Set α) (t : Set β) : f '' (s ∪ f ⁻¹' t) ⊆ f '' s ∪ t :=
  image_subset_iff.2 (union_preimage_subset _ _ _)
#align set.subset_image_union Set.subset_image_union

theorem preimage_subset_iff {A : Set α} {B : Set β} {f : α → β} :
    f ⁻¹' B ⊆ A ↔ ∀ a : α, f a ∈ B → a ∈ A :=
  Iff.rfl
#align set.preimage_subset_iff Set.preimage_subset_iff

theorem image_eq_image {f : α → β} (hf : Injective f) : f '' s = f '' t ↔ s = t :=
  Iff.symm <|
    (Iff.intro fun eq => eq ▸ rfl) fun eq => by
      rw [← preimage_image_eq s hf, ← preimage_image_eq t hf, eq]
#align set.image_eq_image Set.image_eq_image

theorem image_subset_image_iff {f : α → β} (hf : Injective f) : f '' s ⊆ f '' t ↔ s ⊆ t := by
  refine' Iff.symm <| (Iff.intro (image_subset f)) fun h => _
  rw [← preimage_image_eq s hf, ← preimage_image_eq t hf]
  exact preimage_mono h
#align set.image_subset_image_iff Set.image_subset_image_iff

theorem prod_quotient_preimage_eq_image [s : Setoid α] (g : Quotient s → β) {h : α → β}
    (Hh : h = g ∘ Quotient.mk'') (r : Set (β × β)) :
    { x : Quotient s × Quotient s | (g x.1, g x.2) ∈ r } =
      (fun a : α × α => (⟦a.1⟧, ⟦a.2⟧)) '' ((fun a : α × α => (h a.1, h a.2)) ⁻¹' r) :=
  Hh.symm ▸
    Set.ext fun ⟨a₁, a₂⟩ =>
      ⟨Quot.induction_on₂ a₁ a₂ fun a₁ a₂ h => ⟨(a₁, a₂), h, rfl⟩, fun ⟨⟨b₁, b₂⟩, h₁, h₂⟩ =>
        show (g a₁, g a₂) ∈ r from
          have h₃ : ⟦b₁⟧ = a₁ ∧ ⟦b₂⟧ = a₂ := Prod.ext_iff.1 h₂
          h₃.1 ▸ h₃.2 ▸ h₁⟩
#align set.prod_quotient_preimage_eq_image Set.prod_quotient_preimage_eq_image

theorem exists_image_iff (f : α → β) (x : Set α) (P : β → Prop) :
    (∃ a : f '' x, P a) ↔ ∃ a : x, P (f a) :=
  ⟨fun ⟨a, h⟩ => ⟨⟨_, a.prop.choose_spec.1⟩, a.prop.choose_spec.2.symm ▸ h⟩, fun ⟨a, h⟩ =>
    ⟨⟨_, _, a.prop, rfl⟩, h⟩⟩
#align set.exists_image_iff Set.exists_image_iff

/-- Restriction of `f` to `s` factors through `s.imageFactorization f : s → f '' s`. -/
def imageFactorization (f : α → β) (s : Set α) : s → f '' s := fun p =>
  ⟨f p.1, mem_image_of_mem f p.2⟩
#align set.image_factorization Set.imageFactorization

theorem imageFactorization_eq {f : α → β} {s : Set α} :
    Subtype.val ∘ imageFactorization f s = f ∘ Subtype.val :=
  funext fun _ => rfl
#align set.image_factorization_eq Set.imageFactorization_eq

theorem surjective_onto_image {f : α → β} {s : Set α} : Surjective (imageFactorization f s) :=
  fun ⟨_, ⟨a, ha, rfl⟩⟩ => ⟨⟨a, ha⟩, rfl⟩
#align set.surjective_onto_image Set.surjective_onto_image

/-- If the only elements outside `s` are those left fixed by `σ`, then mapping by `σ` has no effect.
-/
theorem image_perm {s : Set α} {σ : Equiv.Perm α} (hs : { a : α | σ a ≠ a } ⊆ s) : σ '' s = s := by
  ext i
  obtain hi | hi := eq_or_ne (σ i) i
  · refine' ⟨_, fun h => ⟨i, h, hi⟩⟩
    rintro ⟨j, hj, h⟩
    rwa [σ.injective (hi.trans h.symm)]
  · refine' iff_of_true ⟨σ.symm i, hs fun h => hi _, σ.apply_symm_apply _⟩ (hs hi)
    convert congr_arg σ h <;> exact (σ.apply_symm_apply _).symm
#align set.image_perm Set.image_perm

end Image

/-! ### Lemmas about the powerset and image. -/

/-- The powerset of `{a} ∪ s` is `𝒫 s` together with `{a} ∪ t` for each `t ∈ 𝒫 s`. -/
theorem powerset_insert (s : Set α) (a : α) : 𝒫 insert a s = 𝒫 s ∪ insert a '' 𝒫 s := by
  ext t
  simp_rw [mem_union, mem_image, mem_powerset_iff]
  constructor
  · intro h
    by_cases hs : a ∈ t
    · right
      refine' ⟨t \ {a}, _, _⟩
      · rw [diff_singleton_subset_iff]
        assumption
      · rw [insert_diff_singleton, insert_eq_of_mem hs]
    · left
      exact (subset_insert_iff_of_not_mem hs).mp h
  · rintro (h | ⟨s', h₁, rfl⟩)
    · exact subset_trans h (subset_insert a s)
    · exact insert_subset_insert h₁
#align set.powerset_insert Set.powerset_insert

/-! ### Lemmas about range of a function. -/

section Range

variable {f : ι → α} {s t : Set α}

/-- Range of a function.

This function is more flexible than `f '' univ`, as the image requires that the domain is in Type
and not an arbitrary Sort. -/
def range (f : ι → α) : Set α :=
  { x | ∃ y, f y = x }
#align set.range Set.range

@[simp]
theorem mem_range {x : α} : x ∈ range f ↔ ∃ y, f y = x :=
  Iff.rfl
#align set.mem_range Set.mem_range

-- Porting note
-- @[simp] `simp` can prove this
theorem mem_range_self (i : ι) : f i ∈ range f :=
  ⟨i, rfl⟩
#align set.mem_range_self Set.mem_range_self

theorem forall_range_iff {p : α → Prop} : (∀ a ∈ range f, p a) ↔ ∀ i, p (f i) := by simp
#align set.forall_range_iff Set.forall_range_iff

theorem forall_subtype_range_iff {p : range f → Prop} :
    (∀ a : range f, p a) ↔ ∀ i, p ⟨f i, mem_range_self _⟩ :=
  ⟨fun H i => H _, fun H ⟨y, i, hi⟩ => by
    subst hi
    apply H⟩
#align set.forall_subtype_range_iff Set.forall_subtype_range_iff

theorem exists_range_iff {p : α → Prop} : (∃ a ∈ range f, p a) ↔ ∃ i, p (f i) := by simp
#align set.exists_range_iff Set.exists_range_iff

theorem exists_range_iff' {p : α → Prop} : (∃ a, a ∈ range f ∧ p a) ↔ ∃ i, p (f i) := by
  simpa only [exists_prop] using exists_range_iff
#align set.exists_range_iff' Set.exists_range_iff'

theorem exists_subtype_range_iff {p : range f → Prop} :
    (∃ a : range f, p a) ↔ ∃ i, p ⟨f i, mem_range_self _⟩ :=
  ⟨fun ⟨⟨a, i, hi⟩, ha⟩ => by
    subst a
    exact ⟨i, ha⟩,
   fun ⟨i, hi⟩ => ⟨_, hi⟩⟩
#align set.exists_subtype_range_iff Set.exists_subtype_range_iff

theorem range_iff_surjective : range f = univ ↔ Surjective f :=
  eq_univ_iff_forall
#align set.range_iff_surjective Set.range_iff_surjective

-- Porting note: Lean4 unfolds `Surjective` here, ruining dot notation
alias range_iff_surjective ↔ _ _root_.Function.Surjective.range_eq
#align function.surjective.range_eq Function.Surjective.range_eq

@[simp]
theorem image_univ {f : α → β} : f '' univ = range f := by
  ext
  simp [image, range]
#align set.image_univ Set.image_univ

theorem image_subset_range (f : α → β) (s) : f '' s ⊆ range f := by
  rw [← image_univ]; exact image_subset _ (subset_univ _)
#align set.image_subset_range Set.image_subset_range

theorem mem_range_of_mem_image (f : α → β) (s) {x : β} (h : x ∈ f '' s) : x ∈ range f :=
  image_subset_range f s h
#align set.mem_range_of_mem_image Set.mem_range_of_mem_image

theorem _root_.Nat.mem_range_succ (i : ℕ) : i ∈ range Nat.succ ↔ 0 < i :=
  ⟨by
    rintro ⟨n, rfl⟩
    exact Nat.succ_pos n, fun h => ⟨_, Nat.succ_pred_eq_of_pos h⟩⟩
#align nat.mem_range_succ Nat.mem_range_succ

theorem Nonempty.preimage' {s : Set β} (hs : s.Nonempty) {f : α → β} (hf : s ⊆ Set.range f) :
    (f ⁻¹' s).Nonempty :=
  let ⟨_, hy⟩ := hs
  let ⟨x, hx⟩ := hf hy
  ⟨x, Set.mem_preimage.2 <| hx.symm ▸ hy⟩
#align set.nonempty.preimage' Set.Nonempty.preimage'

theorem range_comp (g : α → β) (f : ι → α) : range (g ∘ f) = g '' range f :=
  Subset.antisymm (forall_range_iff.mpr fun _ => mem_image_of_mem g (mem_range_self _))
    (ball_image_iff.mpr <| forall_range_iff.mpr mem_range_self)
#align set.range_comp Set.range_comp

theorem range_subset_iff : range f ⊆ s ↔ ∀ y, f y ∈ s :=
  forall_range_iff
#align set.range_subset_iff Set.range_subset_iff

theorem range_eq_iff (f : α → β) (s : Set β) :
    range f = s ↔ (∀ a, f a ∈ s) ∧ ∀ b ∈ s, ∃ a, f a = b := by
  rw [← range_subset_iff]
  exact le_antisymm_iff
#align set.range_eq_iff Set.range_eq_iff

theorem range_comp_subset_range (f : α → β) (g : β → γ) : range (g ∘ f) ⊆ range g := by
  rw [range_comp]; apply image_subset_range
#align set.range_comp_subset_range Set.range_comp_subset_range

theorem range_nonempty_iff_nonempty : (range f).Nonempty ↔ Nonempty ι :=
  ⟨fun ⟨_, x, _⟩ => ⟨x⟩, fun ⟨x⟩ => ⟨f x, mem_range_self x⟩⟩
#align set.range_nonempty_iff_nonempty Set.range_nonempty_iff_nonempty

theorem range_nonempty [h : Nonempty ι] (f : ι → α) : (range f).Nonempty :=
  range_nonempty_iff_nonempty.2 h
#align set.range_nonempty Set.range_nonempty

@[simp]
theorem range_eq_empty_iff {f : ι → α} : range f = ∅ ↔ IsEmpty ι := by
  rw [← not_nonempty_iff, ← range_nonempty_iff_nonempty, not_nonempty_iff_eq_empty]
#align set.range_eq_empty_iff Set.range_eq_empty_iff

theorem range_eq_empty [IsEmpty ι] (f : ι → α) : range f = ∅ :=
  range_eq_empty_iff.2 ‹_›
#align set.range_eq_empty Set.range_eq_empty

instance [Nonempty ι] (f : ι → α) : Nonempty (range f) :=
  (range_nonempty f).to_subtype

@[simp]
theorem image_union_image_compl_eq_range (f : α → β) : f '' s ∪ f '' sᶜ = range f := by
  rw [← image_union, ← image_univ, ← union_compl_self]
#align set.image_union_image_compl_eq_range Set.image_union_image_compl_eq_range

theorem insert_image_compl_eq_range (f : α → β) (x : α) : insert (f x) (f '' {x}ᶜ) = range f := by
  ext y; rw [mem_range, mem_insert_iff, mem_image]
  constructor
  · rintro (h | ⟨x', _, h⟩)
    · exact ⟨x, h.symm⟩
    · exact ⟨x', h⟩
  · rintro ⟨x', h⟩
    by_cases hx : x' = x
    · left
      rw [← h, hx]
    · right
      refine' ⟨_, _, h⟩
      rw [mem_compl_singleton_iff]
      exact hx
#align set.insert_image_compl_eq_range Set.insert_image_compl_eq_range

theorem image_preimage_eq_inter_range {f : α → β} {t : Set β} : f '' (f ⁻¹' t) = t ∩ range f :=
  ext fun x =>
    ⟨fun ⟨x, hx, HEq⟩ => HEq ▸ ⟨hx, mem_range_self _⟩, fun ⟨hx, ⟨y, h_eq⟩⟩ =>
      h_eq ▸ mem_image_of_mem f <| show y ∈ f ⁻¹' t by rw [preimage, mem_setOf, h_eq]; exact hx⟩
#align set.image_preimage_eq_inter_range Set.image_preimage_eq_inter_range

theorem image_preimage_eq_of_subset {f : α → β} {s : Set β} (hs : s ⊆ range f) :
    f '' (f ⁻¹' s) = s := by rw [image_preimage_eq_inter_range, inter_eq_self_of_subset_left hs]
#align set.image_preimage_eq_of_subset Set.image_preimage_eq_of_subset

theorem image_preimage_eq_iff {f : α → β} {s : Set β} : f '' (f ⁻¹' s) = s ↔ s ⊆ range f :=
  ⟨by
    intro h
    rw [← h]
    apply image_subset_range,
   image_preimage_eq_of_subset⟩
#align set.image_preimage_eq_iff Set.image_preimage_eq_iff

theorem subset_range_iff_exists_image_eq {f : α → β} {s : Set β} : s ⊆ range f ↔ ∃ t, f '' t = s :=
  ⟨fun h => ⟨_, image_preimage_eq_iff.2 h⟩, fun ⟨_, ht⟩ => ht ▸ image_subset_range _ _⟩
#align set.subset_range_iff_exists_image_eq Set.subset_range_iff_exists_image_eq

theorem range_image (f : α → β) : range (image f) = 𝒫 range f :=
  ext fun _ => subset_range_iff_exists_image_eq.symm
#align set.range_image Set.range_image

@[simp]
theorem exists_subset_range_and_iff {f : α → β} {p : Set β → Prop} :
    (∃ s, s ⊆ range f ∧ p s) ↔ ∃ s, p (f '' s) := by
  rw [← exists_range_iff, range_image]; rfl
#align set.exists_subset_range_and_iff Set.exists_subset_range_and_iff

theorem exists_subset_range_iff {f : α → β} {p : Set β → Prop} :
    (∃ (s : _) (_ : s ⊆ range f), p s) ↔ ∃ s, p (f '' s) := by simp
#align set.exists_subset_range_iff Set.exists_subset_range_iff

theorem forall_subset_range_iff {f : α → β} {p : Set β → Prop} :
    (∀ s, s ⊆ range f → p s) ↔ ∀ s, p (f '' s) := by
  rw [← forall_range_iff, range_image]; rfl

theorem preimage_subset_preimage_iff {s t : Set α} {f : β → α} (hs : s ⊆ range f) :
    f ⁻¹' s ⊆ f ⁻¹' t ↔ s ⊆ t := by
  constructor
  · intro h x hx
    rcases hs hx with ⟨y, rfl⟩
    exact h hx
  intro h x; apply h
#align set.preimage_subset_preimage_iff Set.preimage_subset_preimage_iff

theorem preimage_eq_preimage' {s t : Set α} {f : β → α} (hs : s ⊆ range f) (ht : t ⊆ range f) :
    f ⁻¹' s = f ⁻¹' t ↔ s = t := by
  constructor
  · intro h
    apply Subset.antisymm
    · rw [← preimage_subset_preimage_iff hs, h]
    · rw [← preimage_subset_preimage_iff ht, h]
  rintro rfl; rfl
#align set.preimage_eq_preimage' Set.preimage_eq_preimage'

-- Porting note:
-- @[simp] `simp` can prove this
theorem preimage_inter_range {f : α → β} {s : Set β} : f ⁻¹' (s ∩ range f) = f ⁻¹' s :=
  Set.ext fun x => and_iff_left ⟨x, rfl⟩
#align set.preimage_inter_range Set.preimage_inter_range

-- Porting note:
-- @[simp] `simp` can prove this
theorem preimage_range_inter {f : α → β} {s : Set β} : f ⁻¹' (range f ∩ s) = f ⁻¹' s := by
  rw [inter_comm, preimage_inter_range]
#align set.preimage_range_inter Set.preimage_range_inter

theorem preimage_image_preimage {f : α → β} {s : Set β} : f ⁻¹' (f '' (f ⁻¹' s)) = f ⁻¹' s := by
  rw [image_preimage_eq_inter_range, preimage_inter_range]
#align set.preimage_image_preimage Set.preimage_image_preimage

@[simp]
theorem range_id : range (@id α) = univ :=
  range_iff_surjective.2 surjective_id
#align set.range_id Set.range_id

@[simp]
theorem range_id' : (range fun x : α => x) = univ :=
  range_id
#align set.range_id' Set.range_id'

@[simp]
theorem _root_.Prod.range_fst [Nonempty β] : range (Prod.fst : α × β → α) = univ :=
  Function.Surjective.range_eq Prod.fst_surjective
  -- Porting note: should be `Prod.fst_surjective.range_eq` if dot notation works
#align prod.range_fst Prod.range_fst

@[simp]
theorem _root_.Prod.range_snd [Nonempty α] : range (Prod.snd : α × β → β) = univ :=
  Function.Surjective.range_eq Prod.snd_surjective
  -- Porting note: should be `Prod.snd_surjective.range_eq` if dot notation works
#align prod.range_snd Prod.range_snd

@[simp]
theorem range_eval {ι : Type _} {α : ι → Sort _} [∀ i, Nonempty (α i)] (i : ι) :
    range (eval i : (∀ i, α i) → α i) = univ :=
  Function.Surjective.range_eq (surjective_eval i)
  -- Porting note: should be `(surjective_eval i).range_eq` if dot notation works
#align set.range_eval Set.range_eval

theorem range_inl : range (@Sum.inl α β) = {x | Sum.isLeft x} := by ext (_|_) <;> simp
#align set.range_inl Set.range_inl
theorem range_inr : range (@Sum.inr α β) = {x | Sum.isRight x} := by ext (_|_) <;> simp
#align set.range_inr Set.range_inr

theorem isCompl_range_inl_range_inr : IsCompl (range <| @Sum.inl α β) (range Sum.inr) :=
  IsCompl.of_le
    (by
      rintro y ⟨⟨x₁, rfl⟩, ⟨x₂, h⟩⟩
      exact Sum.noConfusion h)
    (by rintro (x | y) - <;> [left, right] <;> exact mem_range_self _)
#align set.is_compl_range_inl_range_inr Set.isCompl_range_inl_range_inr

@[simp]
theorem range_inl_union_range_inr : range (Sum.inl : α → Sum α β) ∪ range Sum.inr = univ :=
  isCompl_range_inl_range_inr.sup_eq_top
#align set.range_inl_union_range_inr Set.range_inl_union_range_inr

@[simp]
theorem range_inl_inter_range_inr : range (Sum.inl : α → Sum α β) ∩ range Sum.inr = ∅ :=
  isCompl_range_inl_range_inr.inf_eq_bot
#align set.range_inl_inter_range_inr Set.range_inl_inter_range_inr

@[simp]
theorem range_inr_union_range_inl : range (Sum.inr : β → Sum α β) ∪ range Sum.inl = univ :=
  isCompl_range_inl_range_inr.symm.sup_eq_top
#align set.range_inr_union_range_inl Set.range_inr_union_range_inl

@[simp]
theorem range_inr_inter_range_inl : range (Sum.inr : β → Sum α β) ∩ range Sum.inl = ∅ :=
  isCompl_range_inl_range_inr.symm.inf_eq_bot
#align set.range_inr_inter_range_inl Set.range_inr_inter_range_inl

@[simp]
theorem preimage_inl_image_inr (s : Set β) : Sum.inl ⁻¹' (@Sum.inr α β '' s) = ∅ := by
  ext
  simp
#align set.preimage_inl_image_inr Set.preimage_inl_image_inr

@[simp]
theorem preimage_inr_image_inl (s : Set α) : Sum.inr ⁻¹' (@Sum.inl α β '' s) = ∅ := by
  ext
  simp
#align set.preimage_inr_image_inl Set.preimage_inr_image_inl

@[simp]
theorem preimage_inl_range_inr : Sum.inl ⁻¹' range (Sum.inr : β → Sum α β) = ∅ := by
  rw [← image_univ, preimage_inl_image_inr]
#align set.preimage_inl_range_inr Set.preimage_inl_range_inr

@[simp]
theorem preimage_inr_range_inl : Sum.inr ⁻¹' range (Sum.inl : α → Sum α β) = ∅ := by
  rw [← image_univ, preimage_inr_image_inl]
#align set.preimage_inr_range_inl Set.preimage_inr_range_inl

@[simp]
theorem compl_range_inl : range (Sum.inl : α → Sum α β)ᶜ = range (Sum.inr : β → Sum α β) :=
  IsCompl.compl_eq isCompl_range_inl_range_inr
#align set.compl_range_inl Set.compl_range_inl

@[simp]
theorem compl_range_inr : range (Sum.inr : β → Sum α β)ᶜ = range (Sum.inl : α → Sum α β) :=
  IsCompl.compl_eq isCompl_range_inl_range_inr.symm
#align set.compl_range_inr Set.compl_range_inr

theorem image_preimage_inl_union_image_preimage_inr (s : Set (Sum α β)) :
    Sum.inl '' (Sum.inl ⁻¹' s) ∪ Sum.inr '' (Sum.inr ⁻¹' s) = s := by
  rw [image_preimage_eq_inter_range, image_preimage_eq_inter_range, ← inter_distrib_left,
    range_inl_union_range_inr, inter_univ]
#align set.image_preimage_inl_union_image_preimage_inr Set.image_preimage_inl_union_image_preimage_inr

@[simp]
theorem range_quot_mk (r : α → α → Prop) : range (Quot.mk r) = univ :=
  (surjective_quot_mk r).range_eq
#align set.range_quot_mk Set.range_quot_mk

@[simp]
theorem range_quot_lift {r : ι → ι → Prop} (hf : ∀ x y, r x y → f x = f y) :
    range (Quot.lift f hf) = range f :=
  ext fun _ => (surjective_quot_mk _).exists
#align set.range_quot_lift Set.range_quot_lift

-- Porting note: the `Setoid α` instance is not being filled in
@[simp]
theorem range_quotient_mk [sa : Setoid α] : (range (α := Quotient sa) fun x : α => ⟦x⟧) = univ :=
  range_quot_mk _
#align set.range_quotient_mk Set.range_quotient_mk

@[simp]
theorem range_quotient_lift [s : Setoid ι] (hf) :
    range (Quotient.lift f hf : Quotient s → α) = range f :=
  range_quot_lift _
#align set.range_quotient_lift Set.range_quotient_lift

@[simp]
theorem range_quotient_mk' {s : Setoid α} : range (Quotient.mk' : α → Quotient s) = univ :=
  range_quot_mk _
#align set.range_quotient_mk' Set.range_quotient_mk'

@[simp]
theorem range_quotient_lift_on' {s : Setoid ι} (hf) :
    (range fun x : Quotient s => Quotient.liftOn' x f hf) = range f :=
  range_quot_lift _
#align set.range_quotient_lift_on' Set.range_quotient_lift_on'

instance canLift (c) (p) [CanLift α β c p] :
    CanLift (Set α) (Set β) ((· '' ·) c) fun s => ∀ x ∈ s, p x where
  prf _ hs := subset_range_iff_exists_image_eq.mp fun x hx => CanLift.prf _ (hs x hx)
#align set.can_lift Set.canLift

theorem range_const_subset {c : α} : (range fun _ : ι => c) ⊆ {c} :=
  range_subset_iff.2 fun _ => rfl
#align set.range_const_subset Set.range_const_subset

@[simp]
theorem range_const : ∀ [Nonempty ι] {c : α}, (range fun _ : ι => c) = {c}
  | ⟨x⟩, _ =>
    (Subset.antisymm range_const_subset) fun _ hy =>
      (mem_singleton_iff.1 hy).symm ▸ mem_range_self x
#align set.range_const Set.range_const

theorem range_subtype_map {p : α → Prop} {q : β → Prop} (f : α → β) (h : ∀ x, p x → q (f x)) :
    range (Subtype.map f h) = (↑) ⁻¹' (f '' { x | p x }) := by
  ext ⟨x, hx⟩
  rw [mem_preimage, mem_range, mem_image, Subtype.exists, Subtype.coe_mk]
  apply Iff.intro
  . rintro ⟨a, b, hab⟩
    rw [Subtype.map, Subtype.mk.injEq] at hab
    use a
    trivial
  . rintro ⟨a, b, hab⟩
    use a
    use b
    rw [Subtype.map, Subtype.mk.injEq]
    exact hab
  -- Porting note: `simp_rw` fails here
  -- simp_rw [mem_preimage, mem_range, mem_image, Subtype.exists, Subtype.map, Subtype.coe_mk,
  --   mem_set_of, exists_prop]
#align set.range_subtype_map Set.range_subtype_map

theorem image_swap_eq_preimage_swap : image (@Prod.swap α β) = preimage Prod.swap :=
  image_eq_preimage_of_inverse Prod.swap_leftInverse Prod.swap_rightInverse
#align set.image_swap_eq_preimage_swap Set.image_swap_eq_preimage_swap

theorem preimage_singleton_nonempty {f : α → β} {y : β} : (f ⁻¹' {y}).Nonempty ↔ y ∈ range f :=
  Iff.rfl
#align set.preimage_singleton_nonempty Set.preimage_singleton_nonempty

theorem preimage_singleton_eq_empty {f : α → β} {y : β} : f ⁻¹' {y} = ∅ ↔ y ∉ range f :=
  not_nonempty_iff_eq_empty.symm.trans preimage_singleton_nonempty.not
#align set.preimage_singleton_eq_empty Set.preimage_singleton_eq_empty

theorem range_subset_singleton {f : ι → α} {x : α} : range f ⊆ {x} ↔ f = const ι x := by
  simp [range_subset_iff, funext_iff, mem_singleton]
#align set.range_subset_singleton Set.range_subset_singleton

theorem image_compl_preimage {f : α → β} {s : Set β} : f '' (f ⁻¹' s)ᶜ = range f \ s := by
  rw [compl_eq_univ_diff, image_diff_preimage, image_univ]
#align set.image_compl_preimage Set.image_compl_preimage

/-- Any map `f : ι → β` factors through a map `rangeFactorization f : ι → range f`. -/
def rangeFactorization (f : ι → β) : ι → range f := fun i => ⟨f i, mem_range_self i⟩
#align set.range_factorization Set.rangeFactorization

theorem rangeFactorization_eq {f : ι → β} : Subtype.val ∘ rangeFactorization f = f :=
  funext fun _ => rfl
#align set.range_factorization_eq Set.rangeFactorization_eq

@[simp]
theorem rangeFactorization_coe (f : ι → β) (a : ι) : (rangeFactorization f a : β) = f a :=
  rfl
#align set.range_factorization_coe Set.rangeFactorization_coe

@[simp]
theorem coe_comp_rangeFactorization (f : ι → β) :
  (↑) ∘ rangeFactorization f = f := rfl
#align set.coe_comp_range_factorization Set.coe_comp_rangeFactorization

theorem surjective_onto_range : Surjective (rangeFactorization f) := fun ⟨_, ⟨i, rfl⟩⟩ => ⟨i, rfl⟩
#align set.surjective_onto_range Set.surjective_onto_range

theorem image_eq_range (f : α → β) (s : Set α) : f '' s = range fun x : s => f x := by
  ext
  constructor
  rintro ⟨x, h1, h2⟩
  exact ⟨⟨x, h1⟩, h2⟩
  rintro ⟨⟨x, h1⟩, h2⟩
  exact ⟨x, h1, h2⟩
#align set.image_eq_range Set.image_eq_range

theorem _root_.Sum.range_eq (f : Sum α β → γ) :
  range f = range (f ∘ Sum.inl) ∪ range (f ∘ Sum.inr) :=
  ext fun _ => Sum.exists
#align sum.range_eq Sum.range_eq

@[simp]
theorem Sum.elim_range (f : α → γ) (g : β → γ) : range (Sum.elim f g) = range f ∪ range g :=
  Sum.range_eq _
#align set.sum.elim_range Set.Sum.elim_range

theorem range_ite_subset' {p : Prop} [Decidable p] {f g : α → β} :
    range (if p then f else g) ⊆ range f ∪ range g := by
  by_cases h : p;
  · rw [if_pos h]
    exact subset_union_left _ _
  · rw [if_neg h]
    exact subset_union_right _ _
#align set.range_ite_subset' Set.range_ite_subset'

theorem range_ite_subset {p : α → Prop} [DecidablePred p] {f g : α → β} :
    (range fun x => if p x then f x else g x) ⊆ range f ∪ range g := by
  rw [range_subset_iff]; intro x; by_cases h : p x
  simp [if_pos h, mem_union, mem_range_self]
  simp [if_neg h, mem_union, mem_range_self]
#align set.range_ite_subset Set.range_ite_subset

@[simp]
theorem preimage_range (f : α → β) : f ⁻¹' range f = univ :=
  eq_univ_of_forall mem_range_self
#align set.preimage_range Set.preimage_range

/-- The range of a function from a `unique` type contains just the
function applied to its single value. -/
theorem range_unique [h : Unique ι] : range f = {f default} := by
  ext x
  rw [mem_range]
  constructor
  · rintro ⟨i, hi⟩
    rw [h.uniq i] at hi
    exact hi ▸ mem_singleton _
  · exact fun h => ⟨default, h.symm⟩
#align set.range_unique Set.range_unique

theorem range_diff_image_subset (f : α → β) (s : Set α) : range f \ f '' s ⊆ f '' sᶜ :=
  fun _ ⟨⟨x, h₁⟩, h₂⟩ => ⟨x, fun h => h₂ ⟨x, h, h₁⟩, h₁⟩
#align set.range_diff_image_subset Set.range_diff_image_subset

theorem range_diff_image {f : α → β} (H : Injective f) (s : Set α) : range f \ f '' s = f '' sᶜ :=
  (Subset.antisymm (range_diff_image_subset f s)) fun _ ⟨_, hx, hy⟩ =>
    hy ▸ ⟨mem_range_self _, fun ⟨_, hx', Eq⟩ => hx <| H Eq ▸ hx'⟩
#align set.range_diff_image Set.range_diff_image

@[simp]
theorem range_inclusion (h : s ⊆ t) : range (inclusion h) = { x : t | (x : α) ∈ s } := by
  ext ⟨x, hx⟩
  -- Porting note: `simp [inclusion]` doesn't solve goal
  apply Iff.intro
  . rw [mem_range]
    rintro ⟨a, ha⟩
    rw [inclusion, Subtype.mk.injEq] at ha
    rw [mem_setOf, Subtype.coe_mk, ← ha]
    exact Subtype.coe_prop _
  . rw [mem_setOf, Subtype.coe_mk, mem_range]
    intro hx'
    use ⟨x, hx'⟩
    trivial
  -- simp_rw [inclusion, mem_range, Subtype.mk_eq_mk]
  -- rw [SetCoe.exists, Subtype.coe_mk, exists_prop, exists_eq_right, mem_set_of, Subtype.coe_mk]
#align set.range_inclusion Set.range_inclusion

/-- We can use the axiom of choice to pick a preimage for every element of `range f`. -/
noncomputable def rangeSplitting (f : α → β) : range f → α := fun x => x.2.choose
#align set.range_splitting Set.rangeSplitting

-- This can not be a `@[simp]` lemma because the head of the left hand side is a variable.
theorem apply_rangeSplitting (f : α → β) (x : range f) : f (rangeSplitting f x) = x :=
  x.2.choose_spec
#align set.apply_range_splitting Set.apply_rangeSplitting

@[simp]
theorem comp_rangeSplitting (f : α → β) :
  f ∘ rangeSplitting f = (↑) := by
  ext
  simp only [Function.comp_apply]
  apply apply_rangeSplitting
#align set.comp_range_splitting Set.comp_rangeSplitting

-- When `f` is injective, see also `Equiv.ofInjective`.
theorem leftInverse_rangeSplitting (f : α → β) :
    LeftInverse (rangeFactorization f) (rangeSplitting f) := fun x => by
  apply Subtype.ext -- Porting note: why doesn't `ext` find this lemma?
  simp only [rangeFactorization_coe]
  apply apply_rangeSplitting
#align set.left_inverse_range_splitting Set.leftInverse_rangeSplitting

theorem rangeSplitting_injective (f : α → β) : Injective (rangeSplitting f) :=
  (leftInverse_rangeSplitting f).injective
#align set.range_splitting_injective Set.rangeSplitting_injective

theorem rightInverse_rangeSplitting {f : α → β} (h : Injective f) :
    RightInverse (rangeFactorization f) (rangeSplitting f) :=
  (leftInverse_rangeSplitting f).rightInverse_of_injective fun _ _ hxy =>
    h <| Subtype.ext_iff.1 hxy
#align set.right_inverse_range_splitting Set.rightInverse_rangeSplitting

theorem preimage_rangeSplitting {f : α → β} (hf : Injective f) :
    preimage (rangeSplitting f) = image (rangeFactorization f) :=
  (image_eq_preimage_of_inverse (rightInverse_rangeSplitting hf)
      (leftInverse_rangeSplitting f)).symm
#align set.preimage_range_splitting Set.preimage_rangeSplitting

theorem isCompl_range_some_none (α : Type _) : IsCompl (range (some : α → Option α)) {none} :=
  IsCompl.of_le (fun _ ⟨⟨_, ha⟩, (hn : _ = none)⟩ => Option.some_ne_none _ (ha.trans hn))
    fun x _ => Option.casesOn x (Or.inr rfl) fun _ => Or.inl <| mem_range_self _
#align set.is_compl_range_some_none Set.isCompl_range_some_none

@[simp]
theorem compl_range_some (α : Type _) : range (some : α → Option α)ᶜ = {none} :=
  (isCompl_range_some_none α).compl_eq
#align set.compl_range_some Set.compl_range_some

@[simp]
theorem range_some_inter_none (α : Type _) : range (some : α → Option α) ∩ {none} = ∅ :=
  (isCompl_range_some_none α).inf_eq_bot
#align set.range_some_inter_none Set.range_some_inter_none

-- Porting note:
-- @[simp] `simp` can prove this
theorem range_some_union_none (α : Type _) : range (some : α → Option α) ∪ {none} = univ :=
  (isCompl_range_some_none α).sup_eq_top
#align set.range_some_union_none Set.range_some_union_none

@[simp]
theorem insert_none_range_some (α : Type _) : insert none (range (some : α → Option α)) = univ :=
  (isCompl_range_some_none α).symm.sup_eq_top
#align set.insert_none_range_some Set.insert_none_range_some

end Range

section Subsingleton

variable {s : Set α}

/-- The image of a subsingleton is a subsingleton. -/
theorem Subsingleton.image (hs : s.Subsingleton) (f : α → β) : (f '' s).Subsingleton :=
  fun _ ⟨_, hx, Hx⟩ _ ⟨_, hy, Hy⟩ => Hx ▸ Hy ▸ congr_arg f (hs hx hy)
#align set.subsingleton.image Set.Subsingleton.image

/-- The preimage of a subsingleton under an injective map is a subsingleton. -/
theorem Subsingleton.preimage {s : Set β} (hs : s.Subsingleton) {f : α → β}
    (hf : Function.Injective f) : (f ⁻¹' s).Subsingleton := fun _ ha _ hb => hf <| hs ha hb
#align set.subsingleton.preimage Set.Subsingleton.preimage

/-- If the image of a set under an injective map is a subsingleton, the set is a subsingleton. -/
theorem subsingleton_of_image {α β : Type _} {f : α → β} (hf : Function.Injective f) (s : Set α)
    (hs : (f '' s).Subsingleton) : s.Subsingleton :=
  (hs.preimage hf).anti <| subset_preimage_image _ _
#align set.subsingleton_of_image Set.subsingleton_of_image

/-- If the preimage of a set under an surjective map is a subsingleton,
the set is a subsingleton. -/
theorem subsingleton_of_preimage {α β : Type _} {f : α → β} (hf : Function.Surjective f) (s : Set β)
    (hs : (f ⁻¹' s).Subsingleton) : s.Subsingleton := fun fx hx fy hy => by
  rcases hf fx, hf fy with ⟨⟨x, rfl⟩, ⟨y, rfl⟩⟩
  exact congr_arg f (hs hx hy)
#align set.subsingleton_of_preimage Set.subsingleton_of_preimage

theorem subsingleton_range {α : Sort _} [Subsingleton α] (f : α → β) : (range f).Subsingleton :=
  forall_range_iff.2 fun x => forall_range_iff.2 fun y => congr_arg f (Subsingleton.elim x y)
#align set.subsingleton_range Set.subsingleton_range

/-- The preimage of a nontrivial set under a surjective map is nontrivial. -/
theorem Nontrivial.preimage {s : Set β} (hs : s.Nontrivial) {f : α → β}
    (hf : Function.Surjective f) : (f ⁻¹' s).Nontrivial := by
  rcases hs with ⟨fx, hx, fy, hy, hxy⟩
  rcases hf fx, hf fy with ⟨⟨x, rfl⟩, ⟨y, rfl⟩⟩
  exact ⟨x, hx, y, hy, mt (congr_arg f) hxy⟩
#align set.nontrivial.preimage Set.Nontrivial.preimage

/-- The image of a nontrivial set under an injective map is nontrivial. -/
theorem Nontrivial.image (hs : s.Nontrivial) {f : α → β} (hf : Function.Injective f) :
    (f '' s).Nontrivial :=
  let ⟨x, hx, y, hy, hxy⟩ := hs
  ⟨f x, mem_image_of_mem f hx, f y, mem_image_of_mem f hy, hf.ne hxy⟩
#align set.nontrivial.image Set.Nontrivial.image

/-- If the image of a set is nontrivial, the set is nontrivial. -/
theorem nontrivial_of_image (f : α → β) (s : Set α) (hs : (f '' s).Nontrivial) : s.Nontrivial :=
  let ⟨_, ⟨x, hx, rfl⟩, _, ⟨y, hy, rfl⟩, hxy⟩ := hs
  ⟨x, hx, y, hy, mt (congr_arg f) hxy⟩
#align set.nontrivial_of_image Set.nontrivial_of_image

/-- If the preimage of a set under an injective map is nontrivial, the set is nontrivial. -/
theorem nontrivial_of_preimage {f : α → β} (hf : Function.Injective f) (s : Set β)
    (hs : (f ⁻¹' s).Nontrivial) : s.Nontrivial :=
  (hs.image hf).mono <| image_preimage_subset _ _
#align set.nontrivial_of_preimage Set.nontrivial_of_preimage

end Subsingleton

end Set

namespace Function

variable {ι : Sort _} {α : Type _} {β : Type _} {f : α → β}

open Set

theorem Surjective.preimage_injective (hf : Surjective f) : Injective (preimage f) := fun _ _ =>
  (preimage_eq_preimage hf).1
#align function.surjective.preimage_injective Function.Surjective.preimage_injective

theorem Injective.preimage_image (hf : Injective f) (s : Set α) : f ⁻¹' (f '' s) = s :=
  preimage_image_eq s hf
#align function.injective.preimage_image Function.Injective.preimage_image

theorem Injective.preimage_surjective (hf : Injective f) : Surjective (preimage f) := by
  intro s
  use f '' s
  rw [hf.preimage_image]
#align function.injective.preimage_surjective Function.Injective.preimage_surjective

theorem Injective.subsingleton_image_iff (hf : Injective f) {s : Set α} :
    (f '' s).Subsingleton ↔ s.Subsingleton :=
  ⟨subsingleton_of_image hf s, fun h => h.image f⟩
#align function.injective.subsingleton_image_iff Function.Injective.subsingleton_image_iff

theorem Surjective.image_preimage (hf : Surjective f) (s : Set β) : f '' (f ⁻¹' s) = s :=
  image_preimage_eq s hf
#align function.surjective.image_preimage Function.Surjective.image_preimage

theorem Surjective.image_surjective (hf : Surjective f) : Surjective (image f) := by
  intro s
  use f ⁻¹' s
  rw [hf.image_preimage]
#align function.surjective.image_surjective Function.Surjective.image_surjective

theorem Surjective.nonempty_preimage (hf : Surjective f) {s : Set β} :
    (f ⁻¹' s).Nonempty ↔ s.Nonempty := by rw [← nonempty_image_iff, hf.image_preimage]
#align function.surjective.nonempty_preimage Function.Surjective.nonempty_preimage

theorem Injective.image_injective (hf : Injective f) : Injective (image f) := by
  intro s t h
  rw [← preimage_image_eq s hf, ← preimage_image_eq t hf, h]
#align function.injective.image_injective Function.Injective.image_injective

theorem Surjective.preimage_subset_preimage_iff {s t : Set β} (hf : Surjective f) :
    f ⁻¹' s ⊆ f ⁻¹' t ↔ s ⊆ t := by
  apply Set.preimage_subset_preimage_iff
  rw [Function.Surjective.range_eq hf]
  apply subset_univ
#align function.surjective.preimage_subset_preimage_iff Function.Surjective.preimage_subset_preimage_iff

theorem Surjective.range_comp {f : ι → ι'} (hf : Surjective f) (g : ι' → α) :
    range (g ∘ f) = range g :=
  ext fun y => (@Surjective.exists _ _ _ hf fun x => g x = y).symm
#align function.surjective.range_comp Function.Surjective.range_comp

theorem Injective.mem_range_iff_exists_unique (hf : Injective f) {b : β} :
    b ∈ range f ↔ ∃! a, f a = b :=
  ⟨fun ⟨a, h⟩ => ⟨a, h, fun _ ha => hf (ha.trans h.symm)⟩, ExistsUnique.exists⟩
#align function.injective.mem_range_iff_exists_unique Function.Injective.mem_range_iff_exists_unique

theorem Injective.exists_unique_of_mem_range (hf : Injective f) {b : β} (hb : b ∈ range f) :
    ∃! a, f a = b :=
  hf.mem_range_iff_exists_unique.mp hb
#align function.injective.exists_unique_of_mem_range Function.Injective.exists_unique_of_mem_range

theorem Injective.compl_image_eq (hf : Injective f) (s : Set α) :
    (f '' s)ᶜ = f '' sᶜ ∪ range fᶜ := by
  ext y
  rcases em (y ∈ range f) with (⟨x, rfl⟩ | hx)
  · simp [hf.eq_iff]
  · rw [mem_range, not_exists] at hx
    simp [hx]
#align function.injective.compl_image_eq Function.Injective.compl_image_eq

theorem LeftInverse.image_image {g : β → α} (h : LeftInverse g f) (s : Set α) : g '' (f '' s) = s :=
  by rw [← image_comp, h.comp_eq_id, image_id]
#align function.left_inverse.image_image Function.LeftInverse.image_image

theorem LeftInverse.preimage_preimage {g : β → α} (h : LeftInverse g f) (s : Set α) :
    f ⁻¹' (g ⁻¹' s) = s := by rw [← preimage_comp, h.comp_eq_id, preimage_id]
#align function.left_inverse.preimage_preimage Function.LeftInverse.preimage_preimage

end Function

namespace EquivLike
variable {E : Type _} [EquivLike E ι ι']

@[simp] lemma range_comp (f : ι' → α) (e : E) : range (f ∘ e) = range f :=
(EquivLike.surjective _).range_comp _
#align equiv_like.range_comp EquivLike.range_comp

end EquivLike

/-! ### Image and preimage on subtypes -/

namespace Subtype

variable {α : Type _}

theorem coe_image {p : α → Prop} {s : Set (Subtype p)} :
    (↑) '' s = { x | ∃ h : p x, (⟨x, h⟩ : Subtype p) ∈ s } :=
  Set.ext fun a =>
    ⟨fun ⟨⟨_, ha'⟩, in_s, h_eq⟩ => h_eq ▸ ⟨ha', in_s⟩, fun ⟨ha, in_s⟩ => ⟨⟨a, ha⟩, in_s, rfl⟩⟩
#align subtype.coe_image Subtype.coe_image

@[simp]
theorem coe_image_of_subset {s t : Set α} (h : t ⊆ s) :
  (↑) '' { x : ↥s | ↑x ∈ t } = t := by
  ext x
  rw [Set.mem_image]
  exact ⟨fun ⟨_, hx', hx⟩ => hx ▸ hx', fun hx => ⟨⟨x, h hx⟩, hx, rfl⟩⟩
#align subtype.coe_image_of_subset Subtype.coe_image_of_subset

theorem range_coe {s : Set α} : range ((↑) : s → α) = s := by
  rw [← Set.image_univ]
  simp [-Set.image_univ, coe_image]
#align subtype.range_coe Subtype.range_coe

/-- A variant of `range_coe`. Try to use `range_coe` if possible.
  This version is useful when defining a new type that is defined as the subtype of something.
  In that case, the coercion doesn't fire anymore. -/
theorem range_val {s : Set α} : range (Subtype.val : s → α) = s :=
  range_coe
#align subtype.range_val Subtype.range_val

/-- We make this the simp lemma instead of `range_coe`. The reason is that if we write
  for `s : Set α` the function `(↑) : s → α`, then the inferred implicit arguments of `(↑)` are
  `↑α (λ x, x ∈ s)`. -/
@[simp]
theorem range_coe_subtype {p : α → Prop} : range ((↑) : Subtype p → α) = { x | p x } :=
  range_coe
#align subtype.range_coe_subtype Subtype.range_coe_subtype

@[simp]
theorem coe_preimage_self (s : Set α) : ((↑) : s → α) ⁻¹' s = univ := by
  rw [← preimage_range, range_coe]
#align subtype.coe_preimage_self Subtype.coe_preimage_self

theorem range_val_subtype {p : α → Prop} : range (Subtype.val : Subtype p → α) = { x | p x } :=
  range_coe
#align subtype.range_val_subtype Subtype.range_val_subtype

theorem coe_image_subset (s : Set α) (t : Set s) : ((↑) : s → α) '' t ⊆ s :=
  fun x ⟨y, _, yvaleq⟩ => by
  rw [← yvaleq]; exact y.property
#align subtype.coe_image_subset Subtype.coe_image_subset

theorem coe_image_univ (s : Set α) : ((↑) : s → α) '' Set.univ = s :=
  image_univ.trans range_coe
#align subtype.coe_image_univ Subtype.coe_image_univ

@[simp]
theorem image_preimage_coe (s t : Set α) :
  ((↑) : s → α) '' (((↑) : s → α) ⁻¹' t) = t ∩ s :=
  image_preimage_eq_inter_range.trans <| congr_arg _ range_coe
#align subtype.image_preimage_coe Subtype.image_preimage_coe

theorem image_preimage_val (s t : Set α) : (Subtype.val : s → α) '' (Subtype.val ⁻¹' t) = t ∩ s :=
  image_preimage_coe s t
#align subtype.image_preimage_val Subtype.image_preimage_val

theorem preimage_coe_eq_preimage_coe_iff {s t u : Set α} :
    ((↑) : s → α) ⁻¹' t = ((↑) : s → α) ⁻¹' u ↔ t ∩ s = u ∩ s := by
  rw [← image_preimage_coe, ← image_preimage_coe, coe_injective.image_injective.eq_iff]
#align subtype.preimage_coe_eq_preimage_coe_iff Subtype.preimage_coe_eq_preimage_coe_iff

-- Porting note:
-- @[simp] `simp` can prove this
theorem preimage_coe_inter_self (s t : Set α) :
  ((↑) : s → α) ⁻¹' (t ∩ s) = ((↑) : s → α) ⁻¹' t := by
  rw [preimage_coe_eq_preimage_coe_iff, inter_assoc, inter_self]
#align subtype.preimage_coe_inter_self Subtype.preimage_coe_inter_self

theorem preimage_val_eq_preimage_val_iff (s t u : Set α) :
    (Subtype.val : s → α) ⁻¹' t = Subtype.val ⁻¹' u ↔ t ∩ s = u ∩ s :=
  preimage_coe_eq_preimage_coe_iff
#align subtype.preimage_val_eq_preimage_val_iff Subtype.preimage_val_eq_preimage_val_iff

theorem exists_set_subtype {t : Set α} (p : Set α → Prop) :
    (∃ s : Set t, p (((↑) : t → α) '' s)) ↔ ∃ s : Set α, s ⊆ t ∧ p s := by
  rw [← exists_subset_range_and_iff, range_coe]
#align subtype.exists_set_subtype Subtype.exists_set_subtype

theorem forall_set_subtype {t : Set α} (p : Set α → Prop) :
    (∀ s : Set t, p (((↑) : t → α) '' s)) ↔ ∀ s : Set α, s ⊆ t → p s := by
  rw [← forall_subset_range_iff, range_coe]

theorem preimage_coe_nonempty {s t : Set α} :
  (((↑) : s → α) ⁻¹' t).Nonempty ↔ (s ∩ t).Nonempty :=
  by rw [inter_comm, ← image_preimage_coe, nonempty_image_iff]
#align subtype.preimage_coe_nonempty Subtype.preimage_coe_nonempty

theorem preimage_coe_eq_empty {s t : Set α} : ((↑) : s → α) ⁻¹' t = ∅ ↔ s ∩ t = ∅ := by
  simp [← not_nonempty_iff_eq_empty, preimage_coe_nonempty]
#align subtype.preimage_coe_eq_empty Subtype.preimage_coe_eq_empty

-- Porting note:
-- @[simp] `simp` can prove this
theorem preimage_coe_compl (s : Set α) : ((↑) : s → α) ⁻¹' sᶜ = ∅ :=
  preimage_coe_eq_empty.2 (inter_compl_self s)
#align subtype.preimage_coe_compl Subtype.preimage_coe_compl

@[simp]
theorem preimage_coe_compl' (s : Set α) :
  (fun x : (sᶜ : Set α) => (x : α)) ⁻¹' s = ∅ :=
  preimage_coe_eq_empty.2 (compl_inter_self s)
#align subtype.preimage_coe_compl' Subtype.preimage_coe_compl'

end Subtype

/-! ### Images and preimages on `Option` -/

open Set

namespace Option

theorem injective_iff {α β} {f : Option α → β} :
    Injective f ↔ Injective (f ∘ some) ∧ f none ∉ range (f ∘ some) := by
  simp only [mem_range, not_exists, (· ∘ ·)]
  refine'
    ⟨fun hf => ⟨hf.comp (Option.some_injective _), fun x => hf.ne <| Option.some_ne_none _⟩, _⟩
  rintro ⟨h_some, h_none⟩ (_ | a) (_ | b) hab
  exacts[rfl, (h_none _ hab.symm).elim, (h_none _ hab).elim, congr_arg some (h_some hab)]
#align option.injective_iff Option.injective_iff

end Option

theorem WithBot.range_eq {α β} (f : WithBot α → β) :
    range f = insert (f ⊥) (range (f ∘ WithBot.some : α → β)) :=
  Option.range_eq f
#align with_bot.range_eq WithBot.range_eq

theorem WithTop.range_eq {α β} (f : WithTop α → β) :
    range f = insert (f ⊤) (range (f ∘ WithBot.some : α → β)) :=
  Option.range_eq f
#align with_top.range_eq WithTop.range_eq

namespace Set

open Function

/-! ### Injectivity and surjectivity lemmas for image and preimage -/

section ImagePreimage

variable {α : Type u} {β : Type v} {f : α → β}

@[simp]
theorem preimage_injective : Injective (preimage f) ↔ Surjective f := by
  refine' ⟨fun h y => _, Surjective.preimage_injective⟩
  obtain ⟨x, hx⟩ : (f ⁻¹' {y}).Nonempty := by
    rw [h.nonempty_apply_iff preimage_empty]
    apply singleton_nonempty
  exact ⟨x, hx⟩
#align set.preimage_injective Set.preimage_injective

@[simp]
theorem preimage_surjective : Surjective (preimage f) ↔ Injective f := by
  refine' ⟨fun h x x' hx => _, Injective.preimage_surjective⟩
  cases' h {x} with s hs; have := mem_singleton x
  rwa [← hs, mem_preimage, hx, ← mem_preimage, hs, mem_singleton_iff, eq_comm] at this
#align set.preimage_surjective Set.preimage_surjective

@[simp]
theorem image_surjective : Surjective (image f) ↔ Surjective f := by
  refine' ⟨fun h y => _, Surjective.image_surjective⟩
  cases' h {y} with s hs
  have := mem_singleton y; rw [← hs] at this; rcases this with ⟨x, _, hx⟩
  exact ⟨x, hx⟩
#align set.image_surjective Set.image_surjective

@[simp]
theorem image_injective : Injective (image f) ↔ Injective f := by
  refine' ⟨fun h x x' hx => _, Injective.image_injective⟩
  rw [← singleton_eq_singleton_iff]; apply h
  rw [image_singleton, image_singleton, hx]
#align set.image_injective Set.image_injective

theorem preimage_eq_iff_eq_image {f : α → β} (hf : Bijective f) {s t} : f ⁻¹' s = t ↔ s = f '' t :=
  by rw [← image_eq_image hf.1, hf.2.image_preimage]
#align set.preimage_eq_iff_eq_image Set.preimage_eq_iff_eq_image

theorem eq_preimage_iff_image_eq {f : α → β} (hf : Bijective f) {s t} : s = f ⁻¹' t ↔ f '' s = t :=
  by rw [← image_eq_image hf.1, hf.2.image_preimage]
#align set.eq_preimage_iff_image_eq Set.eq_preimage_iff_image_eq

end ImagePreimage

end Set

/-! ### Disjoint lemmas for image and preimage -/

section Disjoint
variable {α β γ : Type _} {f : α → β} {s t : Set α}

theorem Disjoint.preimage (f : α → β) {s t : Set β} (h : Disjoint s t) :
    Disjoint (f ⁻¹' s) (f ⁻¹' t) :=
  disjoint_iff_inf_le.mpr fun _ hx => h.le_bot hx
#align disjoint.preimage Disjoint.preimage

namespace Set

theorem disjoint_image_image {f : β → α} {g : γ → α} {s : Set β} {t : Set γ}
    (h : ∀ b ∈ s, ∀ c ∈ t, f b ≠ g c) : Disjoint (f '' s) (g '' t) :=
  disjoint_iff_inf_le.mpr <| by rintro a ⟨⟨b, hb, eq⟩, c, hc, rfl⟩; exact h b hb c hc eq
#align set.disjoint_image_image Set.disjoint_image_image

theorem disjoint_image_of_injective (hf : Injective f) {s t : Set α} (hd : Disjoint s t) :
    Disjoint (f '' s) (f '' t) :=
  disjoint_image_image fun _ hx _ hy => hf.ne fun H => Set.disjoint_iff.1 hd ⟨hx, H.symm ▸ hy⟩
#align set.disjoint_image_of_injective Set.disjoint_image_of_injective

theorem _root_.Disjoint.of_image (h : Disjoint (f '' s) (f '' t)) : Disjoint s t :=
  disjoint_iff_inf_le.mpr fun _ hx =>
    disjoint_left.1 h (mem_image_of_mem _ hx.1) (mem_image_of_mem _ hx.2)
#align disjoint.of_image Disjoint.of_image

theorem disjoint_image_iff (hf : Injective f) : Disjoint (f '' s) (f '' t) ↔ Disjoint s t :=
  ⟨Disjoint.of_image, disjoint_image_of_injective hf⟩
#align set.disjoint_image_iff Set.disjoint_image_iff

theorem _root_.Disjoint.of_preimage (hf : Surjective f) {s t : Set β}
    (h : Disjoint (f ⁻¹' s) (f ⁻¹' t)) : Disjoint s t := by
  rw [disjoint_iff_inter_eq_empty, ← image_preimage_eq (_ ∩ _) hf, preimage_inter, h.inter_eq,
    image_empty]
#align disjoint.of_preimage Disjoint.of_preimage

theorem disjoint_preimage_iff (hf : Surjective f) {s t : Set β} :
    Disjoint (f ⁻¹' s) (f ⁻¹' t) ↔ Disjoint s t :=
  ⟨Disjoint.of_preimage hf, Disjoint.preimage _⟩
#align set.disjoint_preimage_iff Set.disjoint_preimage_iff

theorem preimage_eq_empty {s : Set β} (h : Disjoint s (range f)) :
    f ⁻¹' s = ∅ :=
  by simpa using h.preimage f
#align set.preimage_eq_empty Set.preimage_eq_empty

theorem preimage_eq_empty_iff {s : Set β} : f ⁻¹' s = ∅ ↔ Disjoint s (range f) :=
  ⟨fun h => by
    simp only [eq_empty_iff_forall_not_mem, disjoint_iff_inter_eq_empty, not_exists, mem_inter_iff,
      not_and, mem_range, mem_preimage] at h ⊢
    intro y hy x hx
    rw [← hx] at hy
    exact h x hy,
  preimage_eq_empty⟩
#align set.preimage_eq_empty_iff Set.preimage_eq_empty_iff

end Set

end Disjoint