l3lab/ntp-mathlib huggingface dataset:
theorem numDerangements_tendsto_inv_e :
    Tendsto (fun n => (numDerangements n : ℝ) / n.factorial) atTop (𝓝 (Real.exp (-1))) := by
  -- we show that d(n)/n! is the partial sum of exp(-1), but offset by 1.
  -- this isn't entirely obvious, since we have to ensure that asc_factorial and
  -- factorial interact in the right way, e.g., that k ≤ n always
  let s : ℕ → ℝ := fun n => ∑ k in Finset.range n, (-1 : ℝ) ^ k / k.factorial
  suffices ∀ n : ℕ, (numDerangements n : ℝ) / n.factorial = s (n + 1) by
    simp_rw [this]
    -- shift the function by 1, and then use the fact that the partial sums
    -- converge to the infinite sum
    rw [tendsto_add_atTop_iff_nat
      (f := fun n => ∑ k in Finset.range n, (-1 : ℝ) ^ k / k.factorial) 1]
    apply HasSum.tendsto_sum_nat
    -- there's no specific lemma for ℝ that ∑ x^k/k! sums to exp(x), but it's
    -- true in more general fields, so use that lemma
    rw [Real.exp_eq_exp_ℝ]
    exact expSeries_div_hasSum_exp ℝ (-1 : ℝ)
  intro n
  rw [← Int.cast_ofNat, numDerangements_sum]
  push_cast
  rw [Finset.sum_div]
  -- get down to individual terms
  refine' Finset.sum_congr (refl _) _
  intro k hk
  

Proof state:
s : ℕ → ℝ := fun n => ∑ k in Finset.range n, (-1) ^ k / ↑(Nat.factorial k)
n k : ℕ
hk : k ∈ Finset.range (n + 1)
⊢ (-1) ^ k * ↑(Nat.ascFactorial k (n - k)) / ↑(Nat.factorial n) = (-1) ^ k / ↑(Nat.factorial k)

Next tactic:
have h_le : k ≤ n := Finset.mem_range_succ_iff.mp hk
theorem numDerangements_tendsto_inv_e :
    Tendsto (fun n => (numDerangements n : ℝ) / n.factorial) atTop (𝓝 (Real.exp (-1))) := by
  -- we show that d(n)/n! is the partial sum of exp(-1), but offset by 1.
  -- this isn't entirely obvious, since we have to ensure that asc_factorial and
  -- factorial interact in the right way, e.g., that k ≤ n always
  let s : ℕ → ℝ := fun n => ∑ k in Finset.range n, (-1 : ℝ) ^ k / k.factorial
  suffices ∀ n : ℕ, (numDerangements n : ℝ) / n.factorial = s (n + 1) by
    simp_rw [this]
    -- shift the function by 1, and then use the fact that the partial sums
    -- converge to the infinite sum
    rw [tendsto_add_atTop_iff_nat
      (f := fun n => ∑ k in Finset.range n, (-1 : ℝ) ^ k / k.factorial) 1]
    apply HasSum.tendsto_sum_nat
    -- there's no specific lemma for ℝ that ∑ x^k/k! sums to exp(x), but it's
    -- true in more general fields, so use that lemma
    rw [Real.exp_eq_exp_ℝ]
    exact expSeries_div_hasSum_exp ℝ (-1 : ℝ)
  intro n
  rw [← Int.cast_ofNat, numDerangements_sum]
  push_cast
  rw [Finset.sum_div]
  -- get down to individual terms
  refine' Finset.sum_congr (refl _) _
  intro k hk
  have h_le : k ≤ n := Finset.mem_range_succ_iff.mp hk
  

Proof state:
s : ℕ → ℝ := fun n => ∑ k in Finset.range n, (-1) ^ k / ↑(Nat.factorial k)
n k : ℕ
hk : k ∈ Finset.range (n + 1)
h_le : k ≤ n
⊢ (-1) ^ k * ↑(Nat.ascFactorial k (n - k)) / ↑(Nat.factorial n) = (-1) ^ k / ↑(Nat.factorial k)

Next tactic:
rw [Nat.ascFactorial_eq_div, add_tsub_cancel_of_le h_le]
theorem numDerangements_tendsto_inv_e :
    Tendsto (fun n => (numDerangements n : ℝ) / n.factorial) atTop (𝓝 (Real.exp (-1))) := by
  -- we show that d(n)/n! is the partial sum of exp(-1), but offset by 1.
  -- this isn't entirely obvious, since we have to ensure that asc_factorial and
  -- factorial interact in the right way, e.g., that k ≤ n always
  let s : ℕ → ℝ := fun n => ∑ k in Finset.range n, (-1 : ℝ) ^ k / k.factorial
  suffices ∀ n : ℕ, (numDerangements n : ℝ) / n.factorial = s (n + 1) by
    simp_rw [this]
    -- shift the function by 1, and then use the fact that the partial sums
    -- converge to the infinite sum
    rw [tendsto_add_atTop_iff_nat
      (f := fun n => ∑ k in Finset.range n, (-1 : ℝ) ^ k / k.factorial) 1]
    apply HasSum.tendsto_sum_nat
    -- there's no specific lemma for ℝ that ∑ x^k/k! sums to exp(x), but it's
    -- true in more general fields, so use that lemma
    rw [Real.exp_eq_exp_ℝ]
    exact expSeries_div_hasSum_exp ℝ (-1 : ℝ)
  intro n
  rw [← Int.cast_ofNat, numDerangements_sum]
  push_cast
  rw [Finset.sum_div]
  -- get down to individual terms
  refine' Finset.sum_congr (refl _) _
  intro k hk
  have h_le : k ≤ n := Finset.mem_range_succ_iff.mp hk
  rw [Nat.ascFactorial_eq_div, add_tsub_cancel_of_le h_le]
  

Proof state:
s : ℕ → ℝ := fun n => ∑ k in Finset.range n, (-1) ^ k / ↑(Nat.factorial k)
n k : ℕ
hk : k ∈ Finset.range (n + 1)
h_le : k ≤ n
⊢ (-1) ^ k * ↑(Nat.factorial n / Nat.factorial k) / ↑(Nat.factorial n) = (-1) ^ k / ↑(Nat.factorial k)

Next tactic:
push_cast [Nat.factorial_dvd_factorial h_le]
theorem numDerangements_tendsto_inv_e :
    Tendsto (fun n => (numDerangements n : ℝ) / n.factorial) atTop (𝓝 (Real.exp (-1))) := by
  -- we show that d(n)/n! is the partial sum of exp(-1), but offset by 1.
  -- this isn't entirely obvious, since we have to ensure that asc_factorial and
  -- factorial interact in the right way, e.g., that k ≤ n always
  let s : ℕ → ℝ := fun n => ∑ k in Finset.range n, (-1 : ℝ) ^ k / k.factorial
  suffices ∀ n : ℕ, (numDerangements n : ℝ) / n.factorial = s (n + 1) by
    simp_rw [this]
    -- shift the function by 1, and then use the fact that the partial sums
    -- converge to the infinite sum
    rw [tendsto_add_atTop_iff_nat
      (f := fun n => ∑ k in Finset.range n, (-1 : ℝ) ^ k / k.factorial) 1]
    apply HasSum.tendsto_sum_nat
    -- there's no specific lemma for ℝ that ∑ x^k/k! sums to exp(x), but it's
    -- true in more general fields, so use that lemma
    rw [Real.exp_eq_exp_ℝ]
    exact expSeries_div_hasSum_exp ℝ (-1 : ℝ)
  intro n
  rw [← Int.cast_ofNat, numDerangements_sum]
  push_cast
  rw [Finset.sum_div]
  -- get down to individual terms
  refine' Finset.sum_congr (refl _) _
  intro k hk
  have h_le : k ≤ n := Finset.mem_range_succ_iff.mp hk
  rw [Nat.ascFactorial_eq_div, add_tsub_cancel_of_le h_le]
  push_cast [Nat.factorial_dvd_factorial h_le]
  

Proof state:
s : ℕ → ℝ := fun n => ∑ k in Finset.range n, (-1) ^ k / ↑(Nat.factorial k)
n k : ℕ
hk : k ∈ Finset.range (n + 1)
h_le : k ≤ n
⊢ (-1) ^ k * (↑(Nat.factorial n) / ↑(Nat.factorial k)) / ↑(Nat.factorial n) = (-1) ^ k / ↑(Nat.factorial k)

Next tactic:
field_simp [Nat.factorial_ne_zero]
theorem numDerangements_tendsto_inv_e :
    Tendsto (fun n => (numDerangements n : ℝ) / n.factorial) atTop (𝓝 (Real.exp (-1))) := by
  -- we show that d(n)/n! is the partial sum of exp(-1), but offset by 1.
  -- this isn't entirely obvious, since we have to ensure that asc_factorial and
  -- factorial interact in the right way, e.g., that k ≤ n always
  let s : ℕ → ℝ := fun n => ∑ k in Finset.range n, (-1 : ℝ) ^ k / k.factorial
  suffices ∀ n : ℕ, (numDerangements n : ℝ) / n.factorial = s (n + 1) by
    simp_rw [this]
    -- shift the function by 1, and then use the fact that the partial sums
    -- converge to the infinite sum
    rw [tendsto_add_atTop_iff_nat
      (f := fun n => ∑ k in Finset.range n, (-1 : ℝ) ^ k / k.factorial) 1]
    apply HasSum.tendsto_sum_nat
    -- there's no specific lemma for ℝ that ∑ x^k/k! sums to exp(x), but it's
    -- true in more general fields, so use that lemma
    rw [Real.exp_eq_exp_ℝ]
    exact expSeries_div_hasSum_exp ℝ (-1 : ℝ)
  intro n
  rw [← Int.cast_ofNat, numDerangements_sum]
  push_cast
  rw [Finset.sum_div]
  -- get down to individual terms
  refine' Finset.sum_congr (refl _) _
  intro k hk
  have h_le : k ≤ n := Finset.mem_range_succ_iff.mp hk
  rw [Nat.ascFactorial_eq_div, add_tsub_cancel_of_le h_le]
  push_cast [Nat.factorial_dvd_factorial h_le]
  field_simp [Nat.factorial_ne_zero]
  

Proof state:
s : ℕ → ℝ := fun n => ∑ k in Finset.range n, (-1) ^ k / ↑(Nat.factorial k)
n k : ℕ
hk : k ∈ Finset.range (n + 1)
h_le : k ≤ n
⊢ (-1) ^ k * ↑(Nat.factorial n) * ↑(Nat.factorial k) = (-1) ^ k * (↑(Nat.factorial k) * ↑(Nat.factorial n))

Next tactic:
ring
theorem dite_dite_distrib_left {a : p → α} {b : ¬p → q → α} {c : ¬p → ¬q → α} :
    (dite p a fun hp ↦ dite q (b hp) (c hp)) =
      dite q (fun hq ↦ (dite p a) fun hp ↦ b hp hq) fun hq ↦ (dite p a) fun hp ↦ c hp hq := by
  

Proof state:
α : Sort u_1
p q r : Prop
inst✝¹ : Decidable p
inst✝ : Decidable q
a✝ b✝ c✝ : α
a : p → α
b : ¬p → q → α
c : ¬p → ¬q → α
⊢ (dite p a fun hp => dite q (b hp) (c hp)) = if hq : q then dite p a fun hp => b hp hq else dite p a fun hp => c hp hq

Next tactic:
split_ifs
theorem dite_dite_distrib_left {a : p → α} {b : ¬p → q → α} {c : ¬p → ¬q → α} :
    (dite p a fun hp ↦ dite q (b hp) (c hp)) =
      dite q (fun hq ↦ (dite p a) fun hp ↦ b hp hq) fun hq ↦ (dite p a) fun hp ↦ c hp hq := by
  split_ifs <;> 

Proof state:
case pos
α : Sort u_1
p q r : Prop
inst✝¹ : Decidable p
inst✝ : Decidable q
a✝ b✝ c✝ : α
a : p → α
b : ¬p → q → α
c : ¬p → ¬q → α
h✝¹ : p
h✝ : q
⊢ a h✝¹ = a h✝¹

Next tactic:
rfl
theorem dite_dite_distrib_left {a : p → α} {b : ¬p → q → α} {c : ¬p → ¬q → α} :
    (dite p a fun hp ↦ dite q (b hp) (c hp)) =
      dite q (fun hq ↦ (dite p a) fun hp ↦ b hp hq) fun hq ↦ (dite p a) fun hp ↦ c hp hq := by
  split_ifs <;> 

Proof state:
case neg
α : Sort u_1
p q r : Prop
inst✝¹ : Decidable p
inst✝ : Decidable q
a✝ b✝ c✝ : α
a : p → α
b : ¬p → q → α
c : ¬p → ¬q → α
h✝¹ : p
h✝ : ¬q
⊢ a h✝¹ = a h✝¹

Next tactic:
rfl
theorem dite_dite_distrib_left {a : p → α} {b : ¬p → q → α} {c : ¬p → ¬q → α} :
    (dite p a fun hp ↦ dite q (b hp) (c hp)) =
      dite q (fun hq ↦ (dite p a) fun hp ↦ b hp hq) fun hq ↦ (dite p a) fun hp ↦ c hp hq := by
  split_ifs <;> 

Proof state:
case pos
α : Sort u_1
p q r : Prop
inst✝¹ : Decidable p
inst✝ : Decidable q
a✝ b✝ c✝ : α
a : p → α
b : ¬p → q → α
c : ¬p → ¬q → α
h✝¹ : ¬p
h✝ : q
⊢ b h✝¹ h✝ = b h✝¹ (_ : q)

Next tactic:
rfl
theorem dite_dite_distrib_left {a : p → α} {b : ¬p → q → α} {c : ¬p → ¬q → α} :
    (dite p a fun hp ↦ dite q (b hp) (c hp)) =
      dite q (fun hq ↦ (dite p a) fun hp ↦ b hp hq) fun hq ↦ (dite p a) fun hp ↦ c hp hq := by
  split_ifs <;> 

Proof state:
case neg
α : Sort u_1
p q r : Prop
inst✝¹ : Decidable p
inst✝ : Decidable q
a✝ b✝ c✝ : α
a : p → α
b : ¬p → q → α
c : ¬p → ¬q → α
h✝¹ : ¬p
h✝ : ¬q
⊢ c h✝¹ h✝ = c h✝¹ (_ : ¬q)

Next tactic:
rfl
theorem dite_dite_distrib_right {a : p → q → α} {b : p → ¬q → α} {c : ¬p → α} :
    dite p (fun hp ↦ dite q (a hp) (b hp)) c =
      dite q (fun hq ↦ dite p (fun hp ↦ a hp hq) c) fun hq ↦ dite p (fun hp ↦ b hp hq) c := by
  

Proof state:
α : Sort u_1
p q r : Prop
inst✝¹ : Decidable p
inst✝ : Decidable q
a✝ b✝ c✝ : α
a : p → q → α
b : p → ¬q → α
c : ¬p → α
⊢ dite p (fun hp => dite q (a hp) (b hp)) c =
    if hq : q then dite p (fun hp => a hp hq) c else dite p (fun hp => b hp hq) c

Next tactic:
split_ifs
theorem dite_dite_distrib_right {a : p → q → α} {b : p → ¬q → α} {c : ¬p → α} :
    dite p (fun hp ↦ dite q (a hp) (b hp)) c =
      dite q (fun hq ↦ dite p (fun hp ↦ a hp hq) c) fun hq ↦ dite p (fun hp ↦ b hp hq) c := by
  split_ifs <;> 

Proof state:
case pos
α : Sort u_1
p q r : Prop
inst✝¹ : Decidable p
inst✝ : Decidable q
a✝ b✝ c✝ : α
a : p → q → α
b : p → ¬q → α
c : ¬p → α
h✝¹ : p
h✝ : q
⊢ a h✝¹ h✝ = a h✝¹ (_ : q)

Next tactic:
rfl
theorem dite_dite_distrib_right {a : p → q → α} {b : p → ¬q → α} {c : ¬p → α} :
    dite p (fun hp ↦ dite q (a hp) (b hp)) c =
      dite q (fun hq ↦ dite p (fun hp ↦ a hp hq) c) fun hq ↦ dite p (fun hp ↦ b hp hq) c := by
  split_ifs <;> 

Proof state:
case neg
α : Sort u_1
p q r : Prop
inst✝¹ : Decidable p
inst✝ : Decidable q
a✝ b✝ c✝ : α
a : p → q → α
b : p → ¬q → α
c : ¬p → α
h✝¹ : p
h✝ : ¬q
⊢ b h✝¹ h✝ = b h✝¹ (_ : ¬q)

Next tactic:
rfl
theorem dite_dite_distrib_right {a : p → q → α} {b : p → ¬q → α} {c : ¬p → α} :
    dite p (fun hp ↦ dite q (a hp) (b hp)) c =
      dite q (fun hq ↦ dite p (fun hp ↦ a hp hq) c) fun hq ↦ dite p (fun hp ↦ b hp hq) c := by
  split_ifs <;> 

Proof state:
case pos
α : Sort u_1
p q r : Prop
inst✝¹ : Decidable p
inst✝ : Decidable q
a✝ b✝ c✝ : α
a : p → q → α
b : p → ¬q → α
c : ¬p → α
h✝¹ : ¬p
h✝ : q
⊢ c h✝¹ = c h✝¹

Next tactic:
rfl
theorem dite_dite_distrib_right {a : p → q → α} {b : p → ¬q → α} {c : ¬p → α} :
    dite p (fun hp ↦ dite q (a hp) (b hp)) c =
      dite q (fun hq ↦ dite p (fun hp ↦ a hp hq) c) fun hq ↦ dite p (fun hp ↦ b hp hq) c := by
  split_ifs <;> 

Proof state:
case neg
α : Sort u_1
p q r : Prop
inst✝¹ : Decidable p
inst✝ : Decidable q
a✝ b✝ c✝ : α
a : p → q → α
b : p → ¬q → α
c : ¬p → α
h✝¹ : ¬p
h✝ : ¬q
⊢ c h✝¹ = c h✝¹

Next tactic:
rfl
lemma Prop.forall {f : Prop → Prop} : (∀ p, f p) ↔ f True ∧ f False :=
  ⟨fun h ↦ ⟨h _, h _⟩, by 

Proof state:
α : Sort u_1
p q r : Prop
inst✝¹ : Decidable p
inst✝ : Decidable q
a b c : α
f : Prop → Prop
⊢ f True ∧ f False → ∀ (p : Prop), f p

Next tactic:
rintro ⟨h₁, h₀⟩ p
lemma Prop.forall {f : Prop → Prop} : (∀ p, f p) ↔ f True ∧ f False :=
  ⟨fun h ↦ ⟨h _, h _⟩, by rintro ⟨h₁, h₀⟩ p; 

Proof state:
case intro
α : Sort u_1
p✝ q r : Prop
inst✝¹ : Decidable p✝
inst✝ : Decidable q
a b c : α
f : Prop → Prop
h₁ : f True
h₀ : f False
p : Prop
⊢ f p

Next tactic:
by_cases hp : p
lemma Prop.forall {f : Prop → Prop} : (∀ p, f p) ↔ f True ∧ f False :=
  ⟨fun h ↦ ⟨h _, h _⟩, by rintro ⟨h₁, h₀⟩ p; by_cases hp : p <;> 

Proof state:
case pos
α : Sort u_1
p✝ q r : Prop
inst✝¹ : Decidable p✝
inst✝ : Decidable q
a b c : α
f : Prop → Prop
h₁ : f True
h₀ : f False
p : Prop
hp : p
⊢ f p

Next tactic:
simp only [hp]
lemma Prop.forall {f : Prop → Prop} : (∀ p, f p) ↔ f True ∧ f False :=
  ⟨fun h ↦ ⟨h _, h _⟩, by rintro ⟨h₁, h₀⟩ p; by_cases hp : p <;> 

Proof state:
case neg
α : Sort u_1
p✝ q r : Prop
inst✝¹ : Decidable p✝
inst✝ : Decidable q
a b c : α
f : Prop → Prop
h₁ : f True
h₀ : f False
p : Prop
hp : ¬p
⊢ f p

Next tactic:
simp only [hp]
lemma Prop.forall {f : Prop → Prop} : (∀ p, f p) ↔ f True ∧ f False :=
  ⟨fun h ↦ ⟨h _, h _⟩, by rintro ⟨h₁, h₀⟩ p; by_cases hp : p <;> simp only [hp] <;> 

Proof state:
case pos
α : Sort u_1
p✝ q r : Prop
inst✝¹ : Decidable p✝
inst✝ : Decidable q
a b c : α
f : Prop → Prop
h₁ : f True
h₀ : f False
p : Prop
hp : p
⊢ f True

Next tactic:
assumption
lemma Prop.forall {f : Prop → Prop} : (∀ p, f p) ↔ f True ∧ f False :=
  ⟨fun h ↦ ⟨h _, h _⟩, by rintro ⟨h₁, h₀⟩ p; by_cases hp : p <;> simp only [hp] <;> 

Proof state:
case neg
α : Sort u_1
p✝ q r : Prop
inst✝¹ : Decidable p✝
inst✝ : Decidable q
a b c : α
f : Prop → Prop
h₁ : f True
h₀ : f False
p : Prop
hp : ¬p
⊢ f False

Next tactic:
assumption
lemma Prop.exists {f : Prop → Prop} : (∃ p, f p) ↔ f True ∨ f False :=
  ⟨fun ⟨p, h⟩ ↦ by 

Proof state:
α : Sort u_1
p✝ q r : Prop
inst✝¹ : Decidable p✝
inst✝ : Decidable q
a b c : α
f : Prop → Prop
x✝ : ∃ p, f p
p : Prop
h : f p
⊢ f True ∨ f False

Next tactic:
refine' (em p).imp _ _
lemma Prop.exists {f : Prop → Prop} : (∃ p, f p) ↔ f True ∨ f False :=
  ⟨fun ⟨p, h⟩ ↦ by refine' (em p).imp _ _ <;> 

Proof state:
case refine'_1
α : Sort u_1
p✝ q r : Prop
inst✝¹ : Decidable p✝
inst✝ : Decidable q
a b c : α
f : Prop → Prop
x✝ : ∃ p, f p
p : Prop
h : f p
⊢ p → f True

Next tactic:
intro H
lemma Prop.exists {f : Prop → Prop} : (∃ p, f p) ↔ f True ∨ f False :=
  ⟨fun ⟨p, h⟩ ↦ by refine' (em p).imp _ _ <;> 

Proof state:
case refine'_2
α : Sort u_1
p✝ q r : Prop
inst✝¹ : Decidable p✝
inst✝ : Decidable q
a b c : α
f : Prop → Prop
x✝ : ∃ p, f p
p : Prop
h : f p
⊢ ¬p → f False

Next tactic:
intro H
lemma Prop.exists {f : Prop → Prop} : (∃ p, f p) ↔ f True ∨ f False :=
  ⟨fun ⟨p, h⟩ ↦ by refine' (em p).imp _ _ <;> intro H <;> 

Proof state:
case refine'_1
α : Sort u_1
p✝ q r : Prop
inst✝¹ : Decidable p✝
inst✝ : Decidable q
a b c : α
f : Prop → Prop
x✝ : ∃ p, f p
p : Prop
h : f p
H : p
⊢ f True

Next tactic:
convert h
lemma Prop.exists {f : Prop → Prop} : (∃ p, f p) ↔ f True ∨ f False :=
  ⟨fun ⟨p, h⟩ ↦ by refine' (em p).imp _ _ <;> intro H <;> 

Proof state:
case refine'_2
α : Sort u_1
p✝ q r : Prop
inst✝¹ : Decidable p✝
inst✝ : Decidable q
a b c : α
f : Prop → Prop
x✝ : ∃ p, f p
p : Prop
h : f p
H : ¬p
⊢ f False

Next tactic:
convert h
lemma Prop.exists {f : Prop → Prop} : (∃ p, f p) ↔ f True ∨ f False :=
  ⟨fun ⟨p, h⟩ ↦ by refine' (em p).imp _ _ <;> intro H <;> convert h <;> 

Proof state:
case h.e'_1.a
α : Sort u_1
p✝ q r : Prop
inst✝¹ : Decidable p✝
inst✝ : Decidable q
a b c : α
f : Prop → Prop
x✝ : ∃ p, f p
p : Prop
h : f p
H : p
⊢ True ↔ p

Next tactic:
simp [H]
lemma Prop.exists {f : Prop → Prop} : (∃ p, f p) ↔ f True ∨ f False :=
  ⟨fun ⟨p, h⟩ ↦ by refine' (em p).imp _ _ <;> intro H <;> convert h <;> 

Proof state:
case h.e'_1.a
α : Sort u_1
p✝ q r : Prop
inst✝¹ : Decidable p✝
inst✝ : Decidable q
a b c : α
f : Prop → Prop
x✝ : ∃ p, f p
p : Prop
h : f p
H : ¬p
⊢ False ↔ p

Next tactic:
simp [H]
lemma Prop.exists {f : Prop → Prop} : (∃ p, f p) ↔ f True ∨ f False :=
  ⟨fun ⟨p, h⟩ ↦ by refine' (em p).imp _ _ <;> intro H <;> convert h <;> simp [H],
    by 

Proof state:
α : Sort u_1
p q r : Prop
inst✝¹ : Decidable p
inst✝ : Decidable q
a b c : α
f : Prop → Prop
⊢ f True ∨ f False → ∃ p, f p

Next tactic:
rintro (h | h)
lemma Prop.exists {f : Prop → Prop} : (∃ p, f p) ↔ f True ∨ f False :=
  ⟨fun ⟨p, h⟩ ↦ by refine' (em p).imp _ _ <;> intro H <;> convert h <;> simp [H],
    by rintro (h | h) <;> 

Proof state:
case inl
α : Sort u_1
p q r : Prop
inst✝¹ : Decidable p
inst✝ : Decidable q
a b c : α
f : Prop → Prop
h : f True
⊢ ∃ p, f p

Next tactic:
exact ⟨_, h⟩
lemma Prop.exists {f : Prop → Prop} : (∃ p, f p) ↔ f True ∨ f False :=
  ⟨fun ⟨p, h⟩ ↦ by refine' (em p).imp _ _ <;> intro H <;> convert h <;> simp [H],
    by rintro (h | h) <;> 

Proof state:
case inr
α : Sort u_1
p q r : Prop
inst✝¹ : Decidable p
inst✝ : Decidable q
a b c : α
f : Prop → Prop
h : f False
⊢ ∃ p, f p

Next tactic:
exact ⟨_, h⟩
instance : Abelian Cᵒᵖ := by
  -- porting note: priorities of `Abelian.has_kernels` and `Abelian.has_cokernels` have
  -- been set to 90 in `Abelian.Basic` in order to prevent a timeout here
  

Proof state:
C : Type u_1
inst✝¹ : Category.{?u.28, u_1} C
inst✝ : Abelian C
⊢ Abelian Cᵒᵖ

Next tactic:
exact {
    normalMonoOfMono := fun f => normalMonoOfNormalEpiUnop _ (normalEpiOfEpi f.unop)
    normalEpiOfEpi := fun f => normalEpiOfNormalMonoUnop _ (normalMonoOfMono f.unop) }
/-- The kernel of `f.op` is the opposite of `cokernel f`. -/
@[simps]
def kernelOpUnop : (kernel f.op).unop ≅ cokernel f where
  hom := (kernel.lift f.op (cokernel.π f).op <| by 

Proof state:
C : Type u_1
inst✝¹ : Category.{?u.2298, u_1} C
inst✝ : Abelian C
X Y : C
f : X ⟶ Y
A B : Cᵒᵖ
g : A ⟶ B
⊢ (cokernel.π f).op ≫ f.op = 0

Next tactic:
simp [← op_comp]
/-- The kernel of `f.op` is the opposite of `cokernel f`. -/
@[simps]
def kernelOpUnop : (kernel f.op).unop ≅ cokernel f where
  hom := (kernel.lift f.op (cokernel.π f).op <| by simp [← op_comp]).unop
  inv :=
    cokernel.desc f (kernel.ι f.op).unop <| by
      

Proof state:
C : Type u_1
inst✝¹ : Category.{?u.2298, u_1} C
inst✝ : Abelian C
X Y : C
f : X ⟶ Y
A B : Cᵒᵖ
g : A ⟶ B
⊢ f ≫ (kernel.ι f.op).unop = 0

Next tactic:
rw [← f.unop_op, ← unop_comp, f.unop_op]
/-- The kernel of `f.op` is the opposite of `cokernel f`. -/
@[simps]
def kernelOpUnop : (kernel f.op).unop ≅ cokernel f where
  hom := (kernel.lift f.op (cokernel.π f).op <| by simp [← op_comp]).unop
  inv :=
    cokernel.desc f (kernel.ι f.op).unop <| by
      rw [← f.unop_op, ← unop_comp, f.unop_op]
      

Proof state:
C : Type u_1
inst✝¹ : Category.{?u.2298, u_1} C
inst✝ : Abelian C
X Y : C
f : X ⟶ Y
A B : Cᵒᵖ
g : A ⟶ B
⊢ (kernel.ι f.op ≫ f.op).unop = 0

Next tactic:
simp
/-- The kernel of `f.op` is the opposite of `cokernel f`. -/
@[simps]
def kernelOpUnop : (kernel f.op).unop ≅ cokernel f where
  hom := (kernel.lift f.op (cokernel.π f).op <| by simp [← op_comp]).unop
  inv :=
    cokernel.desc f (kernel.ι f.op).unop <| by
      rw [← f.unop_op, ← unop_comp, f.unop_op]
      simp
  hom_inv_id := by
    

Proof state:
C : Type u_1
inst✝¹ : Category.{?u.2298, u_1} C
inst✝ : Abelian C
X Y : C
f : X ⟶ Y
A B : Cᵒᵖ
g : A ⟶ B
⊢ (kernel.lift f.op (cokernel.π f).op (_ : (f ≫ cokernel.π f).op = 0)).unop ≫
      cokernel.desc f (kernel.ι f.op).unop (_ : f ≫ (kernel.ι f.op).unop = 0) =
    𝟙 (kernel f.op).unop

Next tactic:
rw [← unop_id, ← (cokernel.desc f _ _).unop_op, ← unop_comp]
/-- The kernel of `f.op` is the opposite of `cokernel f`. -/
@[simps]
def kernelOpUnop : (kernel f.op).unop ≅ cokernel f where
  hom := (kernel.lift f.op (cokernel.π f).op <| by simp [← op_comp]).unop
  inv :=
    cokernel.desc f (kernel.ι f.op).unop <| by
      rw [← f.unop_op, ← unop_comp, f.unop_op]
      simp
  hom_inv_id := by
    rw [← unop_id, ← (cokernel.desc f _ _).unop_op, ← unop_comp]
    

Proof state:
C : Type u_1
inst✝¹ : Category.{?u.2298, u_1} C
inst✝ : Abelian C
X Y : C
f : X ⟶ Y
A B : Cᵒᵖ
g : A ⟶ B
⊢ ((cokernel.desc f (kernel.ι f.op).unop (_ : f ≫ (kernel.ι f.op).unop = 0)).op ≫
        kernel.lift f.op (cokernel.π f).op (_ : (f ≫ cokernel.π f).op = 0)).unop =
    (𝟙 (kernel f.op)).unop

Next tactic:
congr 1
/-- The kernel of `f.op` is the opposite of `cokernel f`. -/
@[simps]
def kernelOpUnop : (kernel f.op).unop ≅ cokernel f where
  hom := (kernel.lift f.op (cokernel.π f).op <| by simp [← op_comp]).unop
  inv :=
    cokernel.desc f (kernel.ι f.op).unop <| by
      rw [← f.unop_op, ← unop_comp, f.unop_op]
      simp
  hom_inv_id := by
    rw [← unop_id, ← (cokernel.desc f _ _).unop_op, ← unop_comp]
    congr 1
    

Proof state:
case e_f
C : Type u_1
inst✝¹ : Category.{?u.2298, u_1} C
inst✝ : Abelian C
X Y : C
f : X ⟶ Y
A B : Cᵒᵖ
g : A ⟶ B
⊢ (cokernel.desc f (kernel.ι f.op).unop (_ : f ≫ (kernel.ι f.op).unop = 0)).op ≫
      kernel.lift f.op (cokernel.π f).op (_ : (f ≫ cokernel.π f).op = 0) =
    𝟙 (kernel f.op)

Next tactic:
ext
/-- The kernel of `f.op` is the opposite of `cokernel f`. -/
@[simps]
def kernelOpUnop : (kernel f.op).unop ≅ cokernel f where
  hom := (kernel.lift f.op (cokernel.π f).op <| by simp [← op_comp]).unop
  inv :=
    cokernel.desc f (kernel.ι f.op).unop <| by
      rw [← f.unop_op, ← unop_comp, f.unop_op]
      simp
  hom_inv_id := by
    rw [← unop_id, ← (cokernel.desc f _ _).unop_op, ← unop_comp]
    congr 1
    ext
    

Proof state:
case e_f.h
C : Type u_1
inst✝¹ : Category.{?u.2298, u_1} C
inst✝ : Abelian C
X Y : C
f : X ⟶ Y
A B : Cᵒᵖ
g : A ⟶ B
⊢ ((cokernel.desc f (kernel.ι f.op).unop (_ : f ≫ (kernel.ι f.op).unop = 0)).op ≫
        kernel.lift f.op (cokernel.π f).op (_ : (f ≫ cokernel.π f).op = 0)) ≫
      equalizer.ι f.op 0 =
    𝟙 (kernel f.op) ≫ equalizer.ι f.op 0

Next tactic:
simp [← op_comp]
/-- The kernel of `f.op` is the opposite of `cokernel f`. -/
@[simps]
def kernelOpUnop : (kernel f.op).unop ≅ cokernel f where
  hom := (kernel.lift f.op (cokernel.π f).op <| by simp [← op_comp]).unop
  inv :=
    cokernel.desc f (kernel.ι f.op).unop <| by
      rw [← f.unop_op, ← unop_comp, f.unop_op]
      simp
  hom_inv_id := by
    rw [← unop_id, ← (cokernel.desc f _ _).unop_op, ← unop_comp]
    congr 1
    ext
    simp [← op_comp]
  inv_hom_id := by
    

Proof state:
C : Type u_1
inst✝¹ : Category.{?u.2298, u_1} C
inst✝ : Abelian C
X Y : C
f : X ⟶ Y
A B : Cᵒᵖ
g : A ⟶ B
⊢ cokernel.desc f (kernel.ι f.op).unop (_ : f ≫ (kernel.ι f.op).unop = 0) ≫
      (kernel.lift f.op (cokernel.π f).op (_ : (f ≫ cokernel.π f).op = 0)).unop =
    𝟙 (cokernel f)

Next tactic:
ext
/-- The kernel of `f.op` is the opposite of `cokernel f`. -/
@[simps]
def kernelOpUnop : (kernel f.op).unop ≅ cokernel f where
  hom := (kernel.lift f.op (cokernel.π f).op <| by simp [← op_comp]).unop
  inv :=
    cokernel.desc f (kernel.ι f.op).unop <| by
      rw [← f.unop_op, ← unop_comp, f.unop_op]
      simp
  hom_inv_id := by
    rw [← unop_id, ← (cokernel.desc f _ _).unop_op, ← unop_comp]
    congr 1
    ext
    simp [← op_comp]
  inv_hom_id := by
    ext
    

Proof state:
case h
C : Type u_1
inst✝¹ : Category.{?u.2298, u_1} C
inst✝ : Abelian C
X Y : C
f : X ⟶ Y
A B : Cᵒᵖ
g : A ⟶ B
⊢ coequalizer.π f 0 ≫
      cokernel.desc f (kernel.ι f.op).unop (_ : f ≫ (kernel.ι f.op).unop = 0) ≫
        (kernel.lift f.op (cokernel.π f).op (_ : (f ≫ cokernel.π f).op = 0)).unop =
    coequalizer.π f 0 ≫ 𝟙 (cokernel f)

Next tactic:
simp [← unop_comp]
/-- The cokernel of `f.op` is the opposite of `kernel f`. -/
@[simps]
def cokernelOpUnop : (cokernel f.op).unop ≅ kernel f where
  hom :=
    kernel.lift f (cokernel.π f.op).unop <| by
      

Proof state:
C : Type u_1
inst✝¹ : Category.{?u.12173, u_1} C
inst✝ : Abelian C
X Y : C
f : X ⟶ Y
A B : Cᵒᵖ
g : A ⟶ B
⊢ (cokernel.π f.op).unop ≫ f = 0

Next tactic:
rw [← f.unop_op, ← unop_comp, f.unop_op]