l3lab/ntp-mathlib huggingface dataset:
@[simp] theorem cast_trans {α β γ} (h : α = β) (h2 : β = γ) :
    (Equiv.cast h).trans (Equiv.cast h2) = Equiv.cast (h.trans h2) :=
  ext fun x => by 

Proof state:
α✝ : Sort u
β✝ : Sort v
γ✝ : Sort w
α β γ : Sort u_1
h : α = β
h2 : β = γ
x : α
⊢ ((Equiv.cast h).trans (Equiv.cast h2)) x = (Equiv.cast (_ : α = γ)) x

Next tactic:
substs h h2
@[simp] theorem cast_trans {α β γ} (h : α = β) (h2 : β = γ) :
    (Equiv.cast h).trans (Equiv.cast h2) = Equiv.cast (h.trans h2) :=
  ext fun x => by substs h h2; 

Proof state:
α✝ : Sort u
β : Sort v
γ : Sort w
α : Sort u_1
x : α
⊢ ((Equiv.cast (_ : α = α)).trans (Equiv.cast (_ : α = α))) x = (Equiv.cast (_ : α = α)) x

Next tactic:
rfl
theorem cast_eq_iff_heq {α β} (h : α = β) {a : α} {b : β} : Equiv.cast h a = b ↔ HEq a b := by
  

Proof state:
α✝ : Sort u
β✝ : Sort v
γ : Sort w
α β : Sort u_1
h : α = β
a : α
b : β
⊢ (Equiv.cast h) a = b ↔ HEq a b

Next tactic:
subst h
theorem cast_eq_iff_heq {α β} (h : α = β) {a : α} {b : β} : Equiv.cast h a = b ↔ HEq a b := by
  subst h; 

Proof state:
α✝ : Sort u
β : Sort v
γ : Sort w
α : Sort u_1
a b : α
⊢ (Equiv.cast (_ : α = α)) a = b ↔ HEq a b

Next tactic:
simp [coe_refl]
theorem symm_apply_eq {α β} (e : α ≃ β) {x y} : e.symm x = y ↔ x = e y :=
  ⟨fun H => by 

Proof state:
α✝ : Sort u
β✝ : Sort v
γ : Sort w
α : Sort u_1
β : Sort u_2
e : α ≃ β
x : β
y : α
H : e.symm x = y
⊢ x = e y

Next tactic:
simp [H.symm]
theorem symm_apply_eq {α β} (e : α ≃ β) {x y} : e.symm x = y ↔ x = e y :=
  ⟨fun H => by simp [H.symm], fun H => by 

Proof state:
α✝ : Sort u
β✝ : Sort v
γ : Sort w
α : Sort u_1
β : Sort u_2
e : α ≃ β
x : β
y : α
H : x = e y
⊢ e.symm x = y

Next tactic:
simp [H]
@[simp] theorem symm_symm (e : α ≃ β) : e.symm.symm = e := by 

Proof state:
α : Sort u
β : Sort v
γ : Sort w
e : α ≃ β
⊢ e.symm.symm = e

Next tactic:
cases e
@[simp] theorem symm_symm (e : α ≃ β) : e.symm.symm = e := by cases e; 

Proof state:
case mk
α : Sort u
β : Sort v
γ : Sort w
toFun✝ : α → β
invFun✝ : β → α
left_inv✝ : LeftInverse invFun✝ toFun✝
right_inv✝ : Function.RightInverse invFun✝ toFun✝
⊢ { toFun := toFun✝, invFun := invFun✝, left_inv := left_inv✝, right_inv := right_inv✝ }.symm.symm =
    { toFun := toFun✝, invFun := invFun✝, left_inv := left_inv✝, right_inv := right_inv✝ }

Next tactic:
rfl
@[simp] theorem trans_refl (e : α ≃ β) : e.trans (Equiv.refl β) = e := by 

Proof state:
α : Sort u
β : Sort v
γ : Sort w
e : α ≃ β
⊢ e.trans (Equiv.refl β) = e

Next tactic:
cases e
@[simp] theorem trans_refl (e : α ≃ β) : e.trans (Equiv.refl β) = e := by cases e; 

Proof state:
case mk
α : Sort u
β : Sort v
γ : Sort w
toFun✝ : α → β
invFun✝ : β → α
left_inv✝ : LeftInverse invFun✝ toFun✝
right_inv✝ : Function.RightInverse invFun✝ toFun✝
⊢ { toFun := toFun✝, invFun := invFun✝, left_inv := left_inv✝, right_inv := right_inv✝ }.trans (Equiv.refl β) =
    { toFun := toFun✝, invFun := invFun✝, left_inv := left_inv✝, right_inv := right_inv✝ }

Next tactic:
rfl
@[simp] theorem refl_trans (e : α ≃ β) : (Equiv.refl α).trans e = e := by 

Proof state:
α : Sort u
β : Sort v
γ : Sort w
e : α ≃ β
⊢ (Equiv.refl α).trans e = e

Next tactic:
cases e
@[simp] theorem refl_trans (e : α ≃ β) : (Equiv.refl α).trans e = e := by cases e; 

Proof state:
case mk
α : Sort u
β : Sort v
γ : Sort w
toFun✝ : α → β
invFun✝ : β → α
left_inv✝ : LeftInverse invFun✝ toFun✝
right_inv✝ : Function.RightInverse invFun✝ toFun✝
⊢ (Equiv.refl α).trans { toFun := toFun✝, invFun := invFun✝, left_inv := left_inv✝, right_inv := right_inv✝ } =
    { toFun := toFun✝, invFun := invFun✝, left_inv := left_inv✝, right_inv := right_inv✝ }

Next tactic:
rfl
@[simp] theorem symm_trans_self (e : α ≃ β) : e.symm.trans e = Equiv.refl β := ext <| by 

Proof state:
α : Sort u
β : Sort v
γ : Sort w
e : α ≃ β
⊢ ∀ (x : β), (e.symm.trans e) x = (Equiv.refl β) x

Next tactic:
simp
@[simp] theorem self_trans_symm (e : α ≃ β) : e.trans e.symm = Equiv.refl α := ext <| by 

Proof state:
α : Sort u
β : Sort v
γ : Sort w
e : α ≃ β
⊢ ∀ (x : α), (e.trans e.symm) x = (Equiv.refl α) x

Next tactic:
simp
/-- If `α` is equivalent to `β` and `γ` is equivalent to `δ`, then the type of equivalences `α ≃ γ`
is equivalent to the type of equivalences `β ≃ δ`. -/
def equivCongr (ab : α ≃ β) (cd : γ ≃ δ) : (α ≃ γ) ≃ (β ≃ δ) where
  toFun ac := (ab.symm.trans ac).trans cd
  invFun bd := ab.trans <| bd.trans <| cd.symm
  left_inv ac := by 

Proof state:
α : Sort u
β : Sort v
γ : Sort w
δ : Sort ?u.24086
ab : α ≃ β
cd : γ ≃ δ
ac : α ≃ γ
⊢ (fun bd => ab.trans (bd.trans cd.symm)) ((fun ac => (ab.symm.trans ac).trans cd) ac) = ac

Next tactic:
ext x
/-- If `α` is equivalent to `β` and `γ` is equivalent to `δ`, then the type of equivalences `α ≃ γ`
is equivalent to the type of equivalences `β ≃ δ`. -/
def equivCongr (ab : α ≃ β) (cd : γ ≃ δ) : (α ≃ γ) ≃ (β ≃ δ) where
  toFun ac := (ab.symm.trans ac).trans cd
  invFun bd := ab.trans <| bd.trans <| cd.symm
  left_inv ac := by ext x; 

Proof state:
case H
α : Sort u
β : Sort v
γ : Sort w
δ : Sort ?u.24086
ab : α ≃ β
cd : γ ≃ δ
ac : α ≃ γ
x : α
⊢ ((fun bd => ab.trans (bd.trans cd.symm)) ((fun ac => (ab.symm.trans ac).trans cd) ac)) x = ac x

Next tactic:
simp only [trans_apply, comp_apply, symm_apply_apply]
/-- If `α` is equivalent to `β` and `γ` is equivalent to `δ`, then the type of equivalences `α ≃ γ`
is equivalent to the type of equivalences `β ≃ δ`. -/
def equivCongr (ab : α ≃ β) (cd : γ ≃ δ) : (α ≃ γ) ≃ (β ≃ δ) where
  toFun ac := (ab.symm.trans ac).trans cd
  invFun bd := ab.trans <| bd.trans <| cd.symm
  left_inv ac := by ext x; simp only [trans_apply, comp_apply, symm_apply_apply]
  right_inv ac := by 

Proof state:
α : Sort u
β : Sort v
γ : Sort w
δ : Sort ?u.24086
ab : α ≃ β
cd : γ ≃ δ
ac : β ≃ δ
⊢ (fun ac => (ab.symm.trans ac).trans cd) ((fun bd => ab.trans (bd.trans cd.symm)) ac) = ac

Next tactic:
ext x
/-- If `α` is equivalent to `β` and `γ` is equivalent to `δ`, then the type of equivalences `α ≃ γ`
is equivalent to the type of equivalences `β ≃ δ`. -/
def equivCongr (ab : α ≃ β) (cd : γ ≃ δ) : (α ≃ γ) ≃ (β ≃ δ) where
  toFun ac := (ab.symm.trans ac).trans cd
  invFun bd := ab.trans <| bd.trans <| cd.symm
  left_inv ac := by ext x; simp only [trans_apply, comp_apply, symm_apply_apply]
  right_inv ac := by ext x; 

Proof state:
case H
α : Sort u
β : Sort v
γ : Sort w
δ : Sort ?u.24086
ab : α ≃ β
cd : γ ≃ δ
ac : β ≃ δ
x : β
⊢ ((fun ac => (ab.symm.trans ac).trans cd) ((fun bd => ab.trans (bd.trans cd.symm)) ac)) x = ac x

Next tactic:
simp only [trans_apply, comp_apply, apply_symm_apply]
@[simp] theorem equivCongr_refl {α β} :
    (Equiv.refl α).equivCongr (Equiv.refl β) = Equiv.refl (α ≃ β) := by 

Proof state:
α✝ : Sort u
β✝ : Sort v
γ : Sort w
α : Sort u_1
β : Sort u_2
⊢ equivCongr (Equiv.refl α) (Equiv.refl β) = Equiv.refl (α ≃ β)

Next tactic:
ext
@[simp] theorem equivCongr_refl {α β} :
    (Equiv.refl α).equivCongr (Equiv.refl β) = Equiv.refl (α ≃ β) := by ext; 

Proof state:
case H.H
α✝ : Sort u
β✝ : Sort v
γ : Sort w
α : Sort u_1
β : Sort u_2
x✝¹ : α ≃ β
x✝ : α
⊢ ((equivCongr (Equiv.refl α) (Equiv.refl β)) x✝¹) x✝ = ((Equiv.refl (α ≃ β)) x✝¹) x✝

Next tactic:
rfl
@[simp] theorem equivCongr_symm {δ} (ab : α ≃ β) (cd : γ ≃ δ) :
    (ab.equivCongr cd).symm = ab.symm.equivCongr cd.symm := by 

Proof state:
α : Sort u
β : Sort v
γ : Sort w
δ : Sort u_1
ab : α ≃ β
cd : γ ≃ δ
⊢ (equivCongr ab cd).symm = equivCongr ab.symm cd.symm

Next tactic:
ext
@[simp] theorem equivCongr_symm {δ} (ab : α ≃ β) (cd : γ ≃ δ) :
    (ab.equivCongr cd).symm = ab.symm.equivCongr cd.symm := by ext; 

Proof state:
case H.H
α : Sort u
β : Sort v
γ : Sort w
δ : Sort u_1
ab : α ≃ β
cd : γ ≃ δ
x✝¹ : β ≃ δ
x✝ : α
⊢ ((equivCongr ab cd).symm x✝¹) x✝ = ((equivCongr ab.symm cd.symm) x✝¹) x✝

Next tactic:
rfl
@[simp] theorem equivCongr_trans {δ ε ζ} (ab : α ≃ β) (de : δ ≃ ε) (bc : β ≃ γ) (ef : ε ≃ ζ) :
    (ab.equivCongr de).trans (bc.equivCongr ef) = (ab.trans bc).equivCongr (de.trans ef) := by
  

Proof state:
α : Sort u
β : Sort v
γ : Sort w
δ : Sort u_1
ε : Sort u_2
ζ : Sort u_3
ab : α ≃ β
de : δ ≃ ε
bc : β ≃ γ
ef : ε ≃ ζ
⊢ (equivCongr ab de).trans (equivCongr bc ef) = equivCongr (ab.trans bc) (de.trans ef)

Next tactic:
ext
@[simp] theorem equivCongr_trans {δ ε ζ} (ab : α ≃ β) (de : δ ≃ ε) (bc : β ≃ γ) (ef : ε ≃ ζ) :
    (ab.equivCongr de).trans (bc.equivCongr ef) = (ab.trans bc).equivCongr (de.trans ef) := by
  ext; 

Proof state:
case H.H
α : Sort u
β : Sort v
γ : Sort w
δ : Sort u_1
ε : Sort u_2
ζ : Sort u_3
ab : α ≃ β
de : δ ≃ ε
bc : β ≃ γ
ef : ε ≃ ζ
x✝¹ : α ≃ δ
x✝ : γ
⊢ (((equivCongr ab de).trans (equivCongr bc ef)) x✝¹) x✝ = ((equivCongr (ab.trans bc) (de.trans ef)) x✝¹) x✝

Next tactic:
rfl
@[simp] theorem permCongr_refl : e.permCongr (Equiv.refl _) = Equiv.refl _ := by
  

Proof state:
α : Sort u
β : Sort v
γ : Sort w
α' : Type u_1
β' : Type u_2
e : α' ≃ β'
⊢ (permCongr e) (Equiv.refl α') = Equiv.refl β'

Next tactic:
simp [permCongr_def]
theorem permCongr_trans (p p' : Equiv.Perm α') :
    (e.permCongr p).trans (e.permCongr p') = e.permCongr (p.trans p') := by
  

Proof state:
α : Sort u
β : Sort v
γ : Sort w
α' : Type u_1
β' : Type u_2
e : α' ≃ β'
p p' : Perm α'
⊢ ((permCongr e) p).trans ((permCongr e) p') = (permCongr e) (p.trans p')

Next tactic:
ext
theorem permCongr_trans (p p' : Equiv.Perm α') :
    (e.permCongr p).trans (e.permCongr p') = e.permCongr (p.trans p') := by
  ext; 

Proof state:
case H
α : Sort u
β : Sort v
γ : Sort w
α' : Type u_1
β' : Type u_2
e : α' ≃ β'
p p' : Perm α'
x✝ : β'
⊢ (((permCongr e) p).trans ((permCongr e) p')) x✝ = ((permCongr e) (p.trans p')) x✝

Next tactic:
simp only [trans_apply, comp_apply, permCongr_apply, symm_apply_apply]
/-- If `α₁` is equivalent to `α₂` and `β₁` is equivalent to `β₂`, then the type of maps `α₁ → β₁`
is equivalent to the type of maps `α₂ → β₂`. -/
-- porting note: removing `congr` attribute
@[simps apply]
def arrowCongr {α₁ β₁ α₂ β₂ : Sort*} (e₁ : α₁ ≃ α₂) (e₂ : β₁ ≃ β₂) : (α₁ → β₁) ≃ (α₂ → β₂) where
  toFun f := e₂ ∘ f ∘ e₁.symm
  invFun f := e₂.symm ∘ f ∘ e₁
  left_inv f := funext fun x => by 

Proof state:
α : Sort u
β : Sort v
γ : Sort w
α₁ : Sort u_1
β₁ : Sort u_2
α₂ : Sort u_3
β₂ : Sort u_4
e₁ : α₁ ≃ α₂
e₂ : β₁ ≃ β₂
f : α₁ → β₁
x : α₁
⊢ (fun f => ⇑e₂.symm ∘ f ∘ ⇑e₁) ((fun f => ⇑e₂ ∘ f ∘ ⇑e₁.symm) f) x = f x

Next tactic:
simp only [comp_apply, symm_apply_apply]
/-- If `α₁` is equivalent to `α₂` and `β₁` is equivalent to `β₂`, then the type of maps `α₁ → β₁`
is equivalent to the type of maps `α₂ → β₂`. -/
-- porting note: removing `congr` attribute
@[simps apply]
def arrowCongr {α₁ β₁ α₂ β₂ : Sort*} (e₁ : α₁ ≃ α₂) (e₂ : β₁ ≃ β₂) : (α₁ → β₁) ≃ (α₂ → β₂) where
  toFun f := e₂ ∘ f ∘ e₁.symm
  invFun f := e₂.symm ∘ f ∘ e₁
  left_inv f := funext fun x => by simp only [comp_apply, symm_apply_apply]
  right_inv f := funext fun x => by 

Proof state:
α : Sort u
β : Sort v
γ : Sort w
α₁ : Sort u_1
β₁ : Sort u_2
α₂ : Sort u_3
β₂ : Sort u_4
e₁ : α₁ ≃ α₂
e₂ : β₁ ≃ β₂
f : α₂ → β₂
x : α₂
⊢ (fun f => ⇑e₂ ∘ f ∘ ⇑e₁.symm) ((fun f => ⇑e₂.symm ∘ f ∘ ⇑e₁) f) x = f x

Next tactic:
simp only [comp_apply, apply_symm_apply]
theorem arrowCongr_comp {α₁ β₁ γ₁ α₂ β₂ γ₂ : Sort*} (ea : α₁ ≃ α₂) (eb : β₁ ≃ β₂) (ec : γ₁ ≃ γ₂)
    (f : α₁ → β₁) (g : β₁ → γ₁) :
    arrowCongr ea ec (g ∘ f) = arrowCongr eb ec g ∘ arrowCongr ea eb f := by
  

Proof state:
α : Sort u
β : Sort v
γ : Sort w
α₁ : Sort u_1
β₁ : Sort u_2
γ₁ : Sort u_3
α₂ : Sort u_4
β₂ : Sort u_5
γ₂ : Sort u_6
ea : α₁ ≃ α₂
eb : β₁ ≃ β₂
ec : γ₁ ≃ γ₂
f : α₁ → β₁
g : β₁ → γ₁
⊢ (arrowCongr ea ec) (g ∘ f) = (arrowCongr eb ec) g ∘ (arrowCongr ea eb) f

Next tactic:
ext
theorem arrowCongr_comp {α₁ β₁ γ₁ α₂ β₂ γ₂ : Sort*} (ea : α₁ ≃ α₂) (eb : β₁ ≃ β₂) (ec : γ₁ ≃ γ₂)
    (f : α₁ → β₁) (g : β₁ → γ₁) :
    arrowCongr ea ec (g ∘ f) = arrowCongr eb ec g ∘ arrowCongr ea eb f := by
  ext; 

Proof state:
case h
α : Sort u
β : Sort v
γ : Sort w
α₁ : Sort u_1
β₁ : Sort u_2
γ₁ : Sort u_3
α₂ : Sort u_4
β₂ : Sort u_5
γ₂ : Sort u_6
ea : α₁ ≃ α₂
eb : β₁ ≃ β₂
ec : γ₁ ≃ γ₂
f : α₁ → β₁
g : β₁ → γ₁
x✝ : α₂
⊢ (arrowCongr ea ec) (g ∘ f) x✝ = ((arrowCongr eb ec) g ∘ (arrowCongr ea eb) f) x✝

Next tactic:
simp only [comp, arrowCongr_apply, eb.symm_apply_apply]
theorem conj_comp (e : α ≃ β) (f₁ f₂ : α → α) : e.conj (f₁ ∘ f₂) = e.conj f₁ ∘ e.conj f₂ := by
  

Proof state:
α : Sort u
β : Sort v
γ : Sort w
e : α ≃ β
f₁ f₂ : α → α
⊢ (conj e) (f₁ ∘ f₂) = (conj e) f₁ ∘ (conj e) f₂

Next tactic:
apply arrowCongr_comp
/-- `Prop` is noncomputably equivalent to `Bool`. -/
noncomputable def propEquivBool : Prop ≃ Bool where
  toFun p := @decide p (Classical.propDecidable _)
  invFun b := b
  left_inv p := by 

Proof state:
α : Sort u
β : Sort v
γ : Sort w
p : Prop
⊢ (fun b => b = true) ((fun p => decide p) p) = p

Next tactic:
simp [@Bool.decide_iff p (Classical.propDecidable _)]
/-- `Prop` is noncomputably equivalent to `Bool`. -/
noncomputable def propEquivBool : Prop ≃ Bool where
  toFun p := @decide p (Classical.propDecidable _)
  invFun b := b
  left_inv p := by simp [@Bool.decide_iff p (Classical.propDecidable _)]
  right_inv b := by 

Proof state:
α : Sort u
β : Sort v
γ : Sort w
b : Bool
⊢ (fun p => decide p) ((fun b => b = true) b) = b

Next tactic:
cases b
/-- `Prop` is noncomputably equivalent to `Bool`. -/
noncomputable def propEquivBool : Prop ≃ Bool where
  toFun p := @decide p (Classical.propDecidable _)
  invFun b := b
  left_inv p := by simp [@Bool.decide_iff p (Classical.propDecidable _)]
  right_inv b := by cases b <;> 

Proof state:
case false
α : Sort u
β : Sort v
γ : Sort w
⊢ (fun p => decide p) ((fun b => b = true) false) = false

Next tactic:
simp
/-- `Prop` is noncomputably equivalent to `Bool`. -/
noncomputable def propEquivBool : Prop ≃ Bool where
  toFun p := @decide p (Classical.propDecidable _)
  invFun b := b
  left_inv p := by simp [@Bool.decide_iff p (Classical.propDecidable _)]
  right_inv b := by cases b <;> 

Proof state:
case true
α : Sort u
β : Sort v
γ : Sort w
⊢ (fun p => decide p) ((fun b => b = true) true) = true

Next tactic:
simp
/-- The equivalence `(∀ i, β i) ≃ β ⋆` when the domain of `β` only contains `⋆` -/
@[simps (config := .asFn)]
def piUnique [Unique α] (β : α → Sort*) : (∀ i, β i) ≃ β default where
  toFun f := f default
  invFun := uniqueElim
  left_inv f := by 

Proof state:
α : Sort u
β✝ : Sort v
γ : Sort w
inst✝ : Unique α
β : α → Sort u_1
f : (i : α) → β i
⊢ uniqueElim ((fun f => f default) f) = f

Next tactic:
ext i
/-- The equivalence `(∀ i, β i) ≃ β ⋆` when the domain of `β` only contains `⋆` -/
@[simps (config := .asFn)]
def piUnique [Unique α] (β : α → Sort*) : (∀ i, β i) ≃ β default where
  toFun f := f default
  invFun := uniqueElim
  left_inv f := by ext i; 

Proof state:
case h
α : Sort u
β✝ : Sort v
γ : Sort w
inst✝ : Unique α
β : α → Sort u_1
f : (i : α) → β i
i : α
⊢ uniqueElim ((fun f => f default) f) i = f i

Next tactic:
cases Unique.eq_default i
/-- The equivalence `(∀ i, β i) ≃ β ⋆` when the domain of `β` only contains `⋆` -/
@[simps (config := .asFn)]
def piUnique [Unique α] (β : α → Sort*) : (∀ i, β i) ≃ β default where
  toFun f := f default
  invFun := uniqueElim
  left_inv f := by ext i; cases Unique.eq_default i; 

Proof state:
case h.refl
α : Sort u
β✝ : Sort v
γ : Sort w
inst✝ : Unique α
β : α → Sort u_1
f : (i : α) → β i
⊢ uniqueElim ((fun f => f default) f) default = f default

Next tactic:
rfl
protected theorem exists_unique_congr {p : α → Prop} {q : β → Prop}
    (f : α ≃ β) (h : ∀ {x}, p x ↔ q (f x)) : (∃! x, p x) ↔ ∃! y, q y := by
  

Proof state:
α : Sort u
β : Sort v
γ : Sort w
p : α → Prop
q : β → Prop
f : α ≃ β
h : ∀ {x : α}, p x ↔ q (f x)
⊢ (∃! x, p x) ↔ ∃! y, q y

Next tactic:
constructor
protected theorem exists_unique_congr {p : α → Prop} {q : β → Prop}
    (f : α ≃ β) (h : ∀ {x}, p x ↔ q (f x)) : (∃! x, p x) ↔ ∃! y, q y := by
  constructor
  · 

Proof state:
case mp
α : Sort u
β : Sort v
γ : Sort w
p : α → Prop
q : β → Prop
f : α ≃ β
h : ∀ {x : α}, p x ↔ q (f x)
⊢ (∃! x, p x) → ∃! y, q y

Next tactic:
rintro ⟨a, ha₁, ha₂⟩
protected theorem exists_unique_congr {p : α → Prop} {q : β → Prop}
    (f : α ≃ β) (h : ∀ {x}, p x ↔ q (f x)) : (∃! x, p x) ↔ ∃! y, q y := by
  constructor
  · rintro ⟨a, ha₁, ha₂⟩
    

Proof state:
case mp.intro.intro
α : Sort u
β : Sort v
γ : Sort w
p : α → Prop
q : β → Prop
f : α ≃ β
h : ∀ {x : α}, p x ↔ q (f x)
a : α
ha₁ : p a
ha₂ : ∀ (y : α), (fun x => p x) y → y = a
⊢ ∃! y, q y

Next tactic:
exact ⟨f a, h.1 ha₁, fun b hb => f.symm_apply_eq.1 (ha₂ (f.symm b) (h.2 (by simpa using hb)))⟩
protected theorem exists_unique_congr {p : α → Prop} {q : β → Prop}
    (f : α ≃ β) (h : ∀ {x}, p x ↔ q (f x)) : (∃! x, p x) ↔ ∃! y, q y := by
  constructor
  · rintro ⟨a, ha₁, ha₂⟩
    exact ⟨f a, h.1 ha₁, fun b hb => f.symm_apply_eq.1 (ha₂ (f.symm b) (h.2 (by 

Proof state:
α : Sort u
β : Sort v
γ : Sort w
p : α → Prop
q : β → Prop
f : α ≃ β
h : ∀ {x : α}, p x ↔ q (f x)
a : α
ha₁ : p a
ha₂ : ∀ (y : α), (fun x => p x) y → y = a
b : β
hb : (fun y => q y) b
⊢ q (f (f.symm b))

Next tactic:
simpa using hb
protected theorem exists_unique_congr {p : α → Prop} {q : β → Prop}
    (f : α ≃ β) (h : ∀ {x}, p x ↔ q (f x)) : (∃! x, p x) ↔ ∃! y, q y := by
  constructor
  · rintro ⟨a, ha₁, ha₂⟩
    exact ⟨f a, h.1 ha₁, fun b hb => f.symm_apply_eq.1 (ha₂ (f.symm b) (h.2 (by simpa using hb)))⟩
  · 

Proof state:
case mpr
α : Sort u
β : Sort v
γ : Sort w
p : α → Prop
q : β → Prop
f : α ≃ β
h : ∀ {x : α}, p x ↔ q (f x)
⊢ (∃! y, q y) → ∃! x, p x

Next tactic:
rintro ⟨b, hb₁, hb₂⟩
protected theorem exists_unique_congr {p : α → Prop} {q : β → Prop}
    (f : α ≃ β) (h : ∀ {x}, p x ↔ q (f x)) : (∃! x, p x) ↔ ∃! y, q y := by
  constructor
  · rintro ⟨a, ha₁, ha₂⟩
    exact ⟨f a, h.1 ha₁, fun b hb => f.symm_apply_eq.1 (ha₂ (f.symm b) (h.2 (by simpa using hb)))⟩
  · rintro ⟨b, hb₁, hb₂⟩
    

Proof state:
case mpr.intro.intro
α : Sort u
β : Sort v
γ : Sort w
p : α → Prop
q : β → Prop
f : α ≃ β
h : ∀ {x : α}, p x ↔ q (f x)
b : β
hb₁ : q b
hb₂ : ∀ (y : β), (fun y => q y) y → y = b
⊢ ∃! x, p x

Next tactic:
exact ⟨f.symm b, h.2 (by simpa using hb₁), fun y hy => (eq_symm_apply f).2 (hb₂ _ (h.1 hy))⟩
protected theorem exists_unique_congr {p : α → Prop} {q : β → Prop}
    (f : α ≃ β) (h : ∀ {x}, p x ↔ q (f x)) : (∃! x, p x) ↔ ∃! y, q y := by
  constructor
  · rintro ⟨a, ha₁, ha₂⟩
    exact ⟨f a, h.1 ha₁, fun b hb => f.symm_apply_eq.1 (ha₂ (f.symm b) (h.2 (by simpa using hb)))⟩
  · rintro ⟨b, hb₁, hb₂⟩
    exact ⟨f.symm b, h.2 (by 

Proof state:
α : Sort u
β : Sort v
γ : Sort w
p : α → Prop
q : β → Prop
f : α ≃ β
h : ∀ {x : α}, p x ↔ q (f x)
b : β
hb₁ : q b
hb₂ : ∀ (y : β), (fun y => q y) y → y = b
⊢ q (f (f.symm b))

Next tactic:
simpa using hb₁
protected theorem exists_unique_congr_left' {p : α → Prop} (f : α ≃ β) :
    (∃! x, p x) ↔ ∃! y, p (f.symm y) := Equiv.exists_unique_congr f fun {_} => by 

Proof state:
α : Sort u
β : Sort v
γ : Sort w
p : α → Prop
f : α ≃ β
x✝ : α
⊢ p x✝ ↔ p (f.symm (f x✝))

Next tactic:
simp
protected theorem forall_congr {p : α → Prop} {q : β → Prop} (f : α ≃ β)
    (h : ∀ {x}, p x ↔ q (f x)) : (∀ x, p x) ↔ (∀ y, q y) := by
  

Proof state:
α : Sort u
β : Sort v
γ : Sort w
p : α → Prop
q : β → Prop
f : α ≃ β
h : ∀ {x : α}, p x ↔ q (f x)
⊢ (∀ (x : α), p x) ↔ ∀ (y : β), q y

Next tactic:
constructor
protected theorem forall_congr {p : α → Prop} {q : β → Prop} (f : α ≃ β)
    (h : ∀ {x}, p x ↔ q (f x)) : (∀ x, p x) ↔ (∀ y, q y) := by
  constructor <;> 

Proof state:
case mp
α : Sort u
β : Sort v
γ : Sort w
p : α → Prop
q : β → Prop
f : α ≃ β
h : ∀ {x : α}, p x ↔ q (f x)
⊢ (∀ (x : α), p x) → ∀ (y : β), q y

Next tactic:
intro h₂ x
protected theorem forall_congr {p : α → Prop} {q : β → Prop} (f : α ≃ β)
    (h : ∀ {x}, p x ↔ q (f x)) : (∀ x, p x) ↔ (∀ y, q y) := by
  constructor <;> 

Proof state:
case mpr
α : Sort u
β : Sort v
γ : Sort w
p : α → Prop
q : β → Prop
f : α ≃ β
h : ∀ {x : α}, p x ↔ q (f x)
⊢ (∀ (y : β), q y) → ∀ (x : α), p x

Next tactic:
intro h₂ x
protected theorem forall_congr {p : α → Prop} {q : β → Prop} (f : α ≃ β)
    (h : ∀ {x}, p x ↔ q (f x)) : (∀ x, p x) ↔ (∀ y, q y) := by
  constructor <;> intro h₂ x
  · 

Proof state:
case mp
α : Sort u
β : Sort v
γ : Sort w
p : α → Prop
q : β → Prop
f : α ≃ β
h : ∀ {x : α}, p x ↔ q (f x)
h₂ : ∀ (x : α), p x
x : β
⊢ q x

Next tactic:
rw [← f.right_inv x]
protected theorem forall_congr {p : α → Prop} {q : β → Prop} (f : α ≃ β)
    (h : ∀ {x}, p x ↔ q (f x)) : (∀ x, p x) ↔ (∀ y, q y) := by
  constructor <;> intro h₂ x
  · rw [← f.right_inv x]; 

Proof state:
case mp
α : Sort u
β : Sort v
γ : Sort w
p : α → Prop
q : β → Prop
f : α ≃ β
h : ∀ {x : α}, p x ↔ q (f x)
h₂ : ∀ (x : α), p x
x : β
⊢ q (Equiv.toFun f (Equiv.invFun f x))

Next tactic:
apply h.mp
protected theorem forall_congr {p : α → Prop} {q : β → Prop} (f : α ≃ β)
    (h : ∀ {x}, p x ↔ q (f x)) : (∀ x, p x) ↔ (∀ y, q y) := by
  constructor <;> intro h₂ x
  · rw [← f.right_inv x]; apply h.mp; 

Proof state:
case mp
α : Sort u
β : Sort v
γ : Sort w
p : α → Prop
q : β → Prop
f : α ≃ β
h : ∀ {x : α}, p x ↔ q (f x)
h₂ : ∀ (x : α), p x
x : β
⊢ p (Equiv.invFun f x)

Next tactic:
apply h₂
protected theorem forall_congr {p : α → Prop} {q : β → Prop} (f : α ≃ β)
    (h : ∀ {x}, p x ↔ q (f x)) : (∀ x, p x) ↔ (∀ y, q y) := by
  constructor <;> intro h₂ x
  · rw [← f.right_inv x]; apply h.mp; apply h₂
  · 

Proof state:
case mpr
α : Sort u
β : Sort v
γ : Sort w
p : α → Prop
q : β → Prop
f : α ≃ β
h : ∀ {x : α}, p x ↔ q (f x)
h₂ : ∀ (y : β), q y
x : α
⊢ p x

Next tactic:
apply h.mpr
protected theorem forall_congr {p : α → Prop} {q : β → Prop} (f : α ≃ β)
    (h : ∀ {x}, p x ↔ q (f x)) : (∀ x, p x) ↔ (∀ y, q y) := by
  constructor <;> intro h₂ x
  · rw [← f.right_inv x]; apply h.mp; apply h₂
  · apply h.mpr; 

Proof state:
case mpr
α : Sort u
β : Sort v
γ : Sort w
p : α → Prop
q : β → Prop
f : α ≃ β
h : ∀ {x : α}, p x ↔ q (f x)
h₂ : ∀ (y : β), q y
x : α
⊢ q (f x)

Next tactic:
apply h₂
protected theorem forall_congr_left' {p : α → Prop} (f : α ≃ β) : (∀ x, p x) ↔ ∀ y, p (f.symm y) :=
  Equiv.forall_congr f <| by 

Proof state:
α : Sort u
β : Sort v
γ : Sort w
p : α → Prop
f : α ≃ β
⊢ ∀ {x : α}, p x ↔ p (f.symm (f x))

Next tactic:
simp
protected theorem exists_congr_left {α β} (f : α ≃ β) {p : α → Prop} :
    (∃ a, p a) ↔ ∃ b, p (f.symm b) :=
  ⟨fun ⟨a, h⟩ => ⟨f a, by 

Proof state:
α✝ : Sort u
β✝ : Sort v
γ : Sort w
α : Sort u_1
β : Sort u_2
f : α ≃ β
p : α → Prop
x✝ : ∃ a, p a
a : α
h : p a
⊢ p (f.symm (f a))

Next tactic:
simpa using h
/-- An equivalence `e : α ≃ β` generates an equivalence between quotient spaces,
if `ra a₁ a₂ ↔ rb (e a₁) (e a₂)`. -/
protected def congr {ra : α → α → Prop} {rb : β → β → Prop} (e : α ≃ β)
    (eq : ∀ a₁ a₂, ra a₁ a₂ ↔ rb (e a₁) (e a₂)) : Quot ra ≃ Quot rb where
  toFun := Quot.map e fun a₁ a₂ => (eq a₁ a₂).1
  invFun := Quot.map e.symm fun b₁ b₂ h =>
    (eq (e.symm b₁) (e.symm b₂)).2
      ((e.apply_symm_apply b₁).symm ▸ (e.apply_symm_apply b₂).symm ▸ h)
  left_inv := by 

Proof state:
α : Sort u
β : Sort v
γ : Sort w
ra : α → α → Prop
rb : β → β → Prop
e : α ≃ β
eq : ∀ (a₁ a₂ : α), ra a₁ a₂ ↔ rb (e a₁) (e a₂)
⊢ LeftInverse (Quot.map ⇑e.symm (_ : ∀ (b₁ b₂ : β), rb b₁ b₂ → ra (e.symm b₁) (e.symm b₂)))
    (Quot.map ⇑e (_ : ∀ (a₁ a₂ : α), ra a₁ a₂ → rb (e a₁) (e a₂)))

Next tactic:
rintro ⟨a⟩
/-- An equivalence `e : α ≃ β` generates an equivalence between quotient spaces,
if `ra a₁ a₂ ↔ rb (e a₁) (e a₂)`. -/
protected def congr {ra : α → α → Prop} {rb : β → β → Prop} (e : α ≃ β)
    (eq : ∀ a₁ a₂, ra a₁ a₂ ↔ rb (e a₁) (e a₂)) : Quot ra ≃ Quot rb where
  toFun := Quot.map e fun a₁ a₂ => (eq a₁ a₂).1
  invFun := Quot.map e.symm fun b₁ b₂ h =>
    (eq (e.symm b₁) (e.symm b₂)).2
      ((e.apply_symm_apply b₁).symm ▸ (e.apply_symm_apply b₂).symm ▸ h)
  left_inv := by rintro ⟨a⟩; 

Proof state:
case mk
α : Sort u
β : Sort v
γ : Sort w
ra : α → α → Prop
rb : β → β → Prop
e : α ≃ β
eq : ∀ (a₁ a₂ : α), ra a₁ a₂ ↔ rb (e a₁) (e a₂)
x✝ : Quot ra
a : α
⊢ Quot.map ⇑e.symm (_ : ∀ (b₁ b₂ : β), rb b₁ b₂ → ra (e.symm b₁) (e.symm b₂))
      (Quot.map ⇑e (_ : ∀ (a₁ a₂ : α), ra a₁ a₂ → rb (e a₁) (e a₂)) (mk ra a)) =
    mk ra a

Next tactic:
simp only [Quot.map, Equiv.symm_apply_apply]