l3lab/ntp-mathlib huggingface dataset:
lemma effectiveEpi_iff_epi {X Y : C} (f : X ⟶ Y) : EffectiveEpi f ↔ Epi f := by
  rw [effectiveEpi_iff_effectiveEpiFamily, h]
  have w : f = (Limits.Sigma.ι (fun () ↦ X) ()) ≫ (Limits.Sigma.desc (fun () ↦ f))
  · simp only [Limits.colimit.ι_desc, Limits.Cofan.mk_pt, Limits.Cofan.mk_ι_app]
  refine ⟨?_, fun _ ↦ epi_of_epi_fac w.symm⟩
  intro
  rw [w]
  have : Epi (Limits.Sigma.ι (fun () ↦ X) ()) := ⟨fun _ _ h ↦ by ext; 

Proof state:
case h
C : Type u_1
inst✝¹ : Category.{u_2, u_1} C
inst✝ : HasFiniteCoproducts C
h✝ :
  ∀ {α : Type} [inst : Fintype α] {B : C} (X : α → C) (π : (a : α) → X a ⟶ B),
    EffectiveEpiFamily X π ↔ Epi (Sigma.desc π)
X Y : C
f : X ⟶ Y
w :
  f =
    Sigma.ι (fun x => X) () ≫
      Sigma.desc fun x =>
        match x with
        | PUnit.unit => f
a✝ :
  Epi
    (Sigma.desc fun x =>
      match x with
      | PUnit.unit => f)
Z✝ : C
x✝¹ x✝ : (∐ fun x => X) ⟶ Z✝
h : Sigma.ι (fun x => X) () ≫ x✝¹ = Sigma.ι (fun x => X) () ≫ x✝
b✝ : Unit
⊢ Sigma.ι (fun x => X) b✝ ≫ x✝¹ = Sigma.ι (fun x => X) b✝ ≫ x✝

Next tactic:
exact h
lemma effectiveEpi_iff_epi {X Y : C} (f : X ⟶ Y) : EffectiveEpi f ↔ Epi f := by
  rw [effectiveEpi_iff_effectiveEpiFamily, h]
  have w : f = (Limits.Sigma.ι (fun () ↦ X) ()) ≫ (Limits.Sigma.desc (fun () ↦ f))
  · simp only [Limits.colimit.ι_desc, Limits.Cofan.mk_pt, Limits.Cofan.mk_ι_app]
  refine ⟨?_, fun _ ↦ epi_of_epi_fac w.symm⟩
  intro
  rw [w]
  have : Epi (Limits.Sigma.ι (fun () ↦ X) ()) := ⟨fun _ _ h ↦ by ext; exact h⟩
  

Proof state:
C : Type u_1
inst✝¹ : Category.{u_2, u_1} C
inst✝ : HasFiniteCoproducts C
h :
  ∀ {α : Type} [inst : Fintype α] {B : C} (X : α → C) (π : (a : α) → X a ⟶ B),
    EffectiveEpiFamily X π ↔ Epi (Sigma.desc π)
X Y : C
f : X ⟶ Y
w :
  f =
    Sigma.ι (fun x => X) () ≫
      Sigma.desc fun x =>
        match x with
        | PUnit.unit => f
a✝ :
  Epi
    (Sigma.desc fun x =>
      match x with
      | PUnit.unit => f)
this : Epi (Sigma.ι (fun x => X) ())
⊢ Epi
    (Sigma.ι (fun x => X) () ≫
      Sigma.desc fun x =>
        match x with
        | PUnit.unit => f)

Next tactic:
exact epi_comp _ _
/-- The dual quaternions can be equivalently represented as a quaternion with dual coefficients,
or as a dual number with quaternion coefficients.

See also `Matrix.dualNumberEquiv` for a similar result. -/
def dualNumberEquiv : Quaternion (DualNumber R) ≃ₐ[R] DualNumber (Quaternion R) where
  toFun q :=
    (⟨q.re.fst, q.imI.fst, q.imJ.fst, q.imK.fst⟩, ⟨q.re.snd, q.imI.snd, q.imJ.snd, q.imK.snd⟩)
  invFun d :=
    ⟨(d.fst.re, d.snd.re), (d.fst.imI, d.snd.imI), (d.fst.imJ, d.snd.imJ), (d.fst.imK, d.snd.imK)⟩
  left_inv := fun ⟨⟨r, rε⟩, ⟨i, iε⟩, ⟨j, jε⟩, ⟨k, kε⟩⟩ => rfl
  right_inv := fun ⟨⟨r, i, j, k⟩, ⟨rε, iε, jε, kε⟩⟩ => rfl
  map_mul' := by
    rintro ⟨⟨xr, xrε⟩, ⟨xi, xiε⟩, ⟨xj, xjε⟩, ⟨xk, xkε⟩⟩
    rintro ⟨⟨yr, yrε⟩, ⟨yi, yiε⟩, ⟨yj, yjε⟩, ⟨yk, ykε⟩⟩
    ext : 1
    · rfl
    · dsimp
      

Proof state:
case mk.mk.mk.mk.mk.mk.mk.mk.mk.mk.h2
R : Type u_1
inst✝ : CommRing R
xr xrε xi xiε xj xjε xk xkε yr yrε yi yiε yj yjε yk ykε : R
⊢ {
      re :=
        TrivSqZeroExt.snd
          ({ re := (xr, xrε), imI := (xi, xiε), imJ := (xj, xjε), imK := (xk, xkε) } *
              { re := (yr, yrε), imI := (yi, yiε), imJ := (yj, yjε), imK := (yk, ykε) }).re,
      imI :=
        TrivSqZeroExt.snd
          ({ re := (xr, xrε), imI := (xi, xiε), imJ := (xj, xjε), imK := (xk, xkε) } *
              { re := (yr, yrε), imI := (yi, yiε), imJ := (yj, yjε), imK := (yk, ykε) }).imI,
      imJ :=
        TrivSqZeroExt.snd
          ({ re := (xr, xrε), imI := (xi, xiε), imJ := (xj, xjε), imK := (xk, xkε) } *
              { re := (yr, yrε), imI := (yi, yiε), imJ := (yj, yjε), imK := (yk, ykε) }).imJ,
      imK :=
        TrivSqZeroExt.snd
          ({ re := (xr, xrε), imI := (xi, xiε), imJ := (xj, xjε), imK := (xk, xkε) } *
              { re := (yr, yrε), imI := (yi, yiε), imJ := (yj, yjε), imK := (yk, ykε) }).imK } =
    { re := xr, imI := xi, imJ := xj, imK := xk } * { re := yrε, imI := yiε, imJ := yjε, imK := ykε } +
      { re := xrε, imI := xiε, imJ := xjε, imK := xkε } * { re := yr, imI := yi, imJ := yj, imK := yk }

Next tactic:
congr 1
/-- The dual quaternions can be equivalently represented as a quaternion with dual coefficients,
or as a dual number with quaternion coefficients.

See also `Matrix.dualNumberEquiv` for a similar result. -/
def dualNumberEquiv : Quaternion (DualNumber R) ≃ₐ[R] DualNumber (Quaternion R) where
  toFun q :=
    (⟨q.re.fst, q.imI.fst, q.imJ.fst, q.imK.fst⟩, ⟨q.re.snd, q.imI.snd, q.imJ.snd, q.imK.snd⟩)
  invFun d :=
    ⟨(d.fst.re, d.snd.re), (d.fst.imI, d.snd.imI), (d.fst.imJ, d.snd.imJ), (d.fst.imK, d.snd.imK)⟩
  left_inv := fun ⟨⟨r, rε⟩, ⟨i, iε⟩, ⟨j, jε⟩, ⟨k, kε⟩⟩ => rfl
  right_inv := fun ⟨⟨r, i, j, k⟩, ⟨rε, iε, jε, kε⟩⟩ => rfl
  map_mul' := by
    rintro ⟨⟨xr, xrε⟩, ⟨xi, xiε⟩, ⟨xj, xjε⟩, ⟨xk, xkε⟩⟩
    rintro ⟨⟨yr, yrε⟩, ⟨yi, yiε⟩, ⟨yj, yjε⟩, ⟨yk, ykε⟩⟩
    ext : 1
    · rfl
    · dsimp
      congr 1 <;> 

Proof state:
case mk.mk.mk.mk.mk.mk.mk.mk.mk.mk.h2.e_re
R : Type u_1
inst✝ : CommRing R
xr xrε xi xiε xj xjε xk xkε yr yrε yi yiε yj yjε yk ykε : R
⊢ TrivSqZeroExt.snd
      ({ re := (xr, xrε), imI := (xi, xiε), imJ := (xj, xjε), imK := (xk, xkε) } *
          { re := (yr, yrε), imI := (yi, yiε), imJ := (yj, yjε), imK := (yk, ykε) }).re =
    ({ re := xr, imI := xi, imJ := xj, imK := xk } * { re := yrε, imI := yiε, imJ := yjε, imK := ykε }).re +
      ({ re := xrε, imI := xiε, imJ := xjε, imK := xkε } * { re := yr, imI := yi, imJ := yj, imK := yk }).re

Next tactic:
simp
/-- The dual quaternions can be equivalently represented as a quaternion with dual coefficients,
or as a dual number with quaternion coefficients.

See also `Matrix.dualNumberEquiv` for a similar result. -/
def dualNumberEquiv : Quaternion (DualNumber R) ≃ₐ[R] DualNumber (Quaternion R) where
  toFun q :=
    (⟨q.re.fst, q.imI.fst, q.imJ.fst, q.imK.fst⟩, ⟨q.re.snd, q.imI.snd, q.imJ.snd, q.imK.snd⟩)
  invFun d :=
    ⟨(d.fst.re, d.snd.re), (d.fst.imI, d.snd.imI), (d.fst.imJ, d.snd.imJ), (d.fst.imK, d.snd.imK)⟩
  left_inv := fun ⟨⟨r, rε⟩, ⟨i, iε⟩, ⟨j, jε⟩, ⟨k, kε⟩⟩ => rfl
  right_inv := fun ⟨⟨r, i, j, k⟩, ⟨rε, iε, jε, kε⟩⟩ => rfl
  map_mul' := by
    rintro ⟨⟨xr, xrε⟩, ⟨xi, xiε⟩, ⟨xj, xjε⟩, ⟨xk, xkε⟩⟩
    rintro ⟨⟨yr, yrε⟩, ⟨yi, yiε⟩, ⟨yj, yjε⟩, ⟨yk, ykε⟩⟩
    ext : 1
    · rfl
    · dsimp
      congr 1 <;> 

Proof state:
case mk.mk.mk.mk.mk.mk.mk.mk.mk.mk.h2.e_imI
R : Type u_1
inst✝ : CommRing R
xr xrε xi xiε xj xjε xk xkε yr yrε yi yiε yj yjε yk ykε : R
⊢ TrivSqZeroExt.snd
      ({ re := (xr, xrε), imI := (xi, xiε), imJ := (xj, xjε), imK := (xk, xkε) } *
          { re := (yr, yrε), imI := (yi, yiε), imJ := (yj, yjε), imK := (yk, ykε) }).imI =
    ({ re := xr, imI := xi, imJ := xj, imK := xk } * { re := yrε, imI := yiε, imJ := yjε, imK := ykε }).imI +
      ({ re := xrε, imI := xiε, imJ := xjε, imK := xkε } * { re := yr, imI := yi, imJ := yj, imK := yk }).imI

Next tactic:
simp
/-- The dual quaternions can be equivalently represented as a quaternion with dual coefficients,
or as a dual number with quaternion coefficients.

See also `Matrix.dualNumberEquiv` for a similar result. -/
def dualNumberEquiv : Quaternion (DualNumber R) ≃ₐ[R] DualNumber (Quaternion R) where
  toFun q :=
    (⟨q.re.fst, q.imI.fst, q.imJ.fst, q.imK.fst⟩, ⟨q.re.snd, q.imI.snd, q.imJ.snd, q.imK.snd⟩)
  invFun d :=
    ⟨(d.fst.re, d.snd.re), (d.fst.imI, d.snd.imI), (d.fst.imJ, d.snd.imJ), (d.fst.imK, d.snd.imK)⟩
  left_inv := fun ⟨⟨r, rε⟩, ⟨i, iε⟩, ⟨j, jε⟩, ⟨k, kε⟩⟩ => rfl
  right_inv := fun ⟨⟨r, i, j, k⟩, ⟨rε, iε, jε, kε⟩⟩ => rfl
  map_mul' := by
    rintro ⟨⟨xr, xrε⟩, ⟨xi, xiε⟩, ⟨xj, xjε⟩, ⟨xk, xkε⟩⟩
    rintro ⟨⟨yr, yrε⟩, ⟨yi, yiε⟩, ⟨yj, yjε⟩, ⟨yk, ykε⟩⟩
    ext : 1
    · rfl
    · dsimp
      congr 1 <;> 

Proof state:
case mk.mk.mk.mk.mk.mk.mk.mk.mk.mk.h2.e_imJ
R : Type u_1
inst✝ : CommRing R
xr xrε xi xiε xj xjε xk xkε yr yrε yi yiε yj yjε yk ykε : R
⊢ TrivSqZeroExt.snd
      ({ re := (xr, xrε), imI := (xi, xiε), imJ := (xj, xjε), imK := (xk, xkε) } *
          { re := (yr, yrε), imI := (yi, yiε), imJ := (yj, yjε), imK := (yk, ykε) }).imJ =
    ({ re := xr, imI := xi, imJ := xj, imK := xk } * { re := yrε, imI := yiε, imJ := yjε, imK := ykε }).imJ +
      ({ re := xrε, imI := xiε, imJ := xjε, imK := xkε } * { re := yr, imI := yi, imJ := yj, imK := yk }).imJ

Next tactic:
simp
/-- The dual quaternions can be equivalently represented as a quaternion with dual coefficients,
or as a dual number with quaternion coefficients.

See also `Matrix.dualNumberEquiv` for a similar result. -/
def dualNumberEquiv : Quaternion (DualNumber R) ≃ₐ[R] DualNumber (Quaternion R) where
  toFun q :=
    (⟨q.re.fst, q.imI.fst, q.imJ.fst, q.imK.fst⟩, ⟨q.re.snd, q.imI.snd, q.imJ.snd, q.imK.snd⟩)
  invFun d :=
    ⟨(d.fst.re, d.snd.re), (d.fst.imI, d.snd.imI), (d.fst.imJ, d.snd.imJ), (d.fst.imK, d.snd.imK)⟩
  left_inv := fun ⟨⟨r, rε⟩, ⟨i, iε⟩, ⟨j, jε⟩, ⟨k, kε⟩⟩ => rfl
  right_inv := fun ⟨⟨r, i, j, k⟩, ⟨rε, iε, jε, kε⟩⟩ => rfl
  map_mul' := by
    rintro ⟨⟨xr, xrε⟩, ⟨xi, xiε⟩, ⟨xj, xjε⟩, ⟨xk, xkε⟩⟩
    rintro ⟨⟨yr, yrε⟩, ⟨yi, yiε⟩, ⟨yj, yjε⟩, ⟨yk, ykε⟩⟩
    ext : 1
    · rfl
    · dsimp
      congr 1 <;> 

Proof state:
case mk.mk.mk.mk.mk.mk.mk.mk.mk.mk.h2.e_imK
R : Type u_1
inst✝ : CommRing R
xr xrε xi xiε xj xjε xk xkε yr yrε yi yiε yj yjε yk ykε : R
⊢ TrivSqZeroExt.snd
      ({ re := (xr, xrε), imI := (xi, xiε), imJ := (xj, xjε), imK := (xk, xkε) } *
          { re := (yr, yrε), imI := (yi, yiε), imJ := (yj, yjε), imK := (yk, ykε) }).imK =
    ({ re := xr, imI := xi, imJ := xj, imK := xk } * { re := yrε, imI := yiε, imJ := yjε, imK := ykε }).imK +
      ({ re := xrε, imI := xiε, imJ := xjε, imK := xkε } * { re := yr, imI := yi, imJ := yj, imK := yk }).imK

Next tactic:
simp
/-- The dual quaternions can be equivalently represented as a quaternion with dual coefficients,
or as a dual number with quaternion coefficients.

See also `Matrix.dualNumberEquiv` for a similar result. -/
def dualNumberEquiv : Quaternion (DualNumber R) ≃ₐ[R] DualNumber (Quaternion R) where
  toFun q :=
    (⟨q.re.fst, q.imI.fst, q.imJ.fst, q.imK.fst⟩, ⟨q.re.snd, q.imI.snd, q.imJ.snd, q.imK.snd⟩)
  invFun d :=
    ⟨(d.fst.re, d.snd.re), (d.fst.imI, d.snd.imI), (d.fst.imJ, d.snd.imJ), (d.fst.imK, d.snd.imK)⟩
  left_inv := fun ⟨⟨r, rε⟩, ⟨i, iε⟩, ⟨j, jε⟩, ⟨k, kε⟩⟩ => rfl
  right_inv := fun ⟨⟨r, i, j, k⟩, ⟨rε, iε, jε, kε⟩⟩ => rfl
  map_mul' := by
    rintro ⟨⟨xr, xrε⟩, ⟨xi, xiε⟩, ⟨xj, xjε⟩, ⟨xk, xkε⟩⟩
    rintro ⟨⟨yr, yrε⟩, ⟨yi, yiε⟩, ⟨yj, yjε⟩, ⟨yk, ykε⟩⟩
    ext : 1
    · rfl
    · dsimp
      congr 1 <;> simp <;> 

Proof state:
case mk.mk.mk.mk.mk.mk.mk.mk.mk.mk.h2.e_re
R : Type u_1
inst✝ : CommRing R
xr xrε xi xiε xj xjε xk xkε yr yrε yi yiε yj yjε yk ykε : R
⊢ xr * yrε + xrε * yr - (xi * yiε + xiε * yi) - (xj * yjε + xjε * yj) - (xk * ykε + xkε * yk) =
    xr * yrε - xi * yiε - xj * yjε - xk * ykε + (xrε * yr - xiε * yi - xjε * yj - xkε * yk)

Next tactic:
ring
/-- The dual quaternions can be equivalently represented as a quaternion with dual coefficients,
or as a dual number with quaternion coefficients.

See also `Matrix.dualNumberEquiv` for a similar result. -/
def dualNumberEquiv : Quaternion (DualNumber R) ≃ₐ[R] DualNumber (Quaternion R) where
  toFun q :=
    (⟨q.re.fst, q.imI.fst, q.imJ.fst, q.imK.fst⟩, ⟨q.re.snd, q.imI.snd, q.imJ.snd, q.imK.snd⟩)
  invFun d :=
    ⟨(d.fst.re, d.snd.re), (d.fst.imI, d.snd.imI), (d.fst.imJ, d.snd.imJ), (d.fst.imK, d.snd.imK)⟩
  left_inv := fun ⟨⟨r, rε⟩, ⟨i, iε⟩, ⟨j, jε⟩, ⟨k, kε⟩⟩ => rfl
  right_inv := fun ⟨⟨r, i, j, k⟩, ⟨rε, iε, jε, kε⟩⟩ => rfl
  map_mul' := by
    rintro ⟨⟨xr, xrε⟩, ⟨xi, xiε⟩, ⟨xj, xjε⟩, ⟨xk, xkε⟩⟩
    rintro ⟨⟨yr, yrε⟩, ⟨yi, yiε⟩, ⟨yj, yjε⟩, ⟨yk, ykε⟩⟩
    ext : 1
    · rfl
    · dsimp
      congr 1 <;> simp <;> 

Proof state:
case mk.mk.mk.mk.mk.mk.mk.mk.mk.mk.h2.e_imI
R : Type u_1
inst✝ : CommRing R
xr xrε xi xiε xj xjε xk xkε yr yrε yi yiε yj yjε yk ykε : R
⊢ xr * yiε + xrε * yi + (xi * yrε + xiε * yr) + (xj * ykε + xjε * yk) - (xk * yjε + xkε * yj) =
    xr * yiε + xi * yrε + xj * ykε - xk * yjε + (xrε * yi + xiε * yr + xjε * yk - xkε * yj)

Next tactic:
ring
/-- The dual quaternions can be equivalently represented as a quaternion with dual coefficients,
or as a dual number with quaternion coefficients.

See also `Matrix.dualNumberEquiv` for a similar result. -/
def dualNumberEquiv : Quaternion (DualNumber R) ≃ₐ[R] DualNumber (Quaternion R) where
  toFun q :=
    (⟨q.re.fst, q.imI.fst, q.imJ.fst, q.imK.fst⟩, ⟨q.re.snd, q.imI.snd, q.imJ.snd, q.imK.snd⟩)
  invFun d :=
    ⟨(d.fst.re, d.snd.re), (d.fst.imI, d.snd.imI), (d.fst.imJ, d.snd.imJ), (d.fst.imK, d.snd.imK)⟩
  left_inv := fun ⟨⟨r, rε⟩, ⟨i, iε⟩, ⟨j, jε⟩, ⟨k, kε⟩⟩ => rfl
  right_inv := fun ⟨⟨r, i, j, k⟩, ⟨rε, iε, jε, kε⟩⟩ => rfl
  map_mul' := by
    rintro ⟨⟨xr, xrε⟩, ⟨xi, xiε⟩, ⟨xj, xjε⟩, ⟨xk, xkε⟩⟩
    rintro ⟨⟨yr, yrε⟩, ⟨yi, yiε⟩, ⟨yj, yjε⟩, ⟨yk, ykε⟩⟩
    ext : 1
    · rfl
    · dsimp
      congr 1 <;> simp <;> 

Proof state:
case mk.mk.mk.mk.mk.mk.mk.mk.mk.mk.h2.e_imJ
R : Type u_1
inst✝ : CommRing R
xr xrε xi xiε xj xjε xk xkε yr yrε yi yiε yj yjε yk ykε : R
⊢ xr * yjε + xrε * yj - (xi * ykε + xiε * yk) + (xj * yrε + xjε * yr) + (xk * yiε + xkε * yi) =
    xr * yjε - xi * ykε + xj * yrε + xk * yiε + (xrε * yj - xiε * yk + xjε * yr + xkε * yi)

Next tactic:
ring
/-- The dual quaternions can be equivalently represented as a quaternion with dual coefficients,
or as a dual number with quaternion coefficients.

See also `Matrix.dualNumberEquiv` for a similar result. -/
def dualNumberEquiv : Quaternion (DualNumber R) ≃ₐ[R] DualNumber (Quaternion R) where
  toFun q :=
    (⟨q.re.fst, q.imI.fst, q.imJ.fst, q.imK.fst⟩, ⟨q.re.snd, q.imI.snd, q.imJ.snd, q.imK.snd⟩)
  invFun d :=
    ⟨(d.fst.re, d.snd.re), (d.fst.imI, d.snd.imI), (d.fst.imJ, d.snd.imJ), (d.fst.imK, d.snd.imK)⟩
  left_inv := fun ⟨⟨r, rε⟩, ⟨i, iε⟩, ⟨j, jε⟩, ⟨k, kε⟩⟩ => rfl
  right_inv := fun ⟨⟨r, i, j, k⟩, ⟨rε, iε, jε, kε⟩⟩ => rfl
  map_mul' := by
    rintro ⟨⟨xr, xrε⟩, ⟨xi, xiε⟩, ⟨xj, xjε⟩, ⟨xk, xkε⟩⟩
    rintro ⟨⟨yr, yrε⟩, ⟨yi, yiε⟩, ⟨yj, yjε⟩, ⟨yk, ykε⟩⟩
    ext : 1
    · rfl
    · dsimp
      congr 1 <;> simp <;> 

Proof state:
case mk.mk.mk.mk.mk.mk.mk.mk.mk.mk.h2.e_imK
R : Type u_1
inst✝ : CommRing R
xr xrε xi xiε xj xjε xk xkε yr yrε yi yiε yj yjε yk ykε : R
⊢ xr * ykε + xrε * yk + (xi * yjε + xiε * yj) - (xj * yiε + xjε * yi) + (xk * yrε + xkε * yr) =
    xr * ykε + xi * yjε - xj * yiε + xk * yrε + (xrε * yk + xiε * yj - xjε * yi + xkε * yr)

Next tactic:
ring
theorem prod_factorial_dvd_factorial_sum : (∏ i in s, (f i)!) ∣ (∑ i in s, f i)! := by
  

Proof state:
α : Type u_1
s : Finset α
f : α → ℕ
⊢ ∏ i in s, (f i)! ∣ (∑ i in s, f i)!

Next tactic:
classical
    induction' s using Finset.induction with a' s' has ih
    · simp only [prod_empty, factorial, dvd_refl]
    · simp only [Finset.prod_insert has, Finset.sum_insert has]
      refine' dvd_trans (mul_dvd_mul_left (f a')! ih) _
      apply Nat.factorial_mul_factorial_dvd_factorial_add
theorem prod_factorial_dvd_factorial_sum : (∏ i in s, (f i)!) ∣ (∑ i in s, f i)! := by
  classical
    

Proof state:
α : Type u_1
s : Finset α
f : α → ℕ
⊢ ∏ i in s, (f i)! ∣ (∑ i in s, f i)!

Next tactic:
induction' s using Finset.induction with a' s' has ih
theorem prod_factorial_dvd_factorial_sum : (∏ i in s, (f i)!) ∣ (∑ i in s, f i)! := by
  classical
    induction' s using Finset.induction with a' s' has ih
    · 

Proof state:
case empty
α : Type u_1
s : Finset α
f : α → ℕ
⊢ ∏ i in ∅, (f i)! ∣ (∑ i in ∅, f i)!

Next tactic:
simp only [prod_empty, factorial, dvd_refl]
theorem prod_factorial_dvd_factorial_sum : (∏ i in s, (f i)!) ∣ (∑ i in s, f i)! := by
  classical
    induction' s using Finset.induction with a' s' has ih
    · simp only [prod_empty, factorial, dvd_refl]
    · 

Proof state:
case insert
α : Type u_1
s : Finset α
f : α → ℕ
a' : α
s' : Finset α
has : a' ∉ s'
ih : ∏ i in s', (f i)! ∣ (∑ i in s', f i)!
⊢ ∏ i in insert a' s', (f i)! ∣ (∑ i in insert a' s', f i)!

Next tactic:
simp only [Finset.prod_insert has, Finset.sum_insert has]
theorem prod_factorial_dvd_factorial_sum : (∏ i in s, (f i)!) ∣ (∑ i in s, f i)! := by
  classical
    induction' s using Finset.induction with a' s' has ih
    · simp only [prod_empty, factorial, dvd_refl]
    · simp only [Finset.prod_insert has, Finset.sum_insert has]
      

Proof state:
case insert
α : Type u_1
s : Finset α
f : α → ℕ
a' : α
s' : Finset α
has : a' ∉ s'
ih : ∏ i in s', (f i)! ∣ (∑ i in s', f i)!
⊢ (f a')! * ∏ i in s', (f i)! ∣ (f a' + ∑ i in s', f i)!

Next tactic:
refine' dvd_trans (mul_dvd_mul_left (f a')! ih) _
theorem prod_factorial_dvd_factorial_sum : (∏ i in s, (f i)!) ∣ (∑ i in s, f i)! := by
  classical
    induction' s using Finset.induction with a' s' has ih
    · simp only [prod_empty, factorial, dvd_refl]
    · simp only [Finset.prod_insert has, Finset.sum_insert has]
      refine' dvd_trans (mul_dvd_mul_left (f a')! ih) _
      

Proof state:
case insert
α : Type u_1
s : Finset α
f : α → ℕ
a' : α
s' : Finset α
has : a' ∉ s'
ih : ∏ i in s', (f i)! ∣ (∑ i in s', f i)!
⊢ (f a')! * (∑ i in s', f i)! ∣ (f a' + ∑ i in s', f i)!

Next tactic:
apply Nat.factorial_mul_factorial_dvd_factorial_add
theorem descFactorial_eq_prod_range (n : ℕ) : ∀ k, n.descFactorial k = ∏ i in range k, (n - i)
  | 0 => rfl
  | k + 1 => by 

Proof state:
α : Type u_1
s : Finset α
f : α → ℕ
n k : ℕ
⊢ descFactorial n (k + 1) = ∏ i in range (k + 1), (n - i)

Next tactic:
rw [descFactorial, prod_range_succ, mul_comm, descFactorial_eq_prod_range n k]
@[ext]
protected theorem ext {x y : ContT r m α} (h : ∀ f, x.run f = y.run f) : x = y := by
  

Proof state:
r : Type u
m : Type u → Type v
α β γ ω : Type w
x y : ContT r m α
h : ∀ (f : α → m r), run x f = run y f
⊢ x = y

Next tactic:
unfold ContT
@[ext]
protected theorem ext {x y : ContT r m α} (h : ∀ f, x.run f = y.run f) : x = y := by
  unfold ContT; 

Proof state:
r : Type u
m : Type u → Type v
α β γ ω : Type w
x y : ContT r m α
h : ∀ (f : α → m r), run x f = run y f
⊢ x = y

Next tactic:
ext
@[ext]
protected theorem ext {x y : ContT r m α} (h : ∀ f, x.run f = y.run f) : x = y := by
  unfold ContT; ext; 

Proof state:
case h
r : Type u
m : Type u → Type v
α β γ ω : Type w
x y : ContT r m α
h : ∀ (f : α → m r), run x f = run y f
x✝ : α → m r
⊢ x x✝ = y x✝

Next tactic:
apply h
instance : LawfulMonad (ContT r m) := LawfulMonad.mk'
  (id_map := by 

Proof state:
r : Type u
m : Type u → Type v
α β γ ω : Type w
⊢ ∀ {α : Type ?u.3222} (x : ContT r m α), id <$> x = x

Next tactic:
intros
instance : LawfulMonad (ContT r m) := LawfulMonad.mk'
  (id_map := by intros; 

Proof state:
r : Type u
m : Type u → Type v
α β γ ω : Type w
α✝ : Type ?u.3222
x✝ : ContT r m α✝
⊢ id <$> x✝ = x✝

Next tactic:
rfl
instance : LawfulMonad (ContT r m) := LawfulMonad.mk'
  (id_map := by intros; rfl)
  (pure_bind := by 

Proof state:
r : Type u
m : Type u → Type v
α β γ ω : Type w
⊢ ∀ {α β : Type ?u.3222} (x : α) (f : α → ContT r m β), pure x >>= f = f x

Next tactic:
intros
instance : LawfulMonad (ContT r m) := LawfulMonad.mk'
  (id_map := by intros; rfl)
  (pure_bind := by intros; 

Proof state:
r : Type u
m : Type u → Type v
α β γ ω : Type w
α✝ β✝ : Type ?u.3222
x✝ : α✝
f✝ : α✝ → ContT r m β✝
⊢ pure x✝ >>= f✝ = f✝ x✝

Next tactic:
ext
instance : LawfulMonad (ContT r m) := LawfulMonad.mk'
  (id_map := by intros; rfl)
  (pure_bind := by intros; ext; 

Proof state:
case h
r : Type u
m : Type u → Type v
α β γ ω : Type w
α✝ β✝ : Type ?u.3222
x✝ : α✝
f✝¹ : α✝ → ContT r m β✝
f✝ : β✝ → m r
⊢ run (pure x✝ >>= f✝¹) f✝ = run (f✝¹ x✝) f✝

Next tactic:
rfl
instance : LawfulMonad (ContT r m) := LawfulMonad.mk'
  (id_map := by intros; rfl)
  (pure_bind := by intros; ext; rfl)
  (bind_assoc := by 

Proof state:
r : Type u
m : Type u → Type v
α β γ ω : Type w
⊢ ∀ {α β γ : Type ?u.3222} (x : ContT r m α) (f : α → ContT r m β) (g : β → ContT r m γ),
    x >>= f >>= g = x >>= fun x => f x >>= g

Next tactic:
intros
instance : LawfulMonad (ContT r m) := LawfulMonad.mk'
  (id_map := by intros; rfl)
  (pure_bind := by intros; ext; rfl)
  (bind_assoc := by intros; 

Proof state:
r : Type u
m : Type u → Type v
α β γ ω : Type w
α✝ β✝ γ✝ : Type ?u.3222
x✝ : ContT r m α✝
f✝ : α✝ → ContT r m β✝
g✝ : β✝ → ContT r m γ✝
⊢ x✝ >>= f✝ >>= g✝ = x✝ >>= fun x => f✝ x >>= g✝

Next tactic:
ext
instance : LawfulMonad (ContT r m) := LawfulMonad.mk'
  (id_map := by intros; rfl)
  (pure_bind := by intros; ext; rfl)
  (bind_assoc := by intros; ext; 

Proof state:
case h
r : Type u
m : Type u → Type v
α β γ ω : Type w
α✝ β✝ γ✝ : Type ?u.3222
x✝ : ContT r m α✝
f✝¹ : α✝ → ContT r m β✝
g✝ : β✝ → ContT r m γ✝
f✝ : γ✝ → m r
⊢ run (x✝ >>= f✝¹ >>= g✝) f✝ = run (x✝ >>= fun x => f✝¹ x >>= g✝) f✝

Next tactic:
rfl
theorem monadLift_bind [Monad m] [LawfulMonad m] {α β} (x : m α) (f : α → m β) :
    (monadLift (x >>= f) : ContT r m β) = monadLift x >>= monadLift ∘ f := by
  

Proof state:
r : Type u
m : Type u → Type v
α✝ β✝ γ ω : Type w
inst✝¹ : Monad m
inst✝ : LawfulMonad m
α β : Type u
x : m α
f : α → m β
⊢ monadLift (x >>= f) = monadLift x >>= monadLift ∘ f

Next tactic:
ext
theorem monadLift_bind [Monad m] [LawfulMonad m] {α β} (x : m α) (f : α → m β) :
    (monadLift (x >>= f) : ContT r m β) = monadLift x >>= monadLift ∘ f := by
  ext
  

Proof state:
case h
r : Type u
m : Type u → Type v
α✝ β✝ γ ω : Type w
inst✝¹ : Monad m
inst✝ : LawfulMonad m
α β : Type u
x : m α
f : α → m β
f✝ : β → m r
⊢ run (monadLift (x >>= f)) f✝ = run (monadLift x >>= monadLift ∘ f) f✝

Next tactic:
simp only [monadLift, MonadLift.monadLift, (· ∘ ·), (· >>= ·), bind_assoc, id.def, run,
    ContT.monadLift]
instance : LawfulMonadCont (ContT r m) where
  callCC_bind_right := by 

Proof state:
r : Type u
m : Type u → Type v
α β γ ω : Type w
⊢ ∀ {α ω γ : Type ?u.4892} (cmd : ContT r m α) (next : Label ω (ContT r m) γ → α → ContT r m ω),
    (callCC fun f => cmd >>= next f) = do
      let x ← cmd
      callCC fun f => next f x

Next tactic:
intros
instance : LawfulMonadCont (ContT r m) where
  callCC_bind_right := by intros; 

Proof state:
r : Type u
m : Type u → Type v
α β γ ω : Type w
α✝ ω✝ γ✝ : Type ?u.4892
cmd✝ : ContT r m α✝
next✝ : Label ω✝ (ContT r m) γ✝ → α✝ → ContT r m ω✝
⊢ (callCC fun f => cmd✝ >>= next✝ f) = do
    let x ← cmd✝
    callCC fun f => next✝ f x

Next tactic:
ext
instance : LawfulMonadCont (ContT r m) where
  callCC_bind_right := by intros; ext; 

Proof state:
case h
r : Type u
m : Type u → Type v
α β γ ω : Type w
α✝ ω✝ γ✝ : Type ?u.4892
cmd✝ : ContT r m α✝
next✝ : Label ω✝ (ContT r m) γ✝ → α✝ → ContT r m ω✝
f✝ : ω✝ → m r
⊢ run (callCC fun f => cmd✝ >>= next✝ f) f✝ =
    run
      (do
        let x ← cmd✝
        callCC fun f => next✝ f x)
      f✝

Next tactic:
rfl
instance : LawfulMonadCont (ContT r m) where
  callCC_bind_right := by intros; ext; rfl
  callCC_bind_left := by 

Proof state:
r : Type u
m : Type u → Type v
α β γ ω : Type w
⊢ ∀ {α : Type ?u.4892} (β : Type ?u.4892) (x : α) (dead : Label α (ContT r m) β → β → ContT r m α),
    (callCC fun f => goto f x >>= dead f) = pure x

Next tactic:
intros
instance : LawfulMonadCont (ContT r m) where
  callCC_bind_right := by intros; ext; rfl
  callCC_bind_left := by intros; 

Proof state:
r : Type u
m : Type u → Type v
α β γ ω : Type w
α✝ β✝ : Type ?u.4892
x✝ : α✝
dead✝ : Label α✝ (ContT r m) β✝ → β✝ → ContT r m α✝
⊢ (callCC fun f => goto f x✝ >>= dead✝ f) = pure x✝

Next tactic:
ext
instance : LawfulMonadCont (ContT r m) where
  callCC_bind_right := by intros; ext; rfl
  callCC_bind_left := by intros; ext; 

Proof state:
case h
r : Type u
m : Type u → Type v
α β γ ω : Type w
α✝ β✝ : Type ?u.4892
x✝ : α✝
dead✝ : Label α✝ (ContT r m) β✝ → β✝ → ContT r m α✝
f✝ : α✝ → m r
⊢ run (callCC fun f => goto f x✝ >>= dead✝ f) f✝ = run (pure x✝) f✝

Next tactic:
rfl
instance : LawfulMonadCont (ContT r m) where
  callCC_bind_right := by intros; ext; rfl
  callCC_bind_left := by intros; ext; rfl
  callCC_dummy := by 

Proof state:
r : Type u
m : Type u → Type v
α β γ ω : Type w
⊢ ∀ {α β : Type ?u.4892} (dummy : ContT r m α), (callCC fun x => dummy) = dummy

Next tactic:
intros
instance : LawfulMonadCont (ContT r m) where
  callCC_bind_right := by intros; ext; rfl
  callCC_bind_left := by intros; ext; rfl
  callCC_dummy := by intros; 

Proof state:
r : Type u
m : Type u → Type v
α β γ ω : Type w
α✝ β✝ : Type ?u.4892
dummy✝ : ContT r m α✝
⊢ (callCC fun x => dummy✝) = dummy✝

Next tactic:
ext