l3lab/ntp-mathlib huggingface dataset:
/-- The Grundy value of the sum of two nim games with natural numbers of piles equals their bitwise
xor. -/
@[simp]
theorem grundyValue_nim_add_nim (n m : ℕ) :
    grundyValue (nim.{u} n + nim.{u} m) = n ^^^ m := by
  -- We do strong induction on both variables.
  induction' n using Nat.strong_induction_on with n hn generalizing m
  induction' m using Nat.strong_induction_on with m hm
  rw [grundyValue_eq_mex_left]
  refine (Ordinal.mex_le_of_ne.{u, u} fun i => ?_).antisymm
    (Ordinal.le_mex_of_forall fun ou hu => ?_)
  -- The Grundy value `n ^^^ m` can't be reached by left moves.
  · apply leftMoves_add_cases i <;>
      · -- A left move leaves us with a Grundy value of `k ^^^ m` for `k < n`, or
        -- `n ^^^ k` for `k < m`.
        refine' fun a => leftMovesNimRecOn a fun ok hk => _
        obtain ⟨k, rfl⟩ := Ordinal.lt_omega.1 (hk.trans (Ordinal.nat_lt_omega _))
        simp only [add_moveLeft_inl, add_moveLeft_inr, moveLeft_nim', Equiv.symm_apply_apply]
        -- The inequality follows from injectivity.
        rw [nat_cast_lt] at hk
        first
        | rw [hn _ hk]
        | rw [hm _ hk]
        refine' fun h => hk.ne _
        rw [Ordinal.nat_cast_inj] at h
        first
        | rwa [Nat.xor_left_inj] at h
        | rwa [Nat.xor_right_inj] at h
  -- Every other smaller Grundy value can be reached by left moves.
  · -- If `u < m ^^^ n`, then either `u ^^^ n < m` or `u ^^^ m < n`.
    obtain ⟨u, rfl⟩ := Ordinal.lt_omega.1 (hu.trans (Ordinal.nat_lt_omega _))
    replace hu := Ordinal.nat_cast_lt.1 hu
    cases' Nat.lt_xor_cases hu with h h
    -- In the first case, reducing the `m` pile to `u ^^^ n` gives the desired Grundy value.
    · 

Proof state:
case h.h.refine_2.intro.inl
n : ℕ
hn : ∀ m < n, ∀ (m_1 : ℕ), grundyValue (nim ↑m + nim ↑m_1) = ↑(m ^^^ m_1)
m : ℕ
hm : ∀ m_1 < m, grundyValue (nim ↑n + nim ↑m_1) = ↑(n ^^^ m_1)
u : ℕ
hu : u < n ^^^ m
h : u ^^^ m < n
⊢ ∃ i, grundyValue (moveLeft (nim ↑n + nim ↑m) i) = ↑u

Next tactic:
refine' ⟨toLeftMovesAdd (Sum.inl <| toLeftMovesNim ⟨_, Ordinal.nat_cast_lt.2 h⟩), _⟩
/-- The Grundy value of the sum of two nim games with natural numbers of piles equals their bitwise
xor. -/
@[simp]
theorem grundyValue_nim_add_nim (n m : ℕ) :
    grundyValue (nim.{u} n + nim.{u} m) = n ^^^ m := by
  -- We do strong induction on both variables.
  induction' n using Nat.strong_induction_on with n hn generalizing m
  induction' m using Nat.strong_induction_on with m hm
  rw [grundyValue_eq_mex_left]
  refine (Ordinal.mex_le_of_ne.{u, u} fun i => ?_).antisymm
    (Ordinal.le_mex_of_forall fun ou hu => ?_)
  -- The Grundy value `n ^^^ m` can't be reached by left moves.
  · apply leftMoves_add_cases i <;>
      · -- A left move leaves us with a Grundy value of `k ^^^ m` for `k < n`, or
        -- `n ^^^ k` for `k < m`.
        refine' fun a => leftMovesNimRecOn a fun ok hk => _
        obtain ⟨k, rfl⟩ := Ordinal.lt_omega.1 (hk.trans (Ordinal.nat_lt_omega _))
        simp only [add_moveLeft_inl, add_moveLeft_inr, moveLeft_nim', Equiv.symm_apply_apply]
        -- The inequality follows from injectivity.
        rw [nat_cast_lt] at hk
        first
        | rw [hn _ hk]
        | rw [hm _ hk]
        refine' fun h => hk.ne _
        rw [Ordinal.nat_cast_inj] at h
        first
        | rwa [Nat.xor_left_inj] at h
        | rwa [Nat.xor_right_inj] at h
  -- Every other smaller Grundy value can be reached by left moves.
  · -- If `u < m ^^^ n`, then either `u ^^^ n < m` or `u ^^^ m < n`.
    obtain ⟨u, rfl⟩ := Ordinal.lt_omega.1 (hu.trans (Ordinal.nat_lt_omega _))
    replace hu := Ordinal.nat_cast_lt.1 hu
    cases' Nat.lt_xor_cases hu with h h
    -- In the first case, reducing the `m` pile to `u ^^^ n` gives the desired Grundy value.
    · refine' ⟨toLeftMovesAdd (Sum.inl <| toLeftMovesNim ⟨_, Ordinal.nat_cast_lt.2 h⟩), _⟩
      

Proof state:
case h.h.refine_2.intro.inl
n : ℕ
hn : ∀ m < n, ∀ (m_1 : ℕ), grundyValue (nim ↑m + nim ↑m_1) = ↑(m ^^^ m_1)
m : ℕ
hm : ∀ m_1 < m, grundyValue (nim ↑n + nim ↑m_1) = ↑(n ^^^ m_1)
u : ℕ
hu : u < n ^^^ m
h : u ^^^ m < n
⊢ grundyValue
      (moveLeft (nim ↑n + nim ↑m)
        (toLeftMovesAdd (Sum.inl (toLeftMovesNim { val := ↑(u ^^^ m), property := (_ : ↑(u ^^^ m) < ↑n) })))) =
    ↑u

Next tactic:
simp [Nat.lxor_cancel_right, hn _ h]
/-- The Grundy value of the sum of two nim games with natural numbers of piles equals their bitwise
xor. -/
@[simp]
theorem grundyValue_nim_add_nim (n m : ℕ) :
    grundyValue (nim.{u} n + nim.{u} m) = n ^^^ m := by
  -- We do strong induction on both variables.
  induction' n using Nat.strong_induction_on with n hn generalizing m
  induction' m using Nat.strong_induction_on with m hm
  rw [grundyValue_eq_mex_left]
  refine (Ordinal.mex_le_of_ne.{u, u} fun i => ?_).antisymm
    (Ordinal.le_mex_of_forall fun ou hu => ?_)
  -- The Grundy value `n ^^^ m` can't be reached by left moves.
  · apply leftMoves_add_cases i <;>
      · -- A left move leaves us with a Grundy value of `k ^^^ m` for `k < n`, or
        -- `n ^^^ k` for `k < m`.
        refine' fun a => leftMovesNimRecOn a fun ok hk => _
        obtain ⟨k, rfl⟩ := Ordinal.lt_omega.1 (hk.trans (Ordinal.nat_lt_omega _))
        simp only [add_moveLeft_inl, add_moveLeft_inr, moveLeft_nim', Equiv.symm_apply_apply]
        -- The inequality follows from injectivity.
        rw [nat_cast_lt] at hk
        first
        | rw [hn _ hk]
        | rw [hm _ hk]
        refine' fun h => hk.ne _
        rw [Ordinal.nat_cast_inj] at h
        first
        | rwa [Nat.xor_left_inj] at h
        | rwa [Nat.xor_right_inj] at h
  -- Every other smaller Grundy value can be reached by left moves.
  · -- If `u < m ^^^ n`, then either `u ^^^ n < m` or `u ^^^ m < n`.
    obtain ⟨u, rfl⟩ := Ordinal.lt_omega.1 (hu.trans (Ordinal.nat_lt_omega _))
    replace hu := Ordinal.nat_cast_lt.1 hu
    cases' Nat.lt_xor_cases hu with h h
    -- In the first case, reducing the `m` pile to `u ^^^ n` gives the desired Grundy value.
    · refine' ⟨toLeftMovesAdd (Sum.inl <| toLeftMovesNim ⟨_, Ordinal.nat_cast_lt.2 h⟩), _⟩
      simp [Nat.lxor_cancel_right, hn _ h]
    -- In the second case, reducing the `n` pile to `u ^^^ m` gives the desired Grundy value.
    · 

Proof state:
case h.h.refine_2.intro.inr
n : ℕ
hn : ∀ m < n, ∀ (m_1 : ℕ), grundyValue (nim ↑m + nim ↑m_1) = ↑(m ^^^ m_1)
m : ℕ
hm : ∀ m_1 < m, grundyValue (nim ↑n + nim ↑m_1) = ↑(n ^^^ m_1)
u : ℕ
hu : u < n ^^^ m
h : u ^^^ n < m
⊢ ∃ i, grundyValue (moveLeft (nim ↑n + nim ↑m) i) = ↑u

Next tactic:
refine' ⟨toLeftMovesAdd (Sum.inr <| toLeftMovesNim ⟨_, Ordinal.nat_cast_lt.2 h⟩), _⟩
/-- The Grundy value of the sum of two nim games with natural numbers of piles equals their bitwise
xor. -/
@[simp]
theorem grundyValue_nim_add_nim (n m : ℕ) :
    grundyValue (nim.{u} n + nim.{u} m) = n ^^^ m := by
  -- We do strong induction on both variables.
  induction' n using Nat.strong_induction_on with n hn generalizing m
  induction' m using Nat.strong_induction_on with m hm
  rw [grundyValue_eq_mex_left]
  refine (Ordinal.mex_le_of_ne.{u, u} fun i => ?_).antisymm
    (Ordinal.le_mex_of_forall fun ou hu => ?_)
  -- The Grundy value `n ^^^ m` can't be reached by left moves.
  · apply leftMoves_add_cases i <;>
      · -- A left move leaves us with a Grundy value of `k ^^^ m` for `k < n`, or
        -- `n ^^^ k` for `k < m`.
        refine' fun a => leftMovesNimRecOn a fun ok hk => _
        obtain ⟨k, rfl⟩ := Ordinal.lt_omega.1 (hk.trans (Ordinal.nat_lt_omega _))
        simp only [add_moveLeft_inl, add_moveLeft_inr, moveLeft_nim', Equiv.symm_apply_apply]
        -- The inequality follows from injectivity.
        rw [nat_cast_lt] at hk
        first
        | rw [hn _ hk]
        | rw [hm _ hk]
        refine' fun h => hk.ne _
        rw [Ordinal.nat_cast_inj] at h
        first
        | rwa [Nat.xor_left_inj] at h
        | rwa [Nat.xor_right_inj] at h
  -- Every other smaller Grundy value can be reached by left moves.
  · -- If `u < m ^^^ n`, then either `u ^^^ n < m` or `u ^^^ m < n`.
    obtain ⟨u, rfl⟩ := Ordinal.lt_omega.1 (hu.trans (Ordinal.nat_lt_omega _))
    replace hu := Ordinal.nat_cast_lt.1 hu
    cases' Nat.lt_xor_cases hu with h h
    -- In the first case, reducing the `m` pile to `u ^^^ n` gives the desired Grundy value.
    · refine' ⟨toLeftMovesAdd (Sum.inl <| toLeftMovesNim ⟨_, Ordinal.nat_cast_lt.2 h⟩), _⟩
      simp [Nat.lxor_cancel_right, hn _ h]
    -- In the second case, reducing the `n` pile to `u ^^^ m` gives the desired Grundy value.
    · refine' ⟨toLeftMovesAdd (Sum.inr <| toLeftMovesNim ⟨_, Ordinal.nat_cast_lt.2 h⟩), _⟩
      

Proof state:
case h.h.refine_2.intro.inr
n : ℕ
hn : ∀ m < n, ∀ (m_1 : ℕ), grundyValue (nim ↑m + nim ↑m_1) = ↑(m ^^^ m_1)
m : ℕ
hm : ∀ m_1 < m, grundyValue (nim ↑n + nim ↑m_1) = ↑(n ^^^ m_1)
u : ℕ
hu : u < n ^^^ m
h : u ^^^ n < m
⊢ grundyValue
      (moveLeft (nim ↑n + nim ↑m)
        (toLeftMovesAdd (Sum.inr (toLeftMovesNim { val := ↑(u ^^^ n), property := (_ : ↑(u ^^^ n) < ↑m) })))) =
    ↑u

Next tactic:
have : n ^^^ (u ^^^ n) = u
/-- The Grundy value of the sum of two nim games with natural numbers of piles equals their bitwise
xor. -/
@[simp]
theorem grundyValue_nim_add_nim (n m : ℕ) :
    grundyValue (nim.{u} n + nim.{u} m) = n ^^^ m := by
  -- We do strong induction on both variables.
  induction' n using Nat.strong_induction_on with n hn generalizing m
  induction' m using Nat.strong_induction_on with m hm
  rw [grundyValue_eq_mex_left]
  refine (Ordinal.mex_le_of_ne.{u, u} fun i => ?_).antisymm
    (Ordinal.le_mex_of_forall fun ou hu => ?_)
  -- The Grundy value `n ^^^ m` can't be reached by left moves.
  · apply leftMoves_add_cases i <;>
      · -- A left move leaves us with a Grundy value of `k ^^^ m` for `k < n`, or
        -- `n ^^^ k` for `k < m`.
        refine' fun a => leftMovesNimRecOn a fun ok hk => _
        obtain ⟨k, rfl⟩ := Ordinal.lt_omega.1 (hk.trans (Ordinal.nat_lt_omega _))
        simp only [add_moveLeft_inl, add_moveLeft_inr, moveLeft_nim', Equiv.symm_apply_apply]
        -- The inequality follows from injectivity.
        rw [nat_cast_lt] at hk
        first
        | rw [hn _ hk]
        | rw [hm _ hk]
        refine' fun h => hk.ne _
        rw [Ordinal.nat_cast_inj] at h
        first
        | rwa [Nat.xor_left_inj] at h
        | rwa [Nat.xor_right_inj] at h
  -- Every other smaller Grundy value can be reached by left moves.
  · -- If `u < m ^^^ n`, then either `u ^^^ n < m` or `u ^^^ m < n`.
    obtain ⟨u, rfl⟩ := Ordinal.lt_omega.1 (hu.trans (Ordinal.nat_lt_omega _))
    replace hu := Ordinal.nat_cast_lt.1 hu
    cases' Nat.lt_xor_cases hu with h h
    -- In the first case, reducing the `m` pile to `u ^^^ n` gives the desired Grundy value.
    · refine' ⟨toLeftMovesAdd (Sum.inl <| toLeftMovesNim ⟨_, Ordinal.nat_cast_lt.2 h⟩), _⟩
      simp [Nat.lxor_cancel_right, hn _ h]
    -- In the second case, reducing the `n` pile to `u ^^^ m` gives the desired Grundy value.
    · refine' ⟨toLeftMovesAdd (Sum.inr <| toLeftMovesNim ⟨_, Ordinal.nat_cast_lt.2 h⟩), _⟩
      have : n ^^^ (u ^^^ n) = u; 

Proof state:
case this
n : ℕ
hn : ∀ m < n, ∀ (m_1 : ℕ), grundyValue (nim ↑m + nim ↑m_1) = ↑(m ^^^ m_1)
m : ℕ
hm : ∀ m_1 < m, grundyValue (nim ↑n + nim ↑m_1) = ↑(n ^^^ m_1)
u : ℕ
hu : u < n ^^^ m
h : u ^^^ n < m
⊢ n ^^^ (u ^^^ n) = u
case h.h.refine_2.intro.inr
n : ℕ
hn : ∀ m < n, ∀ (m_1 : ℕ), grundyValue (nim ↑m + nim ↑m_1) = ↑(m ^^^ m_1)
m : ℕ
hm : ∀ m_1 < m, grundyValue (nim ↑n + nim ↑m_1) = ↑(n ^^^ m_1)
u : ℕ
hu : u < n ^^^ m
h : u ^^^ n < m
this : n ^^^ (u ^^^ n) = u
⊢ grundyValue
      (moveLeft (nim ↑n + nim ↑m)
        (toLeftMovesAdd (Sum.inr (toLeftMovesNim { val := ↑(u ^^^ n), property := (_ : ↑(u ^^^ n) < ↑m) })))) =
    ↑u

Next tactic:
rw [Nat.xor_comm u, Nat.xor_cancel_left]
/-- The Grundy value of the sum of two nim games with natural numbers of piles equals their bitwise
xor. -/
@[simp]
theorem grundyValue_nim_add_nim (n m : ℕ) :
    grundyValue (nim.{u} n + nim.{u} m) = n ^^^ m := by
  -- We do strong induction on both variables.
  induction' n using Nat.strong_induction_on with n hn generalizing m
  induction' m using Nat.strong_induction_on with m hm
  rw [grundyValue_eq_mex_left]
  refine (Ordinal.mex_le_of_ne.{u, u} fun i => ?_).antisymm
    (Ordinal.le_mex_of_forall fun ou hu => ?_)
  -- The Grundy value `n ^^^ m` can't be reached by left moves.
  · apply leftMoves_add_cases i <;>
      · -- A left move leaves us with a Grundy value of `k ^^^ m` for `k < n`, or
        -- `n ^^^ k` for `k < m`.
        refine' fun a => leftMovesNimRecOn a fun ok hk => _
        obtain ⟨k, rfl⟩ := Ordinal.lt_omega.1 (hk.trans (Ordinal.nat_lt_omega _))
        simp only [add_moveLeft_inl, add_moveLeft_inr, moveLeft_nim', Equiv.symm_apply_apply]
        -- The inequality follows from injectivity.
        rw [nat_cast_lt] at hk
        first
        | rw [hn _ hk]
        | rw [hm _ hk]
        refine' fun h => hk.ne _
        rw [Ordinal.nat_cast_inj] at h
        first
        | rwa [Nat.xor_left_inj] at h
        | rwa [Nat.xor_right_inj] at h
  -- Every other smaller Grundy value can be reached by left moves.
  · -- If `u < m ^^^ n`, then either `u ^^^ n < m` or `u ^^^ m < n`.
    obtain ⟨u, rfl⟩ := Ordinal.lt_omega.1 (hu.trans (Ordinal.nat_lt_omega _))
    replace hu := Ordinal.nat_cast_lt.1 hu
    cases' Nat.lt_xor_cases hu with h h
    -- In the first case, reducing the `m` pile to `u ^^^ n` gives the desired Grundy value.
    · refine' ⟨toLeftMovesAdd (Sum.inl <| toLeftMovesNim ⟨_, Ordinal.nat_cast_lt.2 h⟩), _⟩
      simp [Nat.lxor_cancel_right, hn _ h]
    -- In the second case, reducing the `n` pile to `u ^^^ m` gives the desired Grundy value.
    · refine' ⟨toLeftMovesAdd (Sum.inr <| toLeftMovesNim ⟨_, Ordinal.nat_cast_lt.2 h⟩), _⟩
      have : n ^^^ (u ^^^ n) = u; rw [Nat.xor_comm u, Nat.xor_cancel_left]
      

Proof state:
case h.h.refine_2.intro.inr
n : ℕ
hn : ∀ m < n, ∀ (m_1 : ℕ), grundyValue (nim ↑m + nim ↑m_1) = ↑(m ^^^ m_1)
m : ℕ
hm : ∀ m_1 < m, grundyValue (nim ↑n + nim ↑m_1) = ↑(n ^^^ m_1)
u : ℕ
hu : u < n ^^^ m
h : u ^^^ n < m
this : n ^^^ (u ^^^ n) = u
⊢ grundyValue
      (moveLeft (nim ↑n + nim ↑m)
        (toLeftMovesAdd (Sum.inr (toLeftMovesNim { val := ↑(u ^^^ n), property := (_ : ↑(u ^^^ n) < ↑m) })))) =
    ↑u

Next tactic:
simpa [hm _ h] using this
theorem nim_add_nim_equiv {n m : ℕ} : nim n + nim m ≈ nim (n ^^^ m) := by
  

Proof state:
n m : ℕ
⊢ nim ↑n + nim ↑m ≈ nim ↑(n ^^^ m)

Next tactic:
rw [← grundyValue_eq_iff_equiv_nim, grundyValue_nim_add_nim]
theorem grundyValue_add (G H : PGame) [G.Impartial] [H.Impartial] {n m : ℕ} (hG : grundyValue G = n)
    (hH : grundyValue H = m) : grundyValue (G + H) = n ^^^ m := by
  

Proof state:
G H : PGame
inst✝¹ : Impartial G
inst✝ : Impartial H
n m : ℕ
hG : grundyValue G = ↑n
hH : grundyValue H = ↑m
⊢ grundyValue (G + H) = ↑(n ^^^ m)

Next tactic:
rw [← nim_grundyValue (n ^^^ m), grundyValue_eq_iff_equiv]
theorem grundyValue_add (G H : PGame) [G.Impartial] [H.Impartial] {n m : ℕ} (hG : grundyValue G = n)
    (hH : grundyValue H = m) : grundyValue (G + H) = n ^^^ m := by
  rw [← nim_grundyValue (n ^^^ m), grundyValue_eq_iff_equiv]
  

Proof state:
G H : PGame
inst✝¹ : Impartial G
inst✝ : Impartial H
n m : ℕ
hG : grundyValue G = ↑n
hH : grundyValue H = ↑m
⊢ G + H ≈ nim ↑(n ^^^ m)

Next tactic:
refine' Equiv.trans _ nim_add_nim_equiv
theorem grundyValue_add (G H : PGame) [G.Impartial] [H.Impartial] {n m : ℕ} (hG : grundyValue G = n)
    (hH : grundyValue H = m) : grundyValue (G + H) = n ^^^ m := by
  rw [← nim_grundyValue (n ^^^ m), grundyValue_eq_iff_equiv]
  refine' Equiv.trans _ nim_add_nim_equiv
  

Proof state:
G H : PGame
inst✝¹ : Impartial G
inst✝ : Impartial H
n m : ℕ
hG : grundyValue G = ↑n
hH : grundyValue H = ↑m
⊢ G + H ≈ nim ↑n + nim ↑m

Next tactic:
convert add_congr (equiv_nim_grundyValue G) (equiv_nim_grundyValue H)
theorem grundyValue_add (G H : PGame) [G.Impartial] [H.Impartial] {n m : ℕ} (hG : grundyValue G = n)
    (hH : grundyValue H = m) : grundyValue (G + H) = n ^^^ m := by
  rw [← nim_grundyValue (n ^^^ m), grundyValue_eq_iff_equiv]
  refine' Equiv.trans _ nim_add_nim_equiv
  convert add_congr (equiv_nim_grundyValue G) (equiv_nim_grundyValue H) <;> 

Proof state:
case h.e'_4.h.e'_5.h.e'_1
G H : PGame
inst✝¹ : Impartial G
inst✝ : Impartial H
n m : ℕ
hG : grundyValue G = ↑n
hH : grundyValue H = ↑m
⊢ ↑n = grundyValue G

Next tactic:
simp only [hG, hH]
theorem grundyValue_add (G H : PGame) [G.Impartial] [H.Impartial] {n m : ℕ} (hG : grundyValue G = n)
    (hH : grundyValue H = m) : grundyValue (G + H) = n ^^^ m := by
  rw [← nim_grundyValue (n ^^^ m), grundyValue_eq_iff_equiv]
  refine' Equiv.trans _ nim_add_nim_equiv
  convert add_congr (equiv_nim_grundyValue G) (equiv_nim_grundyValue H) <;> 

Proof state:
case h.e'_4.h.e'_6.h.e'_1
G H : PGame
inst✝¹ : Impartial G
inst✝ : Impartial H
n m : ℕ
hG : grundyValue G = ↑n
hH : grundyValue H = ↑m
⊢ ↑m = grundyValue H

Next tactic:
simp only [hG, hH]
/-- A restatement of `Types.Limit.lift_π_apply` that uses `Pi.π` and `Pi.lift`,
with specialized universes. -/
theorem pi_lift_π_apply' {β : Type v} (f : β → Type v) {P : Type v}
    (s : ∀ b, P ⟶ f b) (b : β) (x : P) :
    (Pi.π f b : (piObj f) → f b) (@Pi.lift β _ _ f _ P s x) = s b x := by
  

Proof state:
β : Type v
f : β → Type v
P : Type v
s : (b : β) → P ⟶ f b
b : β
x : P
⊢ Pi.π f b (Pi.lift s x) = s b x

Next tactic:
simp
/-- A restatement of `Types.Limit.map_π_apply` that uses `Pi.π` and `Pi.map`,
with specialized universes. -/
theorem pi_map_π_apply' {β : Type v} {f g : β → Type v} (α : ∀ j, f j ⟶ g j) (b : β) (x) :
    (Pi.π g b : ∏ g → g b) (Pi.map α x) = α b ((Pi.π f b : ∏ f → f b) x) := by
  

Proof state:
β : Type v
f g : β → Type v
α : (j : β) → f j ⟶ g j
b : β
x : ∏ fun b => f b
⊢ Pi.π g b (Pi.map α x) = α b (Pi.π f b x)

Next tactic:
simp
/-- The category of types has `PUnit` as a terminal object. -/
def terminalLimitCone : Limits.LimitCone (Functor.empty (Type u)) where
  -- porting note: tidy was able to fill the structure automatically
  cone :=
    { pt := PUnit
      π := (Functor.uniqueFromEmpty _).hom }
  isLimit :=
    { lift := fun _ _ => PUnit.unit
      fac := fun _ => by 

Proof state:
x✝ : Cone (Functor.empty (Type u))
⊢ ∀ (j : Discrete PEmpty.{?u.3735 + 1}),
    (fun x x => PUnit.unit) x✝ ≫
        { pt := PUnit.{u + 1},
                π :=
                  (Functor.uniqueFromEmpty
                      ((Functor.const (Discrete PEmpty.{?u.3735 + 1})).obj PUnit.{u + 1})).hom }.π.app
          j =
      x✝.π.app j

Next tactic:
rintro ⟨⟨⟩⟩
/-- The category of types has `PUnit` as a terminal object. -/
def terminalLimitCone : Limits.LimitCone (Functor.empty (Type u)) where
  -- porting note: tidy was able to fill the structure automatically
  cone :=
    { pt := PUnit
      π := (Functor.uniqueFromEmpty _).hom }
  isLimit :=
    { lift := fun _ _ => PUnit.unit
      fac := fun _ => by rintro ⟨⟨⟩⟩
      uniq := fun _ _ _ => by
        

Proof state:
x✝² : Cone (Functor.empty (Type u))
x✝¹ :
  x✝².pt ⟶
    { pt := PUnit.{u + 1},
        π := (Functor.uniqueFromEmpty ((Functor.const (Discrete PEmpty.{?u.3735 + 1})).obj PUnit.{u + 1})).hom }.pt
x✝ :
  ∀ (j : Discrete PEmpty.{?u.3735 + 1}),
    x✝¹ ≫
        { pt := PUnit.{u + 1},
                π :=
                  (Functor.uniqueFromEmpty
                      ((Functor.const (Discrete PEmpty.{?u.3735 + 1})).obj PUnit.{u + 1})).hom }.π.app
          j =
      x✝².π.app j
⊢ x✝¹ = (fun x x => PUnit.unit) x✝²

Next tactic:
funext
/-- The category of types has `PUnit` as a terminal object. -/
def terminalLimitCone : Limits.LimitCone (Functor.empty (Type u)) where
  -- porting note: tidy was able to fill the structure automatically
  cone :=
    { pt := PUnit
      π := (Functor.uniqueFromEmpty _).hom }
  isLimit :=
    { lift := fun _ _ => PUnit.unit
      fac := fun _ => by rintro ⟨⟨⟩⟩
      uniq := fun _ _ _ => by
        funext
        

Proof state:
case h
x✝³ : Cone (Functor.empty (Type u))
x✝² :
  x✝³.pt ⟶
    { pt := PUnit.{u + 1},
        π := (Functor.uniqueFromEmpty ((Functor.const (Discrete PEmpty.{?u.3735 + 1})).obj PUnit.{u + 1})).hom }.pt
x✝¹ :
  ∀ (j : Discrete PEmpty.{?u.3735 + 1}),
    x✝² ≫
        { pt := PUnit.{u + 1},
                π :=
                  (Functor.uniqueFromEmpty
                      ((Functor.const (Discrete PEmpty.{?u.3735 + 1})).obj PUnit.{u + 1})).hom }.π.app
          j =
      x✝³.π.app j
x✝ : x✝³.pt
⊢ x✝² x✝ = (fun x x => PUnit.unit) x✝³ x✝

Next tactic:
apply Subsingleton.elim
/-- A type is terminal if and only if it is isomorphic to `PUnit`. -/
noncomputable def isTerminalEquivIsoPUnit (X : Type u) : IsTerminal X ≃ (X ≅ PUnit) := by
  

Proof state:
X : Type u
⊢ IsTerminal X ≃ (X ≅ PUnit.{u + 1})

Next tactic:
calc
    IsTerminal X ≃ Unique X := isTerminalEquivUnique _
    _ ≃ (X ≃ PUnit.{u + 1}) := uniqueEquivEquivUnique _ _
    _ ≃ (X ≅ PUnit) := equivEquivIso
/-- The category of types has `PEmpty` as an initial object. -/
def initialColimitCocone : Limits.ColimitCocone (Functor.empty (Type u)) where
  -- porting note: tidy was able to fill the structure automatically
  cocone :=
    { pt := PEmpty
      ι := (Functor.uniqueFromEmpty _).inv }
  isColimit :=
    { desc := fun _ => by 

Proof state:
x✝ : Cocone (Functor.empty (Type u))
⊢ { pt := PEmpty.{u + 1},
        ι := (Functor.uniqueFromEmpty ((Functor.const (Discrete PEmpty.{?u.11885 + 1})).obj PEmpty.{u + 1})).inv }.pt ⟶
    x✝.pt

Next tactic:
rintro ⟨⟩
/-- The category of types has `PEmpty` as an initial object. -/
def initialColimitCocone : Limits.ColimitCocone (Functor.empty (Type u)) where
  -- porting note: tidy was able to fill the structure automatically
  cocone :=
    { pt := PEmpty
      ι := (Functor.uniqueFromEmpty _).inv }
  isColimit :=
    { desc := fun _ => by rintro ⟨⟩
      fac := fun _ => by 

Proof state:
x✝ : Cocone (Functor.empty (Type u))
⊢ ∀ (j : Discrete PEmpty.{?u.11885 + 1}),
    { pt := PEmpty.{u + 1},
                ι :=
                  (Functor.uniqueFromEmpty
                      ((Functor.const (Discrete PEmpty.{?u.11885 + 1})).obj PEmpty.{u + 1})).inv }.ι.app
          j ≫
        (fun x a => PEmpty.casesOn (fun x_1 => x.pt) a) x✝ =
      x✝.ι.app j

Next tactic:
rintro ⟨⟨⟩⟩
/-- The category of types has `PEmpty` as an initial object. -/
def initialColimitCocone : Limits.ColimitCocone (Functor.empty (Type u)) where
  -- porting note: tidy was able to fill the structure automatically
  cocone :=
    { pt := PEmpty
      ι := (Functor.uniqueFromEmpty _).inv }
  isColimit :=
    { desc := fun _ => by rintro ⟨⟩
      fac := fun _ => by rintro ⟨⟨⟩⟩
      uniq := fun _ _ _ => by 

Proof state:
x✝² : Cocone (Functor.empty (Type u))
x✝¹ :
  { pt := PEmpty.{u + 1},
        ι := (Functor.uniqueFromEmpty ((Functor.const (Discrete PEmpty.{?u.11885 + 1})).obj PEmpty.{u + 1})).inv }.pt ⟶
    x✝².pt
x✝ :
  ∀ (j : Discrete PEmpty.{?u.11885 + 1}),
    { pt := PEmpty.{u + 1},
                ι :=
                  (Functor.uniqueFromEmpty
                      ((Functor.const (Discrete PEmpty.{?u.11885 + 1})).obj PEmpty.{u + 1})).inv }.ι.app
          j ≫
        x✝¹ =
      x✝².ι.app j
⊢ x✝¹ = (fun x a => PEmpty.casesOn (fun x_1 => x.pt) a) x✝²

Next tactic:
funext x
/-- The category of types has `PEmpty` as an initial object. -/
def initialColimitCocone : Limits.ColimitCocone (Functor.empty (Type u)) where
  -- porting note: tidy was able to fill the structure automatically
  cocone :=
    { pt := PEmpty
      ι := (Functor.uniqueFromEmpty _).inv }
  isColimit :=
    { desc := fun _ => by rintro ⟨⟩
      fac := fun _ => by rintro ⟨⟨⟩⟩
      uniq := fun _ _ _ => by funext x; 

Proof state:
case h
x✝² : Cocone (Functor.empty (Type u))
x✝¹ :
  { pt := PEmpty.{u + 1},
        ι := (Functor.uniqueFromEmpty ((Functor.const (Discrete PEmpty.{?u.11885 + 1})).obj PEmpty.{u + 1})).inv }.pt ⟶
    x✝².pt
x✝ :
  ∀ (j : Discrete PEmpty.{?u.11885 + 1}),
    { pt := PEmpty.{u + 1},
                ι :=
                  (Functor.uniqueFromEmpty
                      ((Functor.const (Discrete PEmpty.{?u.11885 + 1})).obj PEmpty.{u + 1})).inv }.ι.app
          j ≫
        x✝¹ =
      x✝².ι.app j
x :
  { pt := PEmpty.{u + 1},
      ι := (Functor.uniqueFromEmpty ((Functor.const (Discrete PEmpty.{?u.11885 + 1})).obj PEmpty.{u + 1})).inv }.pt
⊢ x✝¹ x = (fun x a => PEmpty.casesOn (fun x_1 => x.pt) a) x✝² x

Next tactic:
cases x