l3lab/ntp-mathlib huggingface dataset:
@[to_additive (attr := simp)]
theorem map_map {I : Type*} [Group I] (M : Subgroup H) (O : Subgroup I) [M.Normal] [O.Normal]
    (f : G →* H) (g : H →* I) (hf : N ≤ Subgroup.comap f M) (hg : M ≤ Subgroup.comap g O)
    (hgf : N ≤ Subgroup.comap (g.comp f) O :=
      hf.trans ((Subgroup.comap_mono hg).trans_eq (Subgroup.comap_comap _ _ _)))
    (x : G ⧸ N) : map M O g hg (map N M f hf x) = map N O (g.comp f) hgf x := by
  

Proof state:
G : Type u
inst✝⁵ : Group G
N : Subgroup G
nN : Subgroup.Normal N
H : Type v
inst✝⁴ : Group H
M✝ : Type x
inst✝³ : Monoid M✝
I : Type u_1
inst✝² : Group I
M : Subgroup H
O : Subgroup I
inst✝¹ : Subgroup.Normal M
inst✝ : Subgroup.Normal O
f : G →* H
g : H →* I
hf : N ≤ Subgroup.comap f M
hg : M ≤ Subgroup.comap g O
hgf : optParam (N ≤ Subgroup.comap (MonoidHom.comp g f) O) (_ : N ≤ Subgroup.comap (MonoidHom.comp g f) O)
x : G ⧸ N
⊢ (map M O g hg) ((map N M f hf) x) = (map N O (MonoidHom.comp g f) hgf) x

Next tactic:
refine' induction_on' x fun x => _
@[to_additive (attr := simp)]
theorem map_map {I : Type*} [Group I] (M : Subgroup H) (O : Subgroup I) [M.Normal] [O.Normal]
    (f : G →* H) (g : H →* I) (hf : N ≤ Subgroup.comap f M) (hg : M ≤ Subgroup.comap g O)
    (hgf : N ≤ Subgroup.comap (g.comp f) O :=
      hf.trans ((Subgroup.comap_mono hg).trans_eq (Subgroup.comap_comap _ _ _)))
    (x : G ⧸ N) : map M O g hg (map N M f hf x) = map N O (g.comp f) hgf x := by
  refine' induction_on' x fun x => _
  

Proof state:
G : Type u
inst✝⁵ : Group G
N : Subgroup G
nN : Subgroup.Normal N
H : Type v
inst✝⁴ : Group H
M✝ : Type x
inst✝³ : Monoid M✝
I : Type u_1
inst✝² : Group I
M : Subgroup H
O : Subgroup I
inst✝¹ : Subgroup.Normal M
inst✝ : Subgroup.Normal O
f : G →* H
g : H →* I
hf : N ≤ Subgroup.comap f M
hg : M ≤ Subgroup.comap g O
hgf : optParam (N ≤ Subgroup.comap (MonoidHom.comp g f) O) (_ : N ≤ Subgroup.comap (MonoidHom.comp g f) O)
x✝ : G ⧸ N
x : G
⊢ (map M O g hg) ((map N M f hf) ↑x) = (map N O (MonoidHom.comp g f) hgf) ↑x

Next tactic:
simp only [map_mk, MonoidHom.comp_apply]
/-- `QuotientGroup.congr` lifts the isomorphism `e : G ≃ H` to `G ⧸ G' ≃ H ⧸ H'`,
given that `e` maps `G` to `H`. -/
@[to_additive "`QuotientAddGroup.congr` lifts the isomorphism `e : G ≃ H` to `G ⧸ G' ≃ H ⧸ H'`,
 given that `e` maps `G` to `H`."]
def congr (e : G ≃* H) (he : G'.map e = H') : G ⧸ G' ≃* H ⧸ H' :=
  { map G' H' e (he ▸ G'.le_comap_map (e : G →* H)) with
    toFun := map G' H' e (he ▸ G'.le_comap_map (e : G →* H))
    invFun := map H' G' e.symm (he ▸ (G'.map_equiv_eq_comap_symm e).le)
    left_inv := fun x => by
      

Proof state:
G : Type u
inst✝⁴ : Group G
N : Subgroup G
nN : Subgroup.Normal N
H : Type v
inst✝³ : Group H
M : Type x
inst✝² : Monoid M
G' : Subgroup G
H' : Subgroup H
inst✝¹ : Subgroup.Normal G'
inst✝ : Subgroup.Normal H'
e : G ≃* H
he : Subgroup.map (↑e) G' = H'
src✝ : G ⧸ G' →* H ⧸ H' := map G' H' ↑e (_ : G' ≤ Subgroup.comap (↑e) H')
x : G ⧸ G'
⊢ (map H' G' ↑(MulEquiv.symm e) (_ : H' ≤ Subgroup.comap (↑(MulEquiv.symm e)) G'))
      ((map G' H' ↑e (_ : G' ≤ Subgroup.comap (↑e) H')) x) =
    x

Next tactic:
rw [map_map G' H' G' e e.symm (he ▸ G'.le_comap_map (e : G →* H))
        (he ▸ (G'.map_equiv_eq_comap_symm e).le)]
/-- `QuotientGroup.congr` lifts the isomorphism `e : G ≃ H` to `G ⧸ G' ≃ H ⧸ H'`,
given that `e` maps `G` to `H`. -/
@[to_additive "`QuotientAddGroup.congr` lifts the isomorphism `e : G ≃ H` to `G ⧸ G' ≃ H ⧸ H'`,
 given that `e` maps `G` to `H`."]
def congr (e : G ≃* H) (he : G'.map e = H') : G ⧸ G' ≃* H ⧸ H' :=
  { map G' H' e (he ▸ G'.le_comap_map (e : G →* H)) with
    toFun := map G' H' e (he ▸ G'.le_comap_map (e : G →* H))
    invFun := map H' G' e.symm (he ▸ (G'.map_equiv_eq_comap_symm e).le)
    left_inv := fun x => by
      rw [map_map G' H' G' e e.symm (he ▸ G'.le_comap_map (e : G →* H))
        (he ▸ (G'.map_equiv_eq_comap_symm e).le)]
      

Proof state:
G : Type u
inst✝⁴ : Group G
N : Subgroup G
nN : Subgroup.Normal N
H : Type v
inst✝³ : Group H
M : Type x
inst✝² : Monoid M
G' : Subgroup G
H' : Subgroup H
inst✝¹ : Subgroup.Normal G'
inst✝ : Subgroup.Normal H'
e : G ≃* H
he : Subgroup.map (↑e) G' = H'
src✝ : G ⧸ G' →* H ⧸ H' := map G' H' ↑e (_ : G' ≤ Subgroup.comap (↑e) H')
x : G ⧸ G'
⊢ (map G' G' (MonoidHom.comp ↑(MulEquiv.symm e) ↑e) (_ : G' ≤ Subgroup.comap (MonoidHom.comp ↑(MulEquiv.symm e) ↑e) G'))
      x =
    x

Next tactic:
simp only [map_map, ← MulEquiv.coe_monoidHom_trans, MulEquiv.self_trans_symm,
        MulEquiv.coe_monoidHom_refl, map_id_apply]
/-- `QuotientGroup.congr` lifts the isomorphism `e : G ≃ H` to `G ⧸ G' ≃ H ⧸ H'`,
given that `e` maps `G` to `H`. -/
@[to_additive "`QuotientAddGroup.congr` lifts the isomorphism `e : G ≃ H` to `G ⧸ G' ≃ H ⧸ H'`,
 given that `e` maps `G` to `H`."]
def congr (e : G ≃* H) (he : G'.map e = H') : G ⧸ G' ≃* H ⧸ H' :=
  { map G' H' e (he ▸ G'.le_comap_map (e : G →* H)) with
    toFun := map G' H' e (he ▸ G'.le_comap_map (e : G →* H))
    invFun := map H' G' e.symm (he ▸ (G'.map_equiv_eq_comap_symm e).le)
    left_inv := fun x => by
      rw [map_map G' H' G' e e.symm (he ▸ G'.le_comap_map (e : G →* H))
        (he ▸ (G'.map_equiv_eq_comap_symm e).le)]
      simp only [map_map, ← MulEquiv.coe_monoidHom_trans, MulEquiv.self_trans_symm,
        MulEquiv.coe_monoidHom_refl, map_id_apply]
    right_inv := fun x => by
      

Proof state:
G : Type u
inst✝⁴ : Group G
N : Subgroup G
nN : Subgroup.Normal N
H : Type v
inst✝³ : Group H
M : Type x
inst✝² : Monoid M
G' : Subgroup G
H' : Subgroup H
inst✝¹ : Subgroup.Normal G'
inst✝ : Subgroup.Normal H'
e : G ≃* H
he : Subgroup.map (↑e) G' = H'
src✝ : G ⧸ G' →* H ⧸ H' := map G' H' ↑e (_ : G' ≤ Subgroup.comap (↑e) H')
x : H ⧸ H'
⊢ (map G' H' ↑e (_ : G' ≤ Subgroup.comap (↑e) H'))
      ((map H' G' ↑(MulEquiv.symm e) (_ : H' ≤ Subgroup.comap (↑(MulEquiv.symm e)) G')) x) =
    x

Next tactic:
rw [map_map H' G' H' e.symm e (he ▸ (G'.map_equiv_eq_comap_symm e).le)
        (he ▸ G'.le_comap_map (e : G →* H)) ]
/-- `QuotientGroup.congr` lifts the isomorphism `e : G ≃ H` to `G ⧸ G' ≃ H ⧸ H'`,
given that `e` maps `G` to `H`. -/
@[to_additive "`QuotientAddGroup.congr` lifts the isomorphism `e : G ≃ H` to `G ⧸ G' ≃ H ⧸ H'`,
 given that `e` maps `G` to `H`."]
def congr (e : G ≃* H) (he : G'.map e = H') : G ⧸ G' ≃* H ⧸ H' :=
  { map G' H' e (he ▸ G'.le_comap_map (e : G →* H)) with
    toFun := map G' H' e (he ▸ G'.le_comap_map (e : G →* H))
    invFun := map H' G' e.symm (he ▸ (G'.map_equiv_eq_comap_symm e).le)
    left_inv := fun x => by
      rw [map_map G' H' G' e e.symm (he ▸ G'.le_comap_map (e : G →* H))
        (he ▸ (G'.map_equiv_eq_comap_symm e).le)]
      simp only [map_map, ← MulEquiv.coe_monoidHom_trans, MulEquiv.self_trans_symm,
        MulEquiv.coe_monoidHom_refl, map_id_apply]
    right_inv := fun x => by
      rw [map_map H' G' H' e.symm e (he ▸ (G'.map_equiv_eq_comap_symm e).le)
        (he ▸ G'.le_comap_map (e : G →* H)) ]
      

Proof state:
G : Type u
inst✝⁴ : Group G
N : Subgroup G
nN : Subgroup.Normal N
H : Type v
inst✝³ : Group H
M : Type x
inst✝² : Monoid M
G' : Subgroup G
H' : Subgroup H
inst✝¹ : Subgroup.Normal G'
inst✝ : Subgroup.Normal H'
e : G ≃* H
he : Subgroup.map (↑e) G' = H'
src✝ : G ⧸ G' →* H ⧸ H' := map G' H' ↑e (_ : G' ≤ Subgroup.comap (↑e) H')
x : H ⧸ H'
⊢ (map H' H' (MonoidHom.comp ↑e ↑(MulEquiv.symm e)) (_ : H' ≤ Subgroup.comap (MonoidHom.comp ↑e ↑(MulEquiv.symm e)) H'))
      x =
    x

Next tactic:
simp only [← MulEquiv.coe_monoidHom_trans, MulEquiv.symm_trans_self,
        MulEquiv.coe_monoidHom_refl, map_id_apply]
@[simp]
theorem congr_refl (he : G'.map (MulEquiv.refl G : G →* G) = G' := Subgroup.map_id G') :
    congr G' G' (MulEquiv.refl G) he = MulEquiv.refl (G ⧸ G') := by
  

Proof state:
G : Type u
inst✝⁴ : Group G
N : Subgroup G
nN : Subgroup.Normal N
H : Type v
inst✝³ : Group H
M : Type x
inst✝² : Monoid M
G' : Subgroup G
H' : Subgroup H
inst✝¹ : Subgroup.Normal G'
inst✝ : Subgroup.Normal H'
he : optParam (Subgroup.map (↑(MulEquiv.refl G)) G' = G') (_ : Subgroup.map (MonoidHom.id G) G' = G')
⊢ congr G' G' (MulEquiv.refl G) he = MulEquiv.refl (G ⧸ G')

Next tactic:
ext ⟨x⟩
@[simp]
theorem congr_refl (he : G'.map (MulEquiv.refl G : G →* G) = G' := Subgroup.map_id G') :
    congr G' G' (MulEquiv.refl G) he = MulEquiv.refl (G ⧸ G') := by
  ext ⟨x⟩
  

Proof state:
case h.mk
G : Type u
inst✝⁴ : Group G
N : Subgroup G
nN : Subgroup.Normal N
H : Type v
inst✝³ : Group H
M : Type x
inst✝² : Monoid M
G' : Subgroup G
H' : Subgroup H
inst✝¹ : Subgroup.Normal G'
inst✝ : Subgroup.Normal H'
he : optParam (Subgroup.map (↑(MulEquiv.refl G)) G' = G') (_ : Subgroup.map (MonoidHom.id G) G' = G')
x✝ : G ⧸ G'
x : G
⊢ (congr G' G' (MulEquiv.refl G) he) (Quot.mk Setoid.r x) = (MulEquiv.refl (G ⧸ G')) (Quot.mk Setoid.r x)

Next tactic:
rfl
@[to_additive]
theorem kerLift_injective : Injective (kerLift φ) := fun a b =>
  Quotient.inductionOn₂' a b fun a b (h : φ a = φ b) =>
    Quotient.sound' <| by 

Proof state:
G : Type u
inst✝² : Group G
N : Subgroup G
nN : Subgroup.Normal N
H : Type v
inst✝¹ : Group H
M : Type x
inst✝ : Monoid M
φ : G →* H
a✝ b✝ : G ⧸ ker φ
a b : G
h : φ a = φ b
⊢ Setoid.r a b

Next tactic:
rw [leftRel_apply, mem_ker, φ.map_mul, ← h, φ.map_inv, inv_mul_self]
/-- The induced map from the quotient by the kernel to the range. -/
@[to_additive "The induced map from the quotient by the kernel to the range."]
def rangeKerLift : G ⧸ ker φ →* φ.range :=
  lift _ φ.rangeRestrict fun g hg => (mem_ker _).mp <| by 

Proof state:
G : Type u
inst✝² : Group G
N : Subgroup G
nN : Subgroup.Normal N
H : Type v
inst✝¹ : Group H
M : Type x
inst✝ : Monoid M
φ : G →* H
g : G
hg : g ∈ ker φ
⊢ g ∈ ker (rangeRestrict φ)

Next tactic:
rwa [ker_rangeRestrict]
@[to_additive]
theorem rangeKerLift_injective : Injective (rangeKerLift φ) := fun a b =>
  Quotient.inductionOn₂' a b fun a b (h : φ.rangeRestrict a = φ.rangeRestrict b) =>
    Quotient.sound' <| by
      

Proof state:
G : Type u
inst✝² : Group G
N : Subgroup G
nN : Subgroup.Normal N
H : Type v
inst✝¹ : Group H
M : Type x
inst✝ : Monoid M
φ : G →* H
a✝ b✝ : G ⧸ ker φ
a b : G
h : (rangeRestrict φ) a = (rangeRestrict φ) b
⊢ Setoid.r a b

Next tactic:
rw [leftRel_apply, ← ker_rangeRestrict, mem_ker, φ.rangeRestrict.map_mul, ← h,
        φ.rangeRestrict.map_inv, inv_mul_self]
@[to_additive]
theorem rangeKerLift_surjective : Surjective (rangeKerLift φ) := by
  

Proof state:
G : Type u
inst✝² : Group G
N : Subgroup G
nN : Subgroup.Normal N
H : Type v
inst✝¹ : Group H
M : Type x
inst✝ : Monoid M
φ : G →* H
⊢ Surjective ⇑(rangeKerLift φ)

Next tactic:
rintro ⟨_, g, rfl⟩
@[to_additive]
theorem rangeKerLift_surjective : Surjective (rangeKerLift φ) := by
  rintro ⟨_, g, rfl⟩
  

Proof state:
case mk.intro
G : Type u
inst✝² : Group G
N : Subgroup G
nN : Subgroup.Normal N
H : Type v
inst✝¹ : Group H
M : Type x
inst✝ : Monoid M
φ : G →* H
g : G
⊢ ∃ a, (rangeKerLift φ) a = { val := φ g, property := (_ : ∃ y, φ y = φ g) }

Next tactic:
use mk g
@[to_additive]
theorem rangeKerLift_surjective : Surjective (rangeKerLift φ) := by
  rintro ⟨_, g, rfl⟩
  use mk g
  

Proof state:
case h
G : Type u
inst✝² : Group G
N : Subgroup G
nN : Subgroup.Normal N
H : Type v
inst✝¹ : Group H
M : Type x
inst✝ : Monoid M
φ : G →* H
g : G
⊢ (rangeKerLift φ) ↑g = { val := φ g, property := (_ : ∃ y, φ y = φ g) }

Next tactic:
rfl
/-- The canonical isomorphism `G/(ker φ) ≃* H` induced by a homomorphism `φ : G →* H`
with a right inverse `ψ : H → G`. -/
@[to_additive (attr := simps) "The canonical isomorphism `G/(ker φ) ≃+ H` induced by a homomorphism
`φ : G →+ H` with a right inverse `ψ : H → G`."]
def quotientKerEquivOfRightInverse (ψ : H → G) (hφ : RightInverse ψ φ) : G ⧸ ker φ ≃* H :=
  { kerLift φ with
    toFun := kerLift φ
    invFun := mk ∘ ψ
    left_inv := fun x => kerLift_injective φ (by 

Proof state:
G : Type u
inst✝² : Group G
N : Subgroup G
nN : Subgroup.Normal N
H : Type v
inst✝¹ : Group H
M : Type x
inst✝ : Monoid M
φ : G →* H
ψ : H → G
hφ : Function.RightInverse ψ ⇑φ
src✝ : G ⧸ ker φ →* H := kerLift φ
x : G ⧸ ker φ
⊢ (kerLift φ) ((mk ∘ ψ) ((kerLift φ) x)) = (kerLift φ) x

Next tactic:
rw [Function.comp_apply, kerLift_mk', hφ]
/-- Let `A', A, B', B` be subgroups of `G`.
If `A' = B'` and `A = B`, then the quotients `A / (A' ⊓ A)` and `B / (B' ⊓ B)` are isomorphic.

Applying this equiv is nicer than rewriting along the equalities, since the type of
`(A'.subgroupOf A : Subgroup A)` depends on `A`.
-/
@[to_additive "Let `A', A, B', B` be subgroups of `G`. If `A' = B'` and `A = B`, then the quotients
`A / (A' ⊓ A)` and `B / (B' ⊓ B)` are isomorphic. Applying this equiv is nicer than rewriting along
the equalities, since the type of `(A'.addSubgroupOf A : AddSubgroup A)` depends on on `A`. "]
def equivQuotientSubgroupOfOfEq {A' A B' B : Subgroup G} [hAN : (A'.subgroupOf A).Normal]
    [hBN : (B'.subgroupOf B).Normal] (h' : A' = B') (h : A = B) :
    A ⧸ A'.subgroupOf A ≃* B ⧸ B'.subgroupOf B :=
  MonoidHom.toMulEquiv (quotientMapSubgroupOfOfLe h'.le h.le) (quotientMapSubgroupOfOfLe h'.ge h.ge)
    (by 

Proof state:
G : Type u
inst✝² : Group G
N : Subgroup G
nN : Subgroup.Normal N
H : Type v
inst✝¹ : Group H
M : Type x
inst✝ : Monoid M
φ : G →* H
A' A B' B : Subgroup G
hAN : Subgroup.Normal (Subgroup.subgroupOf A' A)
hBN : Subgroup.Normal (Subgroup.subgroupOf B' B)
h' : A' = B'
h : A = B
⊢ MonoidHom.comp (quotientMapSubgroupOfOfLe (_ : B' ≤ A') (_ : B ≤ A))
      (quotientMapSubgroupOfOfLe (_ : A' ≤ B') (_ : A ≤ B)) =
    MonoidHom.id (↥A ⧸ Subgroup.subgroupOf A' A)

Next tactic:
ext ⟨x, hx⟩
/-- Let `A', A, B', B` be subgroups of `G`.
If `A' = B'` and `A = B`, then the quotients `A / (A' ⊓ A)` and `B / (B' ⊓ B)` are isomorphic.

Applying this equiv is nicer than rewriting along the equalities, since the type of
`(A'.subgroupOf A : Subgroup A)` depends on `A`.
-/
@[to_additive "Let `A', A, B', B` be subgroups of `G`. If `A' = B'` and `A = B`, then the quotients
`A / (A' ⊓ A)` and `B / (B' ⊓ B)` are isomorphic. Applying this equiv is nicer than rewriting along
the equalities, since the type of `(A'.addSubgroupOf A : AddSubgroup A)` depends on on `A`. "]
def equivQuotientSubgroupOfOfEq {A' A B' B : Subgroup G} [hAN : (A'.subgroupOf A).Normal]
    [hBN : (B'.subgroupOf B).Normal] (h' : A' = B') (h : A = B) :
    A ⧸ A'.subgroupOf A ≃* B ⧸ B'.subgroupOf B :=
  MonoidHom.toMulEquiv (quotientMapSubgroupOfOfLe h'.le h.le) (quotientMapSubgroupOfOfLe h'.ge h.ge)
    (by ext ⟨x, hx⟩; 

Proof state:
case h.h.mk
G : Type u
inst✝² : Group G
N : Subgroup G
nN : Subgroup.Normal N
H : Type v
inst✝¹ : Group H
M : Type x
inst✝ : Monoid M
φ : G →* H
A' A B' B : Subgroup G
hAN : Subgroup.Normal (Subgroup.subgroupOf A' A)
hBN : Subgroup.Normal (Subgroup.subgroupOf B' B)
h' : A' = B'
h : A = B
x : G
hx : x ∈ A
⊢ (MonoidHom.comp
        (MonoidHom.comp (quotientMapSubgroupOfOfLe (_ : B' ≤ A') (_ : B ≤ A))
          (quotientMapSubgroupOfOfLe (_ : A' ≤ B') (_ : A ≤ B)))
        (mk' (Subgroup.subgroupOf A' A)))
      { val := x, property := hx } =
    (MonoidHom.comp (MonoidHom.id (↥A ⧸ Subgroup.subgroupOf A' A)) (mk' (Subgroup.subgroupOf A' A)))
      { val := x, property := hx }

Next tactic:
rfl
/-- Let `A', A, B', B` be subgroups of `G`.
If `A' = B'` and `A = B`, then the quotients `A / (A' ⊓ A)` and `B / (B' ⊓ B)` are isomorphic.

Applying this equiv is nicer than rewriting along the equalities, since the type of
`(A'.subgroupOf A : Subgroup A)` depends on `A`.
-/
@[to_additive "Let `A', A, B', B` be subgroups of `G`. If `A' = B'` and `A = B`, then the quotients
`A / (A' ⊓ A)` and `B / (B' ⊓ B)` are isomorphic. Applying this equiv is nicer than rewriting along
the equalities, since the type of `(A'.addSubgroupOf A : AddSubgroup A)` depends on on `A`. "]
def equivQuotientSubgroupOfOfEq {A' A B' B : Subgroup G} [hAN : (A'.subgroupOf A).Normal]
    [hBN : (B'.subgroupOf B).Normal] (h' : A' = B') (h : A = B) :
    A ⧸ A'.subgroupOf A ≃* B ⧸ B'.subgroupOf B :=
  MonoidHom.toMulEquiv (quotientMapSubgroupOfOfLe h'.le h.le) (quotientMapSubgroupOfOfLe h'.ge h.ge)
    (by ext ⟨x, hx⟩; rfl)
    (by 

Proof state:
G : Type u
inst✝² : Group G
N : Subgroup G
nN : Subgroup.Normal N
H : Type v
inst✝¹ : Group H
M : Type x
inst✝ : Monoid M
φ : G →* H
A' A B' B : Subgroup G
hAN : Subgroup.Normal (Subgroup.subgroupOf A' A)
hBN : Subgroup.Normal (Subgroup.subgroupOf B' B)
h' : A' = B'
h : A = B
⊢ MonoidHom.comp (quotientMapSubgroupOfOfLe (_ : A' ≤ B') (_ : A ≤ B))
      (quotientMapSubgroupOfOfLe (_ : B' ≤ A') (_ : B ≤ A)) =
    MonoidHom.id (↥B ⧸ Subgroup.subgroupOf B' B)

Next tactic:
ext ⟨x, hx⟩
/-- Let `A', A, B', B` be subgroups of `G`.
If `A' = B'` and `A = B`, then the quotients `A / (A' ⊓ A)` and `B / (B' ⊓ B)` are isomorphic.

Applying this equiv is nicer than rewriting along the equalities, since the type of
`(A'.subgroupOf A : Subgroup A)` depends on `A`.
-/
@[to_additive "Let `A', A, B', B` be subgroups of `G`. If `A' = B'` and `A = B`, then the quotients
`A / (A' ⊓ A)` and `B / (B' ⊓ B)` are isomorphic. Applying this equiv is nicer than rewriting along
the equalities, since the type of `(A'.addSubgroupOf A : AddSubgroup A)` depends on on `A`. "]
def equivQuotientSubgroupOfOfEq {A' A B' B : Subgroup G} [hAN : (A'.subgroupOf A).Normal]
    [hBN : (B'.subgroupOf B).Normal] (h' : A' = B') (h : A = B) :
    A ⧸ A'.subgroupOf A ≃* B ⧸ B'.subgroupOf B :=
  MonoidHom.toMulEquiv (quotientMapSubgroupOfOfLe h'.le h.le) (quotientMapSubgroupOfOfLe h'.ge h.ge)
    (by ext ⟨x, hx⟩; rfl)
    (by ext ⟨x, hx⟩; 

Proof state:
case h.h.mk
G : Type u
inst✝² : Group G
N : Subgroup G
nN : Subgroup.Normal N
H : Type v
inst✝¹ : Group H
M : Type x
inst✝ : Monoid M
φ : G →* H
A' A B' B : Subgroup G
hAN : Subgroup.Normal (Subgroup.subgroupOf A' A)
hBN : Subgroup.Normal (Subgroup.subgroupOf B' B)
h' : A' = B'
h : A = B
x : G
hx : x ∈ B
⊢ (MonoidHom.comp
        (MonoidHom.comp (quotientMapSubgroupOfOfLe (_ : A' ≤ B') (_ : A ≤ B))
          (quotientMapSubgroupOfOfLe (_ : B' ≤ A') (_ : B ≤ A)))
        (mk' (Subgroup.subgroupOf B' B)))
      { val := x, property := hx } =
    (MonoidHom.comp (MonoidHom.id (↥B ⧸ Subgroup.subgroupOf B' B)) (mk' (Subgroup.subgroupOf B' B)))
      { val := x, property := hx }

Next tactic:
rfl
/-- The map of quotients by powers of an integer induced by a group homomorphism. -/
@[to_additive "The map of quotients by multiples of an integer induced by an additive group
homomorphism."]
def homQuotientZPowOfHom :
    A ⧸ (zpowGroupHom n : A →* A).range →* B ⧸ (zpowGroupHom n : B →* B).range :=
  lift _ ((mk' _).comp f) fun g ⟨h, (hg : h ^ n = g)⟩ =>
    (eq_one_iff _).mpr ⟨f h, by
      

Proof state:
G : Type u
inst✝⁵ : Group G
N : Subgroup G
nN : Subgroup.Normal N
H : Type v
inst✝⁴ : Group H
M : Type x
inst✝³ : Monoid M
φ : G →* H
A B C : Type u
inst✝² : CommGroup A
inst✝¹ : CommGroup B
inst✝ : CommGroup C
f : A →* B
g✝ : B →* A
e : A ≃* B
d : B ≃* C
n : ℤ
g : A
x✝ : g ∈ range (zpowGroupHom n)
h : A
hg : h ^ n = g
⊢ (zpowGroupHom n) (f h) = f g

Next tactic:
simp only [← hg, map_zpow, zpowGroupHom_apply]
@[to_additive (attr := simp)]
theorem equivQuotientZPowOfEquiv_refl :
    MulEquiv.refl (A ⧸ (zpowGroupHom n : A →* A).range) =
      equivQuotientZPowOfEquiv (MulEquiv.refl A) n := by
  

Proof state:
G : Type u
inst✝⁵ : Group G
N : Subgroup G
nN : Subgroup.Normal N
H : Type v
inst✝⁴ : Group H
M : Type x
inst✝³ : Monoid M
φ : G →* H
A B C : Type u
inst✝² : CommGroup A
inst✝¹ : CommGroup B
inst✝ : CommGroup C
f : A →* B
g : B →* A
e : A ≃* B
d : B ≃* C
n : ℤ
⊢ MulEquiv.refl (A ⧸ range (zpowGroupHom n)) = equivQuotientZPowOfEquiv (MulEquiv.refl A) n

Next tactic:
ext x
@[to_additive (attr := simp)]
theorem equivQuotientZPowOfEquiv_refl :
    MulEquiv.refl (A ⧸ (zpowGroupHom n : A →* A).range) =
      equivQuotientZPowOfEquiv (MulEquiv.refl A) n := by
  ext x
  

Proof state:
case h
G : Type u
inst✝⁵ : Group G
N : Subgroup G
nN : Subgroup.Normal N
H : Type v
inst✝⁴ : Group H
M : Type x
inst✝³ : Monoid M
φ : G →* H
A B C : Type u
inst✝² : CommGroup A
inst✝¹ : CommGroup B
inst✝ : CommGroup C
f : A →* B
g : B →* A
e : A ≃* B
d : B ≃* C
n : ℤ
x : A ⧸ range (zpowGroupHom n)
⊢ (MulEquiv.refl (A ⧸ range (zpowGroupHom n))) x = (equivQuotientZPowOfEquiv (MulEquiv.refl A) n) x

Next tactic:
rw [← Quotient.out_eq' x]
@[to_additive (attr := simp)]
theorem equivQuotientZPowOfEquiv_refl :
    MulEquiv.refl (A ⧸ (zpowGroupHom n : A →* A).range) =
      equivQuotientZPowOfEquiv (MulEquiv.refl A) n := by
  ext x
  rw [← Quotient.out_eq' x]
  

Proof state:
case h
G : Type u
inst✝⁵ : Group G
N : Subgroup G
nN : Subgroup.Normal N
H : Type v
inst✝⁴ : Group H
M : Type x
inst✝³ : Monoid M
φ : G →* H
A B C : Type u
inst✝² : CommGroup A
inst✝¹ : CommGroup B
inst✝ : CommGroup C
f : A →* B
g : B →* A
e : A ≃* B
d : B ≃* C
n : ℤ
x : A ⧸ range (zpowGroupHom n)
⊢ (MulEquiv.refl (A ⧸ range (zpowGroupHom n))) (Quotient.mk'' (Quotient.out' x)) =
    (equivQuotientZPowOfEquiv (MulEquiv.refl A) n) (Quotient.mk'' (Quotient.out' x))

Next tactic:
rfl
@[to_additive (attr := simp)]
theorem equivQuotientZPowOfEquiv_trans :
    (equivQuotientZPowOfEquiv e n).trans (equivQuotientZPowOfEquiv d n) =
      equivQuotientZPowOfEquiv (e.trans d) n := by
  

Proof state:
G : Type u
inst✝⁵ : Group G
N : Subgroup G
nN : Subgroup.Normal N
H : Type v
inst✝⁴ : Group H
M : Type x
inst✝³ : Monoid M
φ : G →* H
A B C : Type u
inst✝² : CommGroup A
inst✝¹ : CommGroup B
inst✝ : CommGroup C
f : A →* B
g : B →* A
e : A ≃* B
d : B ≃* C
n : ℤ
⊢ MulEquiv.trans (equivQuotientZPowOfEquiv e n) (equivQuotientZPowOfEquiv d n) =
    equivQuotientZPowOfEquiv (MulEquiv.trans e d) n

Next tactic:
ext x
@[to_additive (attr := simp)]
theorem equivQuotientZPowOfEquiv_trans :
    (equivQuotientZPowOfEquiv e n).trans (equivQuotientZPowOfEquiv d n) =
      equivQuotientZPowOfEquiv (e.trans d) n := by
  ext x
  

Proof state:
case h
G : Type u
inst✝⁵ : Group G
N : Subgroup G
nN : Subgroup.Normal N
H : Type v
inst✝⁴ : Group H
M : Type x
inst✝³ : Monoid M
φ : G →* H
A B C : Type u
inst✝² : CommGroup A
inst✝¹ : CommGroup B
inst✝ : CommGroup C
f : A →* B
g : B →* A
e : A ≃* B
d : B ≃* C
n : ℤ
x : A ⧸ range (zpowGroupHom n)
⊢ (MulEquiv.trans (equivQuotientZPowOfEquiv e n) (equivQuotientZPowOfEquiv d n)) x =
    (equivQuotientZPowOfEquiv (MulEquiv.trans e d) n) x

Next tactic:
rw [← Quotient.out_eq' x]
@[to_additive (attr := simp)]
theorem equivQuotientZPowOfEquiv_trans :
    (equivQuotientZPowOfEquiv e n).trans (equivQuotientZPowOfEquiv d n) =
      equivQuotientZPowOfEquiv (e.trans d) n := by
  ext x
  rw [← Quotient.out_eq' x]
  

Proof state:
case h
G : Type u
inst✝⁵ : Group G
N : Subgroup G
nN : Subgroup.Normal N
H : Type v
inst✝⁴ : Group H
M : Type x
inst✝³ : Monoid M
φ : G →* H
A B C : Type u
inst✝² : CommGroup A
inst✝¹ : CommGroup B
inst✝ : CommGroup C
f : A →* B
g : B →* A
e : A ≃* B
d : B ≃* C
n : ℤ
x : A ⧸ range (zpowGroupHom n)
⊢ (MulEquiv.trans (equivQuotientZPowOfEquiv e n) (equivQuotientZPowOfEquiv d n)) (Quotient.mk'' (Quotient.out' x)) =
    (equivQuotientZPowOfEquiv (MulEquiv.trans e d) n) (Quotient.mk'' (Quotient.out' x))

Next tactic:
rfl
/-- **Noether's second isomorphism theorem**: given two subgroups `H` and `N` of a group `G`, where
`N` is normal, defines an isomorphism between `H/(H ∩ N)` and `(HN)/N`. -/
@[to_additive "The second isomorphism theorem: given two subgroups `H` and `N` of a group `G`, where
`N` is normal, defines an isomorphism between `H/(H ∩ N)` and `(H + N)/N`"]
noncomputable def quotientInfEquivProdNormalQuotient (H N : Subgroup G) [N.Normal] :
    H ⧸ N.subgroupOf H ≃* _ ⧸ N.subgroupOf (H ⊔ N) :=
  let
    φ :-- φ is the natural homomorphism H →* (HN)/N.
      H →*
      _ ⧸ N.subgroupOf (H ⊔ N) :=
    (mk' <| N.subgroupOf (H ⊔ N)).comp (inclusion le_sup_left)
  have φ_surjective : Surjective φ := fun x =>
    x.inductionOn' <| by
      

Proof state:
G : Type u
inst✝³ : Group G
N✝ : Subgroup G
nN : Normal N✝
H✝ : Type v
inst✝² : Group H✝
M : Type x
inst✝¹ : Monoid M
φ✝ : G →* H✝
H N : Subgroup G
inst✝ : Normal N
φ : ↥H →* ↥(H ⊔ N) ⧸ subgroupOf N (H ⊔ N) := MonoidHom.comp (mk' (subgroupOf N (H ⊔ N))) (inclusion (_ : H ≤ H ⊔ N))
x : ↥(H ⊔ N) ⧸ subgroupOf N (H ⊔ N)
⊢ ∀ (a : ↥(H ⊔ N)), ∃ a_1, φ a_1 = Quotient.mk'' a

Next tactic:
rintro ⟨y, hy : y ∈ (H ⊔ N)⟩