topology.homotopy.homotopy_groupMathlib.Topology.Homotopy.HomotopyGroup

This file has been ported!

Changes since the initial port

The following section lists changes to this file in mathlib3 and mathlib4 that occured after the initial port. Most recent changes are shown first. Hovering over a commit will show all commits associated with the same mathlib3 commit.

Changes in mathlib3

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(last sync)

feat(topology/homotopy/homotopy_group): group and comm_group instances for π_n (#15681)

This PR adds:

  • Group instance for π_(n+1)
  • Commutative group instance for π_(n+2)

Co-authored-by: Junyan Xu <junyanxu.math@gmail.com> Co-authored-by: Junyan Xu <junyanxumath@gmail.com> Co-authored-by: Kevin Buzzard <k.buzzard@imperial.ac.uk>

Diff
@@ -5,6 +5,9 @@ Authors: Roberto Alvarez
 -/
 
 import algebraic_topology.fundamental_groupoid.fundamental_group
+import group_theory.eckmann_hilton
+import logic.equiv.transfer_instance
+import algebra.group.ext
 
 /-!
 # `n`th homotopy group
@@ -12,187 +15,455 @@ import algebraic_topology.fundamental_groupoid.fundamental_group
 > THIS FILE IS SYNCHRONIZED WITH MATHLIB4.
 > Any changes to this file require a corresponding PR to mathlib4.
 
-We define the `n`th homotopy group at `x`, `π n x`, as the equivalence classes
-of functions from the nth dimensional cube to the topological space `X`
+We define the `n`th homotopy group at `x : X`, `π_n X x`, as the equivalence classes
+of functions from the `n`-dimensional cube to the topological space `X`
 that send the boundary to the base point `x`, up to homotopic equivalence.
-Note that such functions are generalized loops `gen_loop n x`, in particular
-`gen_loop 1 x ≃ path x x`
+Note that such functions are generalized loops `gen_loop (fin n) x`; in particular
+`gen_loop (fin 1) x ≃ path x x`.
 
-We show that `π 0 x` is equivalent to the path-conected components, and
-that `π 1 x` is equivalent to the fundamental group at `x`.
+We show that `π_0 X x` is equivalent to the path-connected components, and
+that `π_1 X x` is equivalent to the fundamental group at `x`.
+We provide a group instance using path composition and show commutativity when `n > 1`.
 
 ## definitions
 
-* `gen_loop n x` is the type of continous fuctions `I^n → X` that send the boundary to `x`
-* `homotopy_group n x` denoted `π n x` is the quotient of `gen_loop n x` by homotopy relative
-  to the boundary
+* `gen_loop N x` is the type of continuous fuctions `I^N → X` that send the boundary to `x`,
+* `homotopy_group.pi n X x` denoted `π_ n X x` is the quotient of `gen_loop (fin n) x` by
+  homotopy relative to the boundary,
+* group instance `group (π_(n+1) X x)`,
+* commutative group instance `comm_group (π_(n+2) X x)`.
 
-TODO: show that `π n x` is a group when `n > 0` and abelian when `n > 1`. Show that
-`pi1_equiv_fundamental_group` is an isomorphism of groups.
+TODO:
+* `Ω^M (Ω^N X) ≃ₜ Ω^(M⊕N) X`, and `Ω^M X ≃ₜ Ω^N X` when `M ≃ N`. Similarly for `π_`.
+* Path-induced homomorphisms. Show that `homotopy_group.pi_1_equiv_fundamental_group`
+  is a group isomorphism.
+* Examples with `𝕊^n`: `π_n (𝕊^n) = ℤ`, `π_m (𝕊^n)` trivial for `m < n`.
+* Actions of π_1 on π_n.
+* Lie algebra: `⁅π_(n+1), π_(m+1)⁆` contained in `π_(n+m+1)`.
 
 -/
 
 open_locale unit_interval topology
+open homeomorph
 
 noncomputable theory
 
-universes u
-variables {X : Type u} [topological_space X]
-variables {n : ℕ} {x : X}
-
-/--
-The `n`-dimensional cube.
--/
-@[derive [has_zero, has_one, topological_space]]
-def cube (n : ℕ) : Type := fin n → I
-local notation `I^` := cube
+localized "notation `I^` N := N → I" in topology
 
 namespace cube
 
-@[continuity] lemma proj_continuous (i : fin n) : continuous (λ f : I^n, f i) :=
-continuous_apply i
+/-- The points in a cube with at least one projection equal to 0 or 1. -/
+def boundary (N : Type*) : set (I^N) := {y | ∃ i, y i = 0 ∨ y i = 1}
 
-/--
-The points of the `n`-dimensional cube with at least one projection equal to 0 or 1.
--/
-def boundary (n : ℕ) : set (I^n) := {y | ∃ i, y i = 0 ∨ y i = 1}
+variables {N : Type*} [decidable_eq N]
 
-/--
-The first projection of a positive-dimensional cube.
--/
-@[simp] def head {n} : I^(n+1) → I := λ c, c 0
+/-- The forward direction of the homeomorphism
+  between the cube $I^N$ and $I × I^{N\setminus\{j\}}$. -/
+@[reducible] def split_at (i : N) : (I^N) ≃ₜ I × I^{j // j ≠ i} := fun_split_at I i
 
-@[continuity] lemma head.continuous {n} : continuous (@head n) := proj_continuous 0
+/-- The backward direction of the homeomorphism
+  between the cube $I^N$ and $I × I^{N\setminus\{j\}}$. -/
+@[reducible] def insert_at (i : N) : I × (I^{j // j ≠ i}) ≃ₜ I^N := (fun_split_at I i).symm
 
-/--
-The projection to the last `n` coordinates from an `n+1` dimensional cube.
--/
-@[simp] def tail {n} : I^(n+1) → I^n := λ c, fin.tail c
+lemma insert_at_boundary (i : N) {t₀ : I} {t} (H : (t₀ = 0 ∨ t₀ = 1) ∨ t ∈ boundary {j // j ≠ i}) :
+  insert_at i ⟨t₀, t⟩ ∈ boundary N :=
+begin
+  obtain H | ⟨j, H⟩ := H,
+  { use i, rwa [fun_split_at_symm_apply, dif_pos rfl] },
+  { use j, rwa [fun_split_at_symm_apply, dif_neg j.prop, subtype.coe_eta] },
+end
 
-instance unique_cube0 : unique (I^0) := pi.unique_of_is_empty _
+end cube
 
-lemma one_char (f : I^1) : f = λ _, f 0 := by convert eq_const_of_unique f
+variables (N X : Type*) [topological_space X] (x : X)
 
-end cube
+/-- The space of paths with both endpoints equal to a specified point `x : X`. -/
+@[reducible] def loop_space := path x x
+localized "notation `Ω` := loop_space" in topology
 
-/--
-The `n`-dimensional generalized loops; functions `I^n → X` that send the boundary to the base point.
--/
-structure gen_loop (n : ℕ) (x : X) extends C(I^n, X) :=
-(boundary : ∀ y ∈ cube.boundary n, to_fun y = x)
+instance loop_space.inhabited : inhabited (path x x) := ⟨path.refl x⟩
+
+/-- The `n`-dimensional generalized loops based at `x` in a space `X` are
+  continuous functions `I^n → X` that sends the boundary to `x`.
+  We allow an arbitrary indexing type `N` in place of `fin n` here. -/
+def gen_loop : set C(I^N, X) := {p | ∀ y ∈ cube.boundary N, p y = x}
+localized "notation `Ω^` := gen_loop" in topology
+
+variables {N X x}
 
 namespace gen_loop
 
-instance fun_like : fun_like (gen_loop n x) (I^n) (λ _, X) :=
+/-- Copy of a `gen_loop` with a new map from the unit cube equal to the old one.
+  Useful to fix definitional equalities. -/
+def copy (f : Ω^N X x) (g : (I^N) → X) (h : g = f) : Ω^N X x :=
+⟨⟨g, h.symm ▸ f.1.2⟩, by { convert f.2, ext1, simp_rw h, refl }⟩
+
+lemma coe_copy (f : Ω^N X x) {g : (I^N) → X} (h : g = f) : ⇑(copy f g h) = g := rfl
+
+lemma copy_eq (f : Ω^N X x) {g : (I^N) → X} (h : g = f) : copy f g h = f :=
+by { ext x, exact congr_fun h x }
+
+lemma boundary (f : Ω^N X x) : ∀ y ∈ cube.boundary N, f y = x := f.2
+
+instance fun_like : fun_like (Ω^N X x) (I^N) (λ _, X) :=
 { coe := λ f, f.1,
   coe_injective' := λ ⟨⟨f, _⟩, _⟩ ⟨⟨g, _⟩, _⟩ h, by { congr, exact h } }
 
-@[ext] lemma ext (f g : gen_loop n x) (H : ∀ y, f y = g y) : f = g := fun_like.ext f g H
+@[ext] lemma ext (f g : Ω^N X x) (H : ∀ y, f y = g y) : f = g :=
+fun_like.coe_injective' (funext H)
 
-@[simp] lemma mk_apply (f : C(I^n, X)) (H y) : (⟨f, H⟩ : gen_loop n x) y = f y := rfl
+@[simp] lemma mk_apply (f : C(I^N, X)) (H y) : (⟨f, H⟩ : Ω^N X x) y = f y := rfl
 
-/--
-The constant `gen_loop` at `x`.
--/
-def const : gen_loop n x := ⟨continuous_map.const _ x, λ _ _, rfl⟩
+/-- The constant `gen_loop` at `x`. -/
+def const : Ω^N X x := ⟨continuous_map.const _ x, λ _ _, rfl⟩
 
-instance inhabited : inhabited (gen_loop n x) := { default := const }
+@[simp] lemma const_apply {t} : (@const N X _ x) t = x := rfl
 
-/--
-The "homotopy relative to boundary" relation between `gen_loop`s.
--/
-def homotopic (f g : gen_loop n x) : Prop :=
-f.to_continuous_map.homotopic_rel g.to_continuous_map (cube.boundary n)
+instance inhabited : inhabited (Ω^N X x) := ⟨const⟩
+
+/-- The "homotopic relative to boundary" relation between `gen_loop`s. -/
+def homotopic (f g : Ω^N X x) : Prop := f.1.homotopic_rel g.1 (cube.boundary N)
 
 namespace homotopic
-section
-variables {f g h : gen_loop n x}
 
-@[refl] lemma refl (f : gen_loop n x) : homotopic f f := continuous_map.homotopic_rel.refl _
+variables {f g h : Ω^N X x}
+
+@[refl] lemma refl (f : Ω^N X x) : homotopic f f := continuous_map.homotopic_rel.refl _
 
-@[symm] lemma symm (H : f.homotopic g) : g.homotopic f := H.symm
+@[symm] lemma symm (H : homotopic f g) : homotopic g f := H.symm
 
-@[trans] lemma trans (H0 : f.homotopic g) (H1 : g.homotopic h) : f.homotopic h := H0.trans H1
+@[trans] lemma trans (H0 : homotopic f g) (H1 : homotopic g h) : homotopic f h := H0.trans H1
 
-lemma equiv : equivalence (@homotopic X _ n x) :=
+lemma equiv : equivalence (@homotopic N X _ x) :=
 ⟨homotopic.refl, λ _ _, homotopic.symm, λ _ _ _, homotopic.trans⟩
 
-instance setoid (n : ℕ) (x : X) : setoid (gen_loop n x) := ⟨homotopic, equiv⟩
+instance setoid (N) (x : X) : setoid (Ω^N X x) := ⟨homotopic, equiv⟩
 
-end
 end homotopic
 
+section loop_homeo
+
+variable [decidable_eq N]
+
+/-- Loop from a generalized loop by currying $I^N → X$ into $I → (I^{N\setminus\{j\}} → X)$. -/
+@[simps] def to_loop (i : N) (p : Ω^N X x) : Ω (Ω^{j // j ≠ i} X x) const :=
+{ to_fun := λ t, ⟨(p.val.comp (cube.insert_at i).to_continuous_map).curry t,
+    λ y yH, p.property (cube.insert_at i (t, y)) (cube.insert_at_boundary i $ or.inr yH)⟩,
+  source' := by { ext t, refine p.property (cube.insert_at i (0, t)) ⟨i, or.inl _⟩, simp },
+  target' := by { ext t, refine p.property (cube.insert_at i (1, t)) ⟨i, or.inr _⟩, simp } }
+
+lemma continuous_to_loop (i : N) : continuous (@to_loop N X _ x _ i) :=
+path.continuous_uncurry_iff.1 $ continuous.subtype_mk (continuous_map.continuous_eval'.comp $
+  continuous.prod_map (continuous_map.continuous_curry.comp $
+    (continuous_map.continuous_comp_left _).comp continuous_subtype_coe) continuous_id) _
+
+/-- Generalized loop from a loop by uncurrying $I → (I^{N\setminus\{j\}} → X)$ into $I^N → X$. -/
+@[simps] def from_loop (i : N) (p : Ω (Ω^{j // j ≠ i} X x) const) : Ω^N X x :=
+⟨(continuous_map.comp ⟨coe⟩ p.to_continuous_map).uncurry.comp (cube.split_at i).to_continuous_map,
+begin
+  rintros y ⟨j, Hj⟩,
+  simp only [subtype.val_eq_coe, continuous_map.comp_apply, to_continuous_map_apply,
+    fun_split_at_apply, continuous_map.uncurry_apply, continuous_map.coe_mk,
+    function.uncurry_apply_pair],
+  obtain rfl | Hne := eq_or_ne j i,
+  { cases Hj; simpa only [Hj, p.coe_to_continuous_map, p.source, p.target] },
+  { exact gen_loop.boundary _ _ ⟨⟨j, Hne⟩, Hj⟩ },
+end⟩
+
+lemma continuous_from_loop (i : N) : continuous (@from_loop N X _ x _ i) :=
+((continuous_map.continuous_comp_left _).comp $ continuous_map.continuous_uncurry.comp $
+  (continuous_map.continuous_comp _).comp continuous_induced_dom).subtype_mk _
+
+lemma to_from (i : N) (p : Ω (Ω^{j // j ≠ i} X x) const) : to_loop i (from_loop i p) = p :=
+begin
+  simp_rw [to_loop, from_loop, continuous_map.comp_assoc, to_continuous_map_as_coe,
+    to_continuous_map_comp_symm, continuous_map.comp_id], ext, refl,
+end
+
+/-- The `n+1`-dimensional loops are in bijection with the loops in the space of
+  `n`-dimensional loops with base point `const`.
+  We allow an arbitrary indexing type `N` in place of `fin n` here. -/
+@[simps] def loop_homeo (i : N) : Ω^N X x ≃ₜ Ω (Ω^{j // j ≠ i} X x) const :=
+{ to_fun := to_loop i,
+  inv_fun := from_loop i,
+  left_inv := λ p, by { ext, exact congr_arg p (equiv.apply_symm_apply _ _) },
+  right_inv := to_from i,
+  continuous_to_fun := continuous_to_loop i,
+  continuous_inv_fun := continuous_from_loop i }
+
+lemma to_loop_apply (i : N) {p : Ω^N X x} {t} {tn} :
+  to_loop i p t tn = p (cube.insert_at i ⟨t, tn⟩) := rfl
+
+lemma from_loop_apply (i : N) {p : Ω (Ω^{j // j ≠ i} X x) const} {t : I^N} :
+  from_loop i p t = p (t i) (cube.split_at i t).snd := rfl
+
+/-- Composition with `cube.insert_at` as a continuous map. -/
+@[reducible] def c_comp_insert (i : N) : C(C(I^N, X), C(I × I^{j // j ≠ i}, X)) :=
+⟨λ f, f.comp (cube.insert_at i).to_continuous_map,
+  (cube.insert_at i).to_continuous_map.continuous_comp_left⟩
+
+/-- A homotopy between `n+1`-dimensional loops `p` and `q` constant on the boundary
+  seen as a homotopy between two paths in the space of `n`-dimensional paths. -/
+def homotopy_to (i : N) {p q : Ω^N X x} (H : p.1.homotopy_rel q.1 (cube.boundary N)) :
+  C(I × I, C(I^{j // j ≠ i}, X)) :=
+((⟨_, continuous_map.continuous_curry⟩: C(_,_)).comp $
+  (c_comp_insert i).comp H.to_continuous_map.curry).uncurry
+
+-- Should be generated with `@[simps]` but it times out.
+lemma homotopy_to_apply (i : N) {p q : Ω^N X x} (H : p.1.homotopy_rel q.1 $ cube.boundary N)
+  (t : I × I) (tₙ : I^{j // j ≠ i}) :
+  homotopy_to i H t tₙ = H (t.fst, cube.insert_at i (t.snd, tₙ)) := rfl
+
+lemma homotopic_to (i : N) {p q : Ω^N X x} :
+  homotopic p q → (to_loop i p).homotopic (to_loop i q) :=
+begin
+  refine nonempty.map (λ H, ⟨⟨⟨λ t, ⟨homotopy_to i H t, _⟩, _⟩, _, _⟩, _⟩),
+  { rintros y ⟨i, iH⟩,
+    rw [homotopy_to_apply, H.eq_fst, p.2],
+    all_goals { apply cube.insert_at_boundary, right, exact ⟨i, iH⟩} },
+  { continuity },
+  show ∀ _ _ _, _,
+  { intros t y yH,
+    split; ext; erw homotopy_to_apply,
+    apply H.eq_fst, work_on_goal 2 { apply H.eq_snd },
+    all_goals { use i, rw [fun_split_at_symm_apply, dif_pos rfl], exact yH } },
+  all_goals { intro, ext, erw [homotopy_to_apply, to_loop_apply] },
+  exacts [H.apply_zero _, H.apply_one _],
+end
+
+/-- The converse to `gen_loop.homotopy_to`: a homotopy between two loops in the space of
+  `n`-dimensional loops can be seen as a homotopy between two `n+1`-dimensional paths. -/
+def homotopy_from (i : N) {p q : Ω^N X x}
+  (H : (to_loop i p).homotopy (to_loop i q)) : C(I × I^N, X) :=
+(continuous_map.comp ⟨_, continuous_map.continuous_uncurry⟩
+  (continuous_map.comp ⟨coe⟩ H.to_continuous_map).curry).uncurry.comp $
+    (continuous_map.id I).prod_map (cube.split_at i).to_continuous_map
+
+-- Should be generated with `@[simps]` but it times out.
+lemma homotopy_from_apply (i : N) {p q : Ω^N X x} (H : (to_loop i p).homotopy (to_loop i q))
+  (t : I × I^N) : homotopy_from i H t = H (t.fst, t.snd i) (λ j, t.snd ↑j) := rfl
+
+lemma homotopic_from (i : N) {p q : Ω^N X x} :
+  (to_loop i p).homotopic (to_loop i q) → homotopic p q :=
+begin
+  refine nonempty.map (λ H, ⟨⟨homotopy_from i H, _, _⟩, _⟩),
+  show ∀ _ _ _, _,
+  { rintros t y ⟨j, jH⟩,
+    erw homotopy_from_apply,
+    obtain rfl | h := eq_or_ne j i,
+    { split,
+      { rw H.eq_fst, exacts [congr_arg p (equiv.right_inv _ _), jH] },
+      { rw H.eq_snd, exacts [congr_arg q (equiv.right_inv _ _), jH] } },
+    { rw [p.2 _ ⟨j, jH⟩, q.2 _ ⟨j, jH⟩], split; { apply boundary, exact ⟨⟨j, h⟩, jH⟩ } } },
+  all_goals { intro,
+    convert homotopy_from_apply _ _ _,
+    rw H.apply_zero <|> rw H.apply_one,
+    apply congr_arg p <|> apply congr_arg q,
+    exact (equiv.right_inv _ _).symm },
+end
+
+/-- Concatenation of two `gen_loop`s along the `i`th coordinate. -/
+def trans_at (i : N) (f g : Ω^N X x) : Ω^N X x :=
+copy (from_loop i $ (to_loop i f).trans $ to_loop i g)
+  (λ t, if (t i : ℝ) ≤ 1/2
+    then f (t.update i $ set.proj_Icc 0 1 zero_le_one (2 * t i))
+    else g (t.update i $ set.proj_Icc 0 1 zero_le_one (2 * t i - 1)))
+begin
+  ext1, symmetry,
+  dsimp only [path.trans, from_loop, path.coe_mk, function.comp_app,
+    mk_apply, continuous_map.comp_apply, to_continuous_map_apply, fun_split_at_apply,
+    continuous_map.uncurry_apply, continuous_map.coe_mk, function.uncurry_apply_pair],
+  split_ifs, change f _ = _, swap, change g _ = _,
+  all_goals { congr' 1 }
+end
+
+/-- Reversal of a `gen_loop` along the `i`th coordinate. -/
+def symm_at (i : N) (f : Ω^N X x) : Ω^N X x :=
+copy (from_loop i (to_loop i f).symm)
+  (λ t, f $ λ j, if j = i then σ (t i) else t j) $
+  by { ext1, change _ = f _, congr, ext1, simp }
+
+lemma trans_at_distrib {i j : N} (h : i ≠ j) (a b c d : Ω^N X x) :
+  trans_at i (trans_at j a b) (trans_at j c d) = trans_at j (trans_at i a c) (trans_at i b d) :=
+begin
+  ext, simp_rw [trans_at, coe_copy, function.update_apply, if_neg h, if_neg h.symm],
+  split_ifs; { congr' 1, ext1, simp only [function.update, eq_rec_constant, dite_eq_ite],
+    apply ite_ite_comm, rintro rfl, exact h.symm },
+end
+
+lemma from_loop_trans_to_loop {i : N} {p q : Ω^N X x} :
+  from_loop i ((to_loop i p).trans $ to_loop i q) = trans_at i p q :=
+(copy_eq _ _).symm
+
+lemma from_loop_symm_to_loop {i : N} {p : Ω^N X x} :
+  from_loop i (to_loop i p).symm = symm_at i p := (copy_eq _ _).symm
+
+end loop_homeo
+
 end gen_loop
 
-/--
-The `n`th homotopy group at `x` defined as the quotient of `gen_loop n x` by the
-`homotopic` relation.
--/
+/-- The `n`th homotopy group at `x` defined as the quotient of `Ω^n x` by the
+  `gen_loop.homotopic` relation. -/
 @[derive inhabited]
-def homotopy_group (n : ℕ) (x : X) : Type _ := quotient (gen_loop.homotopic.setoid n x)
-local notation `π` := homotopy_group
+def homotopy_group (N) (X : Type*) [topological_space X] (x : X) : Type* :=
+quotient (gen_loop.homotopic.setoid N x)
+
+variable [decidable_eq N]
+open gen_loop
+/-- Equivalence between the homotopy group of X and the fundamental group of
+  `Ω^{j // j ≠ i} x`. -/
+def homotopy_group_equiv_fundamental_group (i : N) :
+  homotopy_group N X x ≃ fundamental_group (Ω^{j // j ≠ i} X x) const :=
+begin
+  refine equiv.trans _ (category_theory.groupoid.iso_equiv_hom _ _).symm,
+  apply quotient.congr (loop_homeo i).to_equiv,
+  exact λ p q, ⟨homotopic_to i, homotopic_from i⟩,
+end
 
-/-- The 0-dimensional generalized loops based at `x` are in 1-1 correspondence with `X`. -/
-def gen_loop_zero_equiv : gen_loop 0 x ≃ X :=
-{ to_fun := λ f, f 0,
-  inv_fun := λ x, ⟨continuous_map.const _ x, λ _ ⟨f0,_⟩, f0.elim0⟩,
-  left_inv := λ f, by { ext1, exact congr_arg f (subsingleton.elim _ _) },
-  right_inv := λ _, rfl }
+/-- Homotopy group of finite index. -/
+@[reducible] def homotopy_group.pi (n) (X : Type*) [topological_space X] (x : X) :=
+homotopy_group (fin n) _ x
+localized "notation `π_` := homotopy_group.pi" in topology
 
-/--
-The 0th homotopy "group" is equivalent to the path components of `X`, aka the `zeroth_homotopy`.
--/
-def pi0_equiv_path_components : π 0 x ≃ zeroth_homotopy X :=
-quotient.congr gen_loop_zero_equiv
+/-- The 0-dimensional generalized loops based at `x` are in bijection with `X`. -/
+def gen_loop_homeo_of_is_empty (N x) [is_empty N] : Ω^N X x ≃ₜ X :=
+{ to_fun := λ f, f 0,
+  inv_fun := λ y, ⟨continuous_map.const _ y, λ _ ⟨i, _⟩, is_empty_elim i⟩,
+  left_inv := λ f, by { ext, exact congr_arg f (subsingleton.elim _ _) },
+  right_inv := λ _, rfl,
+  continuous_to_fun :=
+    (continuous_map.continuous_eval_const' (0 : N → I)).comp continuous_induced_dom,
+  continuous_inv_fun := (continuous_map.const'.2).subtype_mk _ }
+
+/-- The homotopy "group" indexed by an empty type is in bijection with
+  the path components of `X`, aka the `zeroth_homotopy`. -/
+def homotopy_group_equiv_zeroth_homotopy_of_is_empty (N x) [is_empty N] :
+  homotopy_group N X x ≃ zeroth_homotopy X :=
+quotient.congr (gen_loop_homeo_of_is_empty N x).to_equiv
 begin
   -- joined iff homotopic
   intros, split; rintro ⟨H⟩,
   exacts
-  [⟨{ to_fun := λ t, H ⟨t, fin.elim0⟩,
-      source' := (H.apply_zero _).trans (congr_arg a₁ matrix.zero_empty.symm),
-      target' := (H.apply_one _).trans (congr_arg a₂ matrix.zero_empty.symm) }⟩,
+  [⟨{ to_fun := λ t, H ⟨t, is_empty_elim⟩,
+      source' := (H.apply_zero _).trans (congr_arg a₁ $ subsingleton.elim _ _),
+      target' := (H.apply_one _).trans (congr_arg a₂ $ subsingleton.elim _ _) }⟩,
    ⟨{ to_fun := λ t0, H t0.fst,
       map_zero_left' := λ _, by convert H.source,
       map_one_left' := λ _, by convert H.target,
-      prop' := λ _ _ ⟨i,_⟩, i.elim0 }⟩]
+      prop' := λ _ _ ⟨i, _⟩, is_empty_elim i }⟩],
 end
 
-/-- The 1-dimensional generalized loops based at `x` are in 1-1 correspondence with
-  paths from `x` to itself. -/
-@[simps] def gen_loop_one_equiv_path_self : gen_loop 1 x ≃ path x x :=
-{ to_fun := λ p, path.mk ⟨λ t, p (λ _, t), by {continuity, exact p.1.2}⟩
-    (p.boundary (λ _, 0) ⟨0, or.inl rfl⟩)
-    (p.boundary (λ _, 1) ⟨1, or.inr rfl⟩),
-  inv_fun := λ p,
-  { to_fun := λ c, p c.head,
-    boundary := begin
-      rintro y ⟨i, iH|iH⟩; cases unique.eq_default i;
-      apply (congr_arg p iH).trans, exacts [p.source, p.target],
-    end },
-  left_inv := λ p, by { ext1, exact congr_arg p y.one_char.symm },
+/-- The 0th homotopy "group" is in bijection with `zeroth_homotopy`. -/
+def homotopy_group.pi_0_equiv_zeroth_homotopy : π_ 0 X x ≃ zeroth_homotopy X :=
+homotopy_group_equiv_zeroth_homotopy_of_is_empty (fin 0) x
+
+/-- The 1-dimensional generalized loops based at `x` are in bijection with loops at `x`. -/
+@[simps] def gen_loop_equiv_of_unique (N) [unique N] : Ω^N X x ≃ Ω X x :=
+{ to_fun := λ p, path.mk ⟨λ t, p (λ _, t), by continuity⟩
+    (gen_loop.boundary _ (λ _, 0) ⟨default, or.inl rfl⟩)
+    (gen_loop.boundary _ (λ _, 1) ⟨default, or.inr rfl⟩),
+  inv_fun := λ p, ⟨⟨λ c, p (c default), by continuity⟩,
+  begin
+    rintro y ⟨i, iH|iH⟩; cases unique.eq_default i; apply (congr_arg p iH).trans,
+    exacts [p.source, p.target],
+  end⟩,
+  left_inv := λ p, by { ext, exact congr_arg p (eq_const_of_unique y).symm },
   right_inv := λ p, by { ext, refl } }
 
-/--
-The first homotopy group at `x` is equivalent to the fundamental group,
-i.e. the loops based at `x` up to homotopy.
--/
-def pi1_equiv_fundamental_group : π 1 x ≃ fundamental_group X x :=
+/-- The homotopy group at `x` indexed by a singleton is in bijection with the fundamental group,
+  i.e. the loops based at `x` up to homotopy. -/
+/- TODO (?): deducing this from `homotopy_group_equiv_fundamental_group` would require
+  combination of `category_theory.functor.map_Aut` and
+  `fundamental_groupoid.fundamental_groupoid_functor` applied to `gen_loop_homeo_of_is_empty`,
+  with possibly worse defeq. -/
+def homotopy_group_equiv_fundamental_group_of_unique (N) [unique N] :
+  homotopy_group N X x ≃ fundamental_group X x :=
 begin
   refine equiv.trans _ (category_theory.groupoid.iso_equiv_hom _ _).symm,
-  refine quotient.congr gen_loop_one_equiv_path_self _,
-  -- homotopic iff homotopic
+  refine quotient.congr (gen_loop_equiv_of_unique N) _,
   intros, split; rintros ⟨H⟩,
-  exacts
-  [⟨{ to_fun := λ tx, H (tx.fst, λ _, tx.snd),
-      map_zero_left' := λ _, by convert H.apply_zero _,
-      map_one_left' := λ _, by convert H.apply_one _,
-      prop' := λ t y iH, H.prop' _ _ ⟨0,iH⟩ }⟩,
-   ⟨{ to_fun := λ tx, H (tx.fst, tx.snd.head),
-      map_zero_left' := λ y, by { convert H.apply_zero _, exact y.one_char },
-      map_one_left' := λ y, by { convert H.apply_one _, exact y.one_char },
-      prop' := λ t y ⟨i, iH⟩, begin
-        cases unique.eq_default i, split,
-        { convert H.eq_fst _ _, exacts [y.one_char, iH] },
-        { convert H.eq_snd _ _, exacts [y.one_char, iH] },
-      end }⟩],
+  { exact
+    ⟨ { to_fun := λ tx, H (tx.fst, λ _, tx.snd),
+        map_zero_left' := λ _, H.apply_zero _,
+        map_one_left' := λ _, H.apply_one _,
+        prop' := λ t y iH, H.prop' _ _ ⟨default, iH⟩ } ⟩ },
+  refine ⟨⟨⟨⟨λ tx, H (tx.fst, tx.snd default), H.continuous.comp _⟩, λ y, _, λ y, _⟩, _⟩⟩,
+  { exact continuous_fst.prod_mk ((continuous_apply _).comp continuous_snd) },
+  { convert H.apply_zero _, exact eq_const_of_unique y },
+  { convert H.apply_one _, exact eq_const_of_unique y },
+  { rintro t y ⟨i, iH⟩,
+    cases unique.eq_default i, split,
+    { convert H.eq_fst _ _, exacts [eq_const_of_unique y, iH] },
+    { convert H.eq_snd _ _, exacts [eq_const_of_unique y, iH] } },
+end
+
+/-- The first homotopy group at `x` is in bijection with the fundamental group. -/
+def homotopy_group.pi_1_equiv_fundamental_group : π_ 1 X x ≃ fundamental_group X x :=
+homotopy_group_equiv_fundamental_group_of_unique (fin 1)
+
+namespace homotopy_group
+
+/-- Group structure on `homotopy_group N X x` for nonempty `N` (in particular `π_(n+1) X x`). -/
+instance group (N) [decidable_eq N] [nonempty N] : group (homotopy_group N X x) :=
+(homotopy_group_equiv_fundamental_group $ classical.arbitrary N).group
+
+/-- Group structure on `homotopy_group` obtained by pulling back path composition along the
+  `i`th direction. The group structures for two different `i j : N` distribute over each
+  other, and therefore are equal by the Eckmann-Hilton argument. -/
+@[reducible] def aux_group (i : N) : group (homotopy_group N X x) :=
+(homotopy_group_equiv_fundamental_group i).group
+
+lemma is_unital_aux_group (i : N) :
+  eckmann_hilton.is_unital (aux_group i).mul (⟦const⟧ : homotopy_group N X x) :=
+⟨⟨(aux_group i).one_mul⟩, ⟨(aux_group i).mul_one⟩⟩
+
+lemma aux_group_indep (i j : N) : (aux_group i : group (homotopy_group N X x)) = aux_group j :=
+begin
+  by_cases h : i = j, { rw h },
+  refine group.ext (eckmann_hilton.mul (is_unital_aux_group i) (is_unital_aux_group j) _),
+  rintro ⟨a⟩ ⟨b⟩ ⟨c⟩ ⟨d⟩,
+  change quotient.mk _ = _,
+  apply congr_arg quotient.mk,
+  simp only [from_loop_trans_to_loop, trans_at_distrib h,
+    coe_to_equiv, loop_homeo_apply, coe_symm_to_equiv, loop_homeo_symm_apply],
 end
+
+lemma trans_at_indep {i} (j) (f g : Ω^N X x) : ⟦trans_at i f g⟧ = ⟦trans_at j f g⟧ :=
+begin
+  simp_rw ← from_loop_trans_to_loop,
+  have := congr_arg (@group.mul _) (aux_group_indep i j),
+  exact congr_fun₂ this ⟦g⟧ ⟦f⟧,
+end
+
+lemma symm_at_indep {i} (j) (f : Ω^N X x) : ⟦symm_at i f⟧ = ⟦symm_at j f⟧ :=
+begin
+  simp_rw ← from_loop_symm_to_loop,
+  have := congr_arg (@group.inv _) (aux_group_indep i j),
+  exact congr_fun this ⟦f⟧,
+end
+
+/-- Characterization of multiplicative identity -/
+lemma one_def [nonempty N] : (1 : homotopy_group N X x) = ⟦const⟧ := rfl
+
+/-- Characterization of multiplication -/
+lemma mul_spec [nonempty N] {i} {p q : Ω^N X x} :
+  (⟦p⟧ * ⟦q⟧ : homotopy_group N X x) = ⟦trans_at i q p⟧ :=
+by { rw [trans_at_indep _ q, ← from_loop_trans_to_loop], apply quotient.sound, refl }
+
+/-- Characterization of multiplicative inverse -/
+lemma inv_spec [nonempty N] {i} {p : Ω^N X x} : (⟦p⟧⁻¹ : homotopy_group N X x) = ⟦symm_at i p⟧ :=
+by { rw [symm_at_indep _ p, ← from_loop_symm_to_loop], apply quotient.sound, refl }
+
+/-- Multiplication on `homotopy_group N X x` is commutative for nontrivial `N`.
+  In particular, multiplication on `π_(n+2)` is commutative. -/
+instance comm_group [nontrivial N] : comm_group (homotopy_group N X x) :=
+let h := exists_ne (classical.arbitrary N) in
+@eckmann_hilton.comm_group (homotopy_group N X x) _ 1 (is_unital_aux_group h.some) _
+begin
+  rintro ⟨a⟩ ⟨b⟩ ⟨c⟩ ⟨d⟩,
+  apply congr_arg quotient.mk,
+  simp only [from_loop_trans_to_loop, trans_at_distrib h.some_spec,
+    coe_to_equiv, loop_homeo_apply, coe_symm_to_equiv, loop_homeo_symm_apply],
+end
+
+end homotopy_group

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(no changes)

(first ported)

Changes in mathlib3port

mathlib3
mathlib3port
Diff
@@ -153,12 +153,12 @@ theorem boundary (f : Ω^ N X x) : ∀ y ∈ Cube.boundary N, f y = x :=
 #align gen_loop.boundary GenLoop.boundary
 -/
 
-#print GenLoop.instDFunLike /-
-instance instDFunLike : DFunLike (Ω^ N X x) (I^N) fun _ => X
+#print GenLoop.instFunLike /-
+instance instFunLike : DFunLike (Ω^ N X x) (I^N) fun _ => X
     where
   coe f := f.1
   coe_injective' := fun ⟨⟨f, _⟩, _⟩ ⟨⟨g, _⟩, _⟩ h => by congr; exact h
-#align gen_loop.fun_like GenLoop.instDFunLike
+#align gen_loop.fun_like GenLoop.instFunLike
 -/
 
 #print GenLoop.ext /-
Diff
@@ -153,18 +153,18 @@ theorem boundary (f : Ω^ N X x) : ∀ y ∈ Cube.boundary N, f y = x :=
 #align gen_loop.boundary GenLoop.boundary
 -/
 
-#print GenLoop.funLike /-
-instance funLike : FunLike (Ω^ N X x) (I^N) fun _ => X
+#print GenLoop.instDFunLike /-
+instance instDFunLike : DFunLike (Ω^ N X x) (I^N) fun _ => X
     where
   coe f := f.1
   coe_injective' := fun ⟨⟨f, _⟩, _⟩ ⟨⟨g, _⟩, _⟩ h => by congr; exact h
-#align gen_loop.fun_like GenLoop.funLike
+#align gen_loop.fun_like GenLoop.instDFunLike
 -/
 
 #print GenLoop.ext /-
 @[ext]
 theorem ext (f g : Ω^ N X x) (H : ∀ y, f y = g y) : f = g :=
-  FunLike.coe_injective' (funext H)
+  DFunLike.coe_injective' (funext H)
 #align gen_loop.ext GenLoop.ext
 -/
 
Diff
@@ -262,7 +262,7 @@ def toLoop (i : N) (p : Ω^ N X x) : Ω (Ω^ { j // j ≠ i } X x) const
 theorem continuous_toLoop (i : N) : Continuous (@toLoop N X _ x _ i) :=
   Path.continuous_uncurry_iff.1 <|
     Continuous.subtype_mk
-      (ContinuousMap.continuous_eval'.comp <|
+      (ContinuousMap.continuous_eval.comp <|
         Continuous.prod_map
           (ContinuousMap.continuous_curry.comp <|
             (ContinuousMap.continuous_comp_left _).comp continuous_subtype_val)
Diff
@@ -3,10 +3,10 @@ Copyright (c) 2021 Roberto Alvarez. All rights reserved.
 Released under Apache 2.0 license as described in the file LICENSE.
 Authors: Roberto Alvarez
 -/
-import Mathbin.AlgebraicTopology.FundamentalGroupoid.FundamentalGroup
-import Mathbin.GroupTheory.EckmannHilton
-import Mathbin.Logic.Equiv.TransferInstance
-import Mathbin.Algebra.Group.Ext
+import AlgebraicTopology.FundamentalGroupoid.FundamentalGroup
+import GroupTheory.EckmannHilton
+import Logic.Equiv.TransferInstance
+import Algebra.Group.Ext
 
 #align_import topology.homotopy.homotopy_group from "leanprover-community/mathlib"@"4c3e1721c58ef9087bbc2c8c38b540f70eda2e53"
 
Diff
@@ -2,17 +2,14 @@
 Copyright (c) 2021 Roberto Alvarez. All rights reserved.
 Released under Apache 2.0 license as described in the file LICENSE.
 Authors: Roberto Alvarez
-
-! This file was ported from Lean 3 source module topology.homotopy.homotopy_group
-! leanprover-community/mathlib commit 4c3e1721c58ef9087bbc2c8c38b540f70eda2e53
-! Please do not edit these lines, except to modify the commit id
-! if you have ported upstream changes.
 -/
 import Mathbin.AlgebraicTopology.FundamentalGroupoid.FundamentalGroup
 import Mathbin.GroupTheory.EckmannHilton
 import Mathbin.Logic.Equiv.TransferInstance
 import Mathbin.Algebra.Group.Ext
 
+#align_import topology.homotopy.homotopy_group from "leanprover-community/mathlib"@"4c3e1721c58ef9087bbc2c8c38b540f70eda2e53"
+
 /-!
 # `n`th homotopy group
 
Diff
@@ -526,7 +526,7 @@ def genLoopHomeoOfIsEmpty (N x) [IsEmpty N] : Ω^ N X x ≃ₜ X
   invFun y := ⟨ContinuousMap.const _ y, fun _ ⟨i, _⟩ => isEmptyElim i⟩
   left_inv f := by ext; exact congr_arg f (Subsingleton.elim _ _)
   right_inv _ := rfl
-  continuous_toFun := (ContinuousMap.continuous_eval_const' (0 : N → I)).comp continuous_induced_dom
+  continuous_toFun := (ContinuousMap.continuous_eval_const (0 : N → I)).comp continuous_induced_dom
   continuous_invFun := ContinuousMap.const'.2.subtype_mk _
 #align gen_loop_homeo_of_is_empty genLoopHomeoOfIsEmpty
 -/
Diff
@@ -67,20 +67,25 @@ def boundary (N : Type _) : Set (I^N) :=
 
 variable {N : Type _} [DecidableEq N]
 
+#print Cube.splitAt /-
 /-- The forward direction of the homeomorphism
   between the cube $I^N$ and $I × I^{N\setminus\{j\}}$. -/
 @[reducible]
 def splitAt (i : N) : (I^N) ≃ₜ I × I^{ j // j ≠ i } :=
   funSplitAt I i
 #align cube.split_at Cube.splitAt
+-/
 
+#print Cube.insertAt /-
 /-- The backward direction of the homeomorphism
   between the cube $I^N$ and $I × I^{N\setminus\{j\}}$. -/
 @[reducible]
 def insertAt (i : N) : (I × I^{ j // j ≠ i }) ≃ₜ I^N :=
   (funSplitAt I i).symm
 #align cube.insert_at Cube.insertAt
+-/
 
+#print Cube.insertAt_boundary /-
 theorem insertAt_boundary (i : N) {t₀ : I} {t}
     (H : (t₀ = 0 ∨ t₀ = 1) ∨ t ∈ boundary { j // j ≠ i }) : insertAt i ⟨t₀, t⟩ ∈ boundary N :=
   by
@@ -88,22 +93,27 @@ theorem insertAt_boundary (i : N) {t₀ : I} {t}
   · use i; rwa [fun_split_at_symm_apply, dif_pos rfl]
   · use j; rwa [fun_split_at_symm_apply, dif_neg j.prop, Subtype.coe_eta]
 #align cube.insert_at_boundary Cube.insertAt_boundary
+-/
 
 end Cube
 
 variable (N X : Type _) [TopologicalSpace X] (x : X)
 
+#print LoopSpace /-
 /-- The space of paths with both endpoints equal to a specified point `x : X`. -/
 @[reducible]
 def LoopSpace :=
   Path x x
 #align loop_space LoopSpace
+-/
 
 scoped[Topology] notation "Ω" => LoopSpace
 
+#print LoopSpace.inhabited /-
 instance LoopSpace.inhabited : Inhabited (Path x x) :=
   ⟨Path.refl x⟩
 #align loop_space.inhabited LoopSpace.inhabited
+-/
 
 #print GenLoop /-
 /-- The `n`-dimensional generalized loops based at `x` in a space `X` are
@@ -120,19 +130,25 @@ variable {N X x}
 
 namespace GenLoop
 
+#print GenLoop.copy /-
 /-- Copy of a `gen_loop` with a new map from the unit cube equal to the old one.
   Useful to fix definitional equalities. -/
 def copy (f : Ω^ N X x) (g : (I^N) → X) (h : g = f) : Ω^ N X x :=
   ⟨⟨g, h.symm ▸ f.1.2⟩, by convert f.2; ext1; simp_rw [h]; rfl⟩
 #align gen_loop.copy GenLoop.copy
+-/
 
+#print GenLoop.coe_copy /-
 theorem coe_copy (f : Ω^ N X x) {g : (I^N) → X} (h : g = f) : ⇑(copy f g h) = g :=
   rfl
 #align gen_loop.coe_copy GenLoop.coe_copy
+-/
 
+#print GenLoop.copy_eq /-
 theorem copy_eq (f : Ω^ N X x) {g : (I^N) → X} (h : g = f) : copy f g h = f := by ext x;
   exact congr_fun h x
 #align gen_loop.copy_eq GenLoop.copy_eq
+-/
 
 #print GenLoop.boundary /-
 theorem boundary (f : Ω^ N X x) : ∀ y ∈ Cube.boundary N, f y = x :=
@@ -232,6 +248,7 @@ section LoopHomeo
 
 variable [DecidableEq N]
 
+#print GenLoop.toLoop /-
 /-- Loop from a generalized loop by currying $I^N → X$ into $I → (I^{N\setminus\{j\}} → X)$. -/
 @[simps]
 def toLoop (i : N) (p : Ω^ N X x) : Ω (Ω^ { j // j ≠ i } X x) const
@@ -242,7 +259,9 @@ def toLoop (i : N) (p : Ω^ N X x) : Ω (Ω^ { j // j ≠ i } X x) const
   source' := by ext t; refine' p.property (Cube.insertAt i (0, t)) ⟨i, Or.inl _⟩; simp
   target' := by ext t; refine' p.property (Cube.insertAt i (1, t)) ⟨i, Or.inr _⟩; simp
 #align gen_loop.to_loop GenLoop.toLoop
+-/
 
+#print GenLoop.continuous_toLoop /-
 theorem continuous_toLoop (i : N) : Continuous (@toLoop N X _ x _ i) :=
   Path.continuous_uncurry_iff.1 <|
     Continuous.subtype_mk
@@ -253,7 +272,9 @@ theorem continuous_toLoop (i : N) : Continuous (@toLoop N X _ x _ i) :=
           continuous_id)
       _
 #align gen_loop.continuous_to_loop GenLoop.continuous_toLoop
+-/
 
+#print GenLoop.fromLoop /-
 /-- Generalized loop from a loop by uncurrying $I → (I^{N\setminus\{j\}} → X)$ into $I^N → X$. -/
 @[simps]
 def fromLoop (i : N) (p : Ω (Ω^ { j // j ≠ i } X x) const) : Ω^ N X x :=
@@ -267,21 +288,27 @@ def fromLoop (i : N) (p : Ω (Ω^ { j // j ≠ i } X x) const) : Ω^ N X x :=
     · cases Hj <;> simpa only [Hj, p.coe_to_continuous_map, p.source, p.target]
     · exact GenLoop.boundary _ _ ⟨⟨j, Hne⟩, Hj⟩⟩
 #align gen_loop.from_loop GenLoop.fromLoop
+-/
 
+#print GenLoop.continuous_fromLoop /-
 theorem continuous_fromLoop (i : N) : Continuous (@fromLoop N X _ x _ i) :=
   ((ContinuousMap.continuous_comp_left _).comp <|
         ContinuousMap.continuous_uncurry.comp <|
           (ContinuousMap.continuous_comp _).comp continuous_induced_dom).subtype_mk
     _
 #align gen_loop.continuous_from_loop GenLoop.continuous_fromLoop
+-/
 
+#print GenLoop.to_from /-
 theorem to_from (i : N) (p : Ω (Ω^ { j // j ≠ i } X x) const) : toLoop i (fromLoop i p) = p :=
   by
   simp_rw [to_loop, from_loop, ContinuousMap.comp_assoc, to_continuous_map_as_coe,
     to_continuous_map_comp_symm, ContinuousMap.comp_id]
   ext; rfl
 #align gen_loop.to_from GenLoop.to_from
+-/
 
+#print GenLoop.loopHomeo /-
 /-- The `n+1`-dimensional loops are in bijection with the loops in the space of
   `n`-dimensional loops with base point `const`.
   We allow an arbitrary indexing type `N` in place of `fin n` here. -/
@@ -295,24 +322,32 @@ def loopHomeo (i : N) : Ω^ N X x ≃ₜ Ω (Ω^ { j // j ≠ i } X x) const
   continuous_toFun := continuous_toLoop i
   continuous_invFun := continuous_fromLoop i
 #align gen_loop.loop_homeo GenLoop.loopHomeo
+-/
 
+#print GenLoop.toLoop_apply /-
 theorem toLoop_apply (i : N) {p : Ω^ N X x} {t} {tn} :
     toLoop i p t tn = p (Cube.insertAt i ⟨t, tn⟩) :=
   rfl
 #align gen_loop.to_loop_apply GenLoop.toLoop_apply
+-/
 
+#print GenLoop.fromLoop_apply /-
 theorem fromLoop_apply (i : N) {p : Ω (Ω^ { j // j ≠ i } X x) const} {t : I^N} :
     fromLoop i p t = p (t i) (Cube.splitAt i t).snd :=
   rfl
 #align gen_loop.from_loop_apply GenLoop.fromLoop_apply
+-/
 
+#print GenLoop.cCompInsert /-
 /-- Composition with `cube.insert_at` as a continuous map. -/
 @[reducible]
 def cCompInsert (i : N) : C(C(I^N, X), C(I × I^{ j // j ≠ i }, X)) :=
   ⟨fun f => f.comp (Cube.insertAt i).toContinuousMap,
     (Cube.insertAt i).toContinuousMap.continuous_comp_left⟩
 #align gen_loop.c_comp_insert GenLoop.cCompInsert
+-/
 
+#print GenLoop.homotopyTo /-
 /-- A homotopy between `n+1`-dimensional loops `p` and `q` constant on the boundary
   seen as a homotopy between two paths in the space of `n`-dimensional paths. -/
 def homotopyTo (i : N) {p q : Ω^ N X x} (H : p.1.HomotopyRel q.1 (Cube.boundary N)) :
@@ -320,15 +355,19 @@ def homotopyTo (i : N) {p q : Ω^ N X x} (H : p.1.HomotopyRel q.1 (Cube.boundary
   ((⟨_, ContinuousMap.continuous_curry⟩ : C(_, _)).comp <|
       (cCompInsert i).comp H.toContinuousMap.curry).uncurry
 #align gen_loop.homotopy_to GenLoop.homotopyTo
+-/
 
+#print GenLoop.homotopyTo_apply /-
 -- Should be generated with `@[simps]` but it times out.
 theorem homotopyTo_apply (i : N) {p q : Ω^ N X x} (H : p.1.HomotopyRel q.1 <| Cube.boundary N)
     (t : I × I) (tₙ : I^{ j // j ≠ i }) :
     homotopyTo i H t tₙ = H (t.fst, Cube.insertAt i (t.snd, tₙ)) :=
   rfl
 #align gen_loop.homotopy_to_apply GenLoop.homotopyTo_apply
+-/
 
-theorem homotopic_to (i : N) {p q : Ω^ N X x} :
+#print GenLoop.homotopicTo /-
+theorem homotopicTo (i : N) {p q : Ω^ N X x} :
     Homotopic p q → (toLoop i p).Homotopic (toLoop i q) :=
   by
   refine' Nonempty.map fun H => ⟨⟨⟨fun t => ⟨homotopy_to i H t, _⟩, _⟩, _, _⟩, _⟩
@@ -343,8 +382,10 @@ theorem homotopic_to (i : N) {p q : Ω^ N X x} :
     all_goals use i; rw [fun_split_at_symm_apply, dif_pos rfl]; exact yH
   all_goals intro; ext; erw [homotopy_to_apply, to_loop_apply]
   exacts [H.apply_zero _, H.apply_one _]
-#align gen_loop.homotopic_to GenLoop.homotopic_to
+#align gen_loop.homotopic_to GenLoop.homotopicTo
+-/
 
+#print GenLoop.homotopyFrom /-
 /-- The converse to `gen_loop.homotopy_to`: a homotopy between two loops in the space of
   `n`-dimensional loops can be seen as a homotopy between two `n+1`-dimensional paths. -/
 def homotopyFrom (i : N) {p q : Ω^ N X x} (H : (toLoop i p).Homotopy (toLoop i q)) :
@@ -353,13 +394,17 @@ def homotopyFrom (i : N) {p q : Ω^ N X x} (H : (toLoop i p).Homotopy (toLoop i
           (ContinuousMap.comp ⟨coe⟩ H.toContinuousMap).curry).uncurry.comp <|
     (ContinuousMap.id I).Prod_map (Cube.splitAt i).toContinuousMap
 #align gen_loop.homotopy_from GenLoop.homotopyFrom
+-/
 
+#print GenLoop.homotopyFrom_apply /-
 -- Should be generated with `@[simps]` but it times out.
 theorem homotopyFrom_apply (i : N) {p q : Ω^ N X x} (H : (toLoop i p).Homotopy (toLoop i q))
     (t : I × I^N) : homotopyFrom i H t = H (t.fst, t.snd i) fun j => t.snd ↑j :=
   rfl
 #align gen_loop.homotopy_from_apply GenLoop.homotopyFrom_apply
+-/
 
+#print GenLoop.homotopicFrom /-
 theorem homotopicFrom (i : N) {p q : Ω^ N X x} :
     (toLoop i p).Homotopic (toLoop i q) → Homotopic p q :=
   by
@@ -383,7 +428,9 @@ theorem homotopicFrom (i : N) {p q : Ω^ N X x} :
     | apply congr_arg q
     exact (Equiv.right_inv _ _).symm
 #align gen_loop.homotopic_from GenLoop.homotopicFrom
+-/
 
+#print GenLoop.transAt /-
 /-- Concatenation of two `gen_loop`s along the `i`th coordinate. -/
 def transAt (i : N) (f g : Ω^ N X x) : Ω^ N X x :=
   copy (fromLoop i <| (toLoop i f).trans <| toLoop i g)
@@ -398,13 +445,17 @@ def transAt (i : N) (f g : Ω^ N X x) : Ω^ N X x :=
       split_ifs; change f _ = _; swap; change g _ = _
       all_goals congr 1)
 #align gen_loop.trans_at GenLoop.transAt
+-/
 
+#print GenLoop.symmAt /-
 /-- Reversal of a `gen_loop` along the `i`th coordinate. -/
 def symmAt (i : N) (f : Ω^ N X x) : Ω^ N X x :=
   (copy (fromLoop i (toLoop i f).symm) fun t => f fun j => if j = i then σ (t i) else t j) <| by
     ext1; change _ = f _; congr; ext1; simp
 #align gen_loop.symm_at GenLoop.symmAt
+-/
 
+#print GenLoop.transAt_distrib /-
 theorem transAt_distrib {i j : N} (h : i ≠ j) (a b c d : Ω^ N X x) :
     transAt i (transAt j a b) (transAt j c d) = transAt j (transAt i a c) (transAt i b d) :=
   by
@@ -413,15 +464,20 @@ theorem transAt_distrib {i j : N} (h : i ≠ j) (a b c d : Ω^ N X x) :
     · congr 1; ext1; simp only [Function.update, eq_rec_constant, dite_eq_ite]
       apply ite_ite_comm; rintro rfl; exact h.symm
 #align gen_loop.trans_at_distrib GenLoop.transAt_distrib
+-/
 
+#print GenLoop.fromLoop_trans_toLoop /-
 theorem fromLoop_trans_toLoop {i : N} {p q : Ω^ N X x} :
     fromLoop i ((toLoop i p).trans <| toLoop i q) = transAt i p q :=
   (copy_eq _ _).symm
 #align gen_loop.from_loop_trans_to_loop GenLoop.fromLoop_trans_toLoop
+-/
 
+#print GenLoop.fromLoop_symm_toLoop /-
 theorem fromLoop_symm_toLoop {i : N} {p : Ω^ N X x} : fromLoop i (toLoop i p).symm = symmAt i p :=
   (copy_eq _ _).symm
 #align gen_loop.from_loop_symm_to_loop GenLoop.fromLoop_symm_toLoop
+-/
 
 end LoopHomeo
 
@@ -440,6 +496,7 @@ variable [DecidableEq N]
 
 open GenLoop
 
+#print homotopyGroupEquivFundamentalGroup /-
 /-- Equivalence between the homotopy group of X and the fundamental group of
   `Ω^{j // j ≠ i} x`. -/
 def homotopyGroupEquivFundamentalGroup (i : N) :
@@ -449,15 +506,19 @@ def homotopyGroupEquivFundamentalGroup (i : N) :
   apply Quotient.congr (loop_homeo i).toEquiv
   exact fun p q => ⟨homotopic_to i, homotopic_from i⟩
 #align homotopy_group_equiv_fundamental_group homotopyGroupEquivFundamentalGroup
+-/
 
+#print HomotopyGroup.Pi /-
 /-- Homotopy group of finite index. -/
 @[reducible]
 def HomotopyGroup.Pi (n) (X : Type _) [TopologicalSpace X] (x : X) :=
   HomotopyGroup (Fin n) _ x
 #align homotopy_group.pi HomotopyGroup.Pi
+-/
 
 scoped[Topology] notation "π_" => HomotopyGroup.Pi
 
+#print genLoopHomeoOfIsEmpty /-
 /-- The 0-dimensional generalized loops based at `x` are in bijection with `X`. -/
 def genLoopHomeoOfIsEmpty (N x) [IsEmpty N] : Ω^ N X x ≃ₜ X
     where
@@ -468,7 +529,9 @@ def genLoopHomeoOfIsEmpty (N x) [IsEmpty N] : Ω^ N X x ≃ₜ X
   continuous_toFun := (ContinuousMap.continuous_eval_const' (0 : N → I)).comp continuous_induced_dom
   continuous_invFun := ContinuousMap.const'.2.subtype_mk _
 #align gen_loop_homeo_of_is_empty genLoopHomeoOfIsEmpty
+-/
 
+#print homotopyGroupEquivZerothHomotopyOfIsEmpty /-
 /-- The homotopy "group" indexed by an empty type is in bijection with
   the path components of `X`, aka the `zeroth_homotopy`. -/
 def homotopyGroupEquivZerothHomotopyOfIsEmpty (N x) [IsEmpty N] :
@@ -487,12 +550,16 @@ def homotopyGroupEquivZerothHomotopyOfIsEmpty (N x) [IsEmpty N] :
             map_one_left' := fun _ => by convert H.target
             prop' := fun _ _ ⟨i, _⟩ => isEmptyElim i }⟩])
 #align homotopy_group_equiv_zeroth_homotopy_of_is_empty homotopyGroupEquivZerothHomotopyOfIsEmpty
+-/
 
+#print HomotopyGroup.pi0EquivZerothHomotopy /-
 /-- The 0th homotopy "group" is in bijection with `zeroth_homotopy`. -/
 def HomotopyGroup.pi0EquivZerothHomotopy : π_ 0 X x ≃ ZerothHomotopy X :=
   homotopyGroupEquivZerothHomotopyOfIsEmpty (Fin 0) x
 #align homotopy_group.pi_0_equiv_zeroth_homotopy HomotopyGroup.pi0EquivZerothHomotopy
+-/
 
+#print genLoopEquivOfUnique /-
 /-- The 1-dimensional generalized loops based at `x` are in bijection with loops at `x`. -/
 @[simps]
 def genLoopEquivOfUnique (N) [Unique N] : Ω^ N X x ≃ Ω X x
@@ -509,7 +576,9 @@ def genLoopEquivOfUnique (N) [Unique N] : Ω^ N X x ≃ Ω X x
   left_inv p := by ext; exact congr_arg p (eq_const_of_unique y).symm
   right_inv p := by ext; rfl
 #align gen_loop_equiv_of_unique genLoopEquivOfUnique
+-/
 
+#print homotopyGroupEquivFundamentalGroupOfUnique /-
 /- TODO (?): deducing this from `homotopy_group_equiv_fundamental_group` would require
   combination of `category_theory.functor.map_Aut` and
   `fundamental_groupoid.fundamental_groupoid_functor` applied to `gen_loop_homeo_of_is_empty`,
@@ -538,19 +607,25 @@ def homotopyGroupEquivFundamentalGroupOfUnique (N) [Unique N] :
     · convert H.eq_fst _ _; exacts [eq_const_of_unique y, iH]
     · convert H.eq_snd _ _; exacts [eq_const_of_unique y, iH]
 #align homotopy_group_equiv_fundamental_group_of_unique homotopyGroupEquivFundamentalGroupOfUnique
+-/
 
+#print HomotopyGroup.pi1EquivFundamentalGroup /-
 /-- The first homotopy group at `x` is in bijection with the fundamental group. -/
 def HomotopyGroup.pi1EquivFundamentalGroup : π_ 1 X x ≃ FundamentalGroup X x :=
   homotopyGroupEquivFundamentalGroupOfUnique (Fin 1)
 #align homotopy_group.pi_1_equiv_fundamental_group HomotopyGroup.pi1EquivFundamentalGroup
+-/
 
 namespace HomotopyGroup
 
+#print HomotopyGroup.group /-
 /-- Group structure on `homotopy_group N X x` for nonempty `N` (in particular `π_(n+1) X x`). -/
 instance group (N) [DecidableEq N] [Nonempty N] : Group (HomotopyGroup N X x) :=
   (homotopyGroupEquivFundamentalGroup <| Classical.arbitrary N).Group
 #align homotopy_group.group HomotopyGroup.group
+-/
 
+#print HomotopyGroup.auxGroup /-
 /-- Group structure on `homotopy_group` obtained by pulling back path composition along the
   `i`th direction. The group structures for two different `i j : N` distribute over each
   other, and therefore are equal by the Eckmann-Hilton argument. -/
@@ -558,12 +633,16 @@ instance group (N) [DecidableEq N] [Nonempty N] : Group (HomotopyGroup N X x) :=
 def auxGroup (i : N) : Group (HomotopyGroup N X x) :=
   (homotopyGroupEquivFundamentalGroup i).Group
 #align homotopy_group.aux_group HomotopyGroup.auxGroup
+-/
 
+#print HomotopyGroup.isUnital_auxGroup /-
 theorem isUnital_auxGroup (i : N) :
     EckmannHilton.IsUnital (auxGroup i).mul (⟦const⟧ : HomotopyGroup N X x) :=
   ⟨⟨(auxGroup i).one_mul⟩, ⟨(auxGroup i).mul_one⟩⟩
 #align homotopy_group.is_unital_aux_group HomotopyGroup.isUnital_auxGroup
+-/
 
+#print HomotopyGroup.auxGroup_indep /-
 theorem auxGroup_indep (i j : N) : (auxGroup i : Group (HomotopyGroup N X x)) = auxGroup j :=
   by
   by_cases h : i = j; · rw [h]
@@ -574,37 +653,49 @@ theorem auxGroup_indep (i j : N) : (auxGroup i : Group (HomotopyGroup N X x)) =
   simp only [from_loop_trans_to_loop, trans_at_distrib h, coe_to_equiv, loop_homeo_apply,
     coe_symm_to_equiv, loop_homeo_symm_apply]
 #align homotopy_group.aux_group_indep HomotopyGroup.auxGroup_indep
+-/
 
+#print HomotopyGroup.transAt_indep /-
 theorem transAt_indep {i} (j) (f g : Ω^ N X x) : ⟦transAt i f g⟧ = ⟦transAt j f g⟧ :=
   by
   simp_rw [← from_loop_trans_to_loop]
   have := congr_arg (@Group.mul _) (aux_group_indep i j)
   exact congr_fun₂ this ⟦g⟧ ⟦f⟧
 #align homotopy_group.trans_at_indep HomotopyGroup.transAt_indep
+-/
 
+#print HomotopyGroup.symmAt_indep /-
 theorem symmAt_indep {i} (j) (f : Ω^ N X x) : ⟦symmAt i f⟧ = ⟦symmAt j f⟧ :=
   by
   simp_rw [← from_loop_symm_to_loop]
   have := congr_arg (@Group.inv _) (aux_group_indep i j)
   exact congr_fun this ⟦f⟧
 #align homotopy_group.symm_at_indep HomotopyGroup.symmAt_indep
+-/
 
+#print HomotopyGroup.one_def /-
 /-- Characterization of multiplicative identity -/
 theorem one_def [Nonempty N] : (1 : HomotopyGroup N X x) = ⟦const⟧ :=
   rfl
 #align homotopy_group.one_def HomotopyGroup.one_def
+-/
 
+#print HomotopyGroup.mul_spec /-
 /-- Characterization of multiplication -/
 theorem mul_spec [Nonempty N] {i} {p q : Ω^ N X x} :
     (⟦p⟧ * ⟦q⟧ : HomotopyGroup N X x) = ⟦transAt i q p⟧ := by
   rw [trans_at_indep _ q, ← from_loop_trans_to_loop]; apply Quotient.sound; rfl
 #align homotopy_group.mul_spec HomotopyGroup.mul_spec
+-/
 
+#print HomotopyGroup.inv_spec /-
 /-- Characterization of multiplicative inverse -/
 theorem inv_spec [Nonempty N] {i} {p : Ω^ N X x} : ((⟦p⟧)⁻¹ : HomotopyGroup N X x) = ⟦symmAt i p⟧ :=
   by rw [symm_at_indep _ p, ← from_loop_symm_to_loop]; apply Quotient.sound; rfl
 #align homotopy_group.inv_spec HomotopyGroup.inv_spec
+-/
 
+#print HomotopyGroup.commGroup /-
 /-- Multiplication on `homotopy_group N X x` is commutative for nontrivial `N`.
   In particular, multiplication on `π_(n+2)` is commutative. -/
 instance commGroup [Nontrivial N] : CommGroup (HomotopyGroup N X x) :=
@@ -616,6 +707,7 @@ instance commGroup [Nontrivial N] : CommGroup (HomotopyGroup N X x) :=
       simp only [from_loop_trans_to_loop, trans_at_distrib h.some_spec, coe_to_equiv,
         loop_homeo_apply, coe_symm_to_equiv, loop_homeo_symm_apply])
 #align homotopy_group.comm_group HomotopyGroup.commGroup
+-/
 
 end HomotopyGroup
 
Diff
@@ -54,15 +54,16 @@ open Homeomorph
 
 noncomputable section
 
--- mathport name: «exprI^ »
 scoped[Topology] notation "I^" N => N → I
 
 namespace Cube
 
+#print Cube.boundary /-
 /-- The points in a cube with at least one projection equal to 0 or 1. -/
 def boundary (N : Type _) : Set (I^N) :=
   {y | ∃ i, y i = 0 ∨ y i = 1}
 #align cube.boundary Cube.boundary
+-/
 
 variable {N : Type _} [DecidableEq N]
 
@@ -98,21 +99,21 @@ def LoopSpace :=
   Path x x
 #align loop_space LoopSpace
 
--- mathport name: exprΩ
 scoped[Topology] notation "Ω" => LoopSpace
 
 instance LoopSpace.inhabited : Inhabited (Path x x) :=
   ⟨Path.refl x⟩
 #align loop_space.inhabited LoopSpace.inhabited
 
+#print GenLoop /-
 /-- The `n`-dimensional generalized loops based at `x` in a space `X` are
   continuous functions `I^n → X` that sends the boundary to `x`.
   We allow an arbitrary indexing type `N` in place of `fin n` here. -/
 def GenLoop : Set C(I^N, X) :=
   {p | ∀ y ∈ Cube.boundary N, p y = x}
 #align gen_loop GenLoop
+-/
 
--- mathport name: «exprΩ^»
 scoped[Topology] notation "Ω^" => GenLoop
 
 variable {N X x}
@@ -133,71 +134,97 @@ theorem copy_eq (f : Ω^ N X x) {g : (I^N) → X} (h : g = f) : copy f g h = f :
   exact congr_fun h x
 #align gen_loop.copy_eq GenLoop.copy_eq
 
+#print GenLoop.boundary /-
 theorem boundary (f : Ω^ N X x) : ∀ y ∈ Cube.boundary N, f y = x :=
   f.2
 #align gen_loop.boundary GenLoop.boundary
+-/
 
+#print GenLoop.funLike /-
 instance funLike : FunLike (Ω^ N X x) (I^N) fun _ => X
     where
   coe f := f.1
   coe_injective' := fun ⟨⟨f, _⟩, _⟩ ⟨⟨g, _⟩, _⟩ h => by congr; exact h
 #align gen_loop.fun_like GenLoop.funLike
+-/
 
+#print GenLoop.ext /-
 @[ext]
 theorem ext (f g : Ω^ N X x) (H : ∀ y, f y = g y) : f = g :=
   FunLike.coe_injective' (funext H)
 #align gen_loop.ext GenLoop.ext
+-/
 
+#print GenLoop.mk_apply /-
 @[simp]
 theorem mk_apply (f : C(I^N, X)) (H y) : (⟨f, H⟩ : Ω^ N X x) y = f y :=
   rfl
 #align gen_loop.mk_apply GenLoop.mk_apply
+-/
 
+#print GenLoop.const /-
 /-- The constant `gen_loop` at `x`. -/
 def const : Ω^ N X x :=
   ⟨ContinuousMap.const _ x, fun _ _ => rfl⟩
 #align gen_loop.const GenLoop.const
+-/
 
+#print GenLoop.const_apply /-
 @[simp]
 theorem const_apply {t} : (@const N X _ x) t = x :=
   rfl
 #align gen_loop.const_apply GenLoop.const_apply
+-/
 
+#print GenLoop.inhabited /-
 instance inhabited : Inhabited (Ω^ N X x) :=
   ⟨const⟩
 #align gen_loop.inhabited GenLoop.inhabited
+-/
 
+#print GenLoop.Homotopic /-
 /-- The "homotopic relative to boundary" relation between `gen_loop`s. -/
 def Homotopic (f g : Ω^ N X x) : Prop :=
   f.1.HomotopicRel g.1 (Cube.boundary N)
 #align gen_loop.homotopic GenLoop.Homotopic
+-/
 
 namespace homotopic
 
 variable {f g h : Ω^ N X x}
 
+#print GenLoop.Homotopic.refl /-
 @[refl]
 theorem refl (f : Ω^ N X x) : Homotopic f f :=
   ContinuousMap.HomotopicRel.refl _
 #align gen_loop.homotopic.refl GenLoop.Homotopic.refl
+-/
 
+#print GenLoop.Homotopic.symm /-
 @[symm]
 theorem symm (H : Homotopic f g) : Homotopic g f :=
   H.symm
 #align gen_loop.homotopic.symm GenLoop.Homotopic.symm
+-/
 
+#print GenLoop.Homotopic.trans /-
 @[trans]
 theorem trans (H0 : Homotopic f g) (H1 : Homotopic g h) : Homotopic f h :=
   H0.trans H1
 #align gen_loop.homotopic.trans GenLoop.Homotopic.trans
+-/
 
+#print GenLoop.Homotopic.equiv /-
 theorem equiv : Equivalence (@Homotopic N X _ x) :=
   ⟨Homotopic.refl, fun _ _ => Homotopic.symm, fun _ _ _ => Homotopic.trans⟩
 #align gen_loop.homotopic.equiv GenLoop.Homotopic.equiv
+-/
 
+#print GenLoop.Homotopic.setoid /-
 instance setoid (N) (x : X) : Setoid (Ω^ N X x) :=
   ⟨Homotopic, equiv⟩
 #align gen_loop.homotopic.setoid GenLoop.Homotopic.setoid
+-/
 
 end homotopic
 
@@ -400,12 +427,14 @@ end LoopHomeo
 
 end GenLoop
 
+#print HomotopyGroup /-
 /-- The `n`th homotopy group at `x` defined as the quotient of `Ω^n x` by the
   `gen_loop.homotopic` relation. -/
 def HomotopyGroup (N) (X : Type _) [TopologicalSpace X] (x : X) : Type _ :=
   Quotient (GenLoop.Homotopic.setoid N x)
 deriving Inhabited
 #align homotopy_group HomotopyGroup
+-/
 
 variable [DecidableEq N]
 
@@ -427,7 +456,6 @@ def HomotopyGroup.Pi (n) (X : Type _) [TopologicalSpace X] (x : X) :=
   HomotopyGroup (Fin n) _ x
 #align homotopy_group.pi HomotopyGroup.Pi
 
--- mathport name: exprπ_
 scoped[Topology] notation "π_" => HomotopyGroup.Pi
 
 /-- The 0-dimensional generalized loops based at `x` are in bijection with `X`. -/
Diff
@@ -4,11 +4,14 @@ Released under Apache 2.0 license as described in the file LICENSE.
 Authors: Roberto Alvarez
 
 ! This file was ported from Lean 3 source module topology.homotopy.homotopy_group
-! leanprover-community/mathlib commit 599fffe78f0e11eb6a034e834ec51882167b9688
+! leanprover-community/mathlib commit 4c3e1721c58ef9087bbc2c8c38b540f70eda2e53
 ! Please do not edit these lines, except to modify the commit id
 ! if you have ported upstream changes.
 -/
 import Mathbin.AlgebraicTopology.FundamentalGroupoid.FundamentalGroup
+import Mathbin.GroupTheory.EckmannHilton
+import Mathbin.Logic.Equiv.TransferInstance
+import Mathbin.Algebra.Group.Ext
 
 /-!
 # `n`th homotopy group
@@ -16,279 +19,575 @@ import Mathbin.AlgebraicTopology.FundamentalGroupoid.FundamentalGroup
 > THIS FILE IS SYNCHRONIZED WITH MATHLIB4.
 > Any changes to this file require a corresponding PR to mathlib4.
 
-We define the `n`th homotopy group at `x`, `π n x`, as the equivalence classes
-of functions from the nth dimensional cube to the topological space `X`
+We define the `n`th homotopy group at `x : X`, `π_n X x`, as the equivalence classes
+of functions from the `n`-dimensional cube to the topological space `X`
 that send the boundary to the base point `x`, up to homotopic equivalence.
-Note that such functions are generalized loops `gen_loop n x`, in particular
-`gen_loop 1 x ≃ path x x`
+Note that such functions are generalized loops `gen_loop (fin n) x`; in particular
+`gen_loop (fin 1) x ≃ path x x`.
 
-We show that `π 0 x` is equivalent to the path-conected components, and
-that `π 1 x` is equivalent to the fundamental group at `x`.
+We show that `π_0 X x` is equivalent to the path-connected components, and
+that `π_1 X x` is equivalent to the fundamental group at `x`.
+We provide a group instance using path composition and show commutativity when `n > 1`.
 
 ## definitions
 
-* `gen_loop n x` is the type of continous fuctions `I^n → X` that send the boundary to `x`
-* `homotopy_group n x` denoted `π n x` is the quotient of `gen_loop n x` by homotopy relative
-  to the boundary
+* `gen_loop N x` is the type of continuous fuctions `I^N → X` that send the boundary to `x`,
+* `homotopy_group.pi n X x` denoted `π_ n X x` is the quotient of `gen_loop (fin n) x` by
+  homotopy relative to the boundary,
+* group instance `group (π_(n+1) X x)`,
+* commutative group instance `comm_group (π_(n+2) X x)`.
 
-TODO: show that `π n x` is a group when `n > 0` and abelian when `n > 1`. Show that
-`pi1_equiv_fundamental_group` is an isomorphism of groups.
+TODO:
+* `Ω^M (Ω^N X) ≃ₜ Ω^(M⊕N) X`, and `Ω^M X ≃ₜ Ω^N X` when `M ≃ N`. Similarly for `π_`.
+* Path-induced homomorphisms. Show that `homotopy_group.pi_1_equiv_fundamental_group`
+  is a group isomorphism.
+* Examples with `𝕊^n`: `π_n (𝕊^n) = ℤ`, `π_m (𝕊^n)` trivial for `m < n`.
+* Actions of π_1 on π_n.
+* Lie algebra: `⁅π_(n+1), π_(m+1)⁆` contained in `π_(n+m+1)`.
 
 -/
 
 
 open scoped unitInterval Topology
 
+open Homeomorph
+
 noncomputable section
 
-universe u
+-- mathport name: «exprI^ »
+scoped[Topology] notation "I^" N => N → I
 
-variable {X : Type u} [TopologicalSpace X]
+namespace Cube
 
-variable {n : ℕ} {x : X}
+/-- The points in a cube with at least one projection equal to 0 or 1. -/
+def boundary (N : Type _) : Set (I^N) :=
+  {y | ∃ i, y i = 0 ∨ y i = 1}
+#align cube.boundary Cube.boundary
 
-#print Cube /-
-/-- The `n`-dimensional cube.
--/
-def Cube (n : ℕ) : Type :=
-  Fin n → I
-deriving Zero, One, TopologicalSpace
-#align cube Cube
--/
+variable {N : Type _} [DecidableEq N]
 
--- mathport name: «exprI^»
-local notation "I^" => Cube
+/-- The forward direction of the homeomorphism
+  between the cube $I^N$ and $I × I^{N\setminus\{j\}}$. -/
+@[reducible]
+def splitAt (i : N) : (I^N) ≃ₜ I × I^{ j // j ≠ i } :=
+  funSplitAt I i
+#align cube.split_at Cube.splitAt
 
-namespace Cube
+/-- The backward direction of the homeomorphism
+  between the cube $I^N$ and $I × I^{N\setminus\{j\}}$. -/
+@[reducible]
+def insertAt (i : N) : (I × I^{ j // j ≠ i }) ≃ₜ I^N :=
+  (funSplitAt I i).symm
+#align cube.insert_at Cube.insertAt
 
-@[continuity]
-theorem proj_continuous (i : Fin n) : Continuous fun f : I^ n => f i :=
-  continuous_apply i
-#align cube.proj_continuous Cube.proj_continuous
+theorem insertAt_boundary (i : N) {t₀ : I} {t}
+    (H : (t₀ = 0 ∨ t₀ = 1) ∨ t ∈ boundary { j // j ≠ i }) : insertAt i ⟨t₀, t⟩ ∈ boundary N :=
+  by
+  obtain H | ⟨j, H⟩ := H
+  · use i; rwa [fun_split_at_symm_apply, dif_pos rfl]
+  · use j; rwa [fun_split_at_symm_apply, dif_neg j.prop, Subtype.coe_eta]
+#align cube.insert_at_boundary Cube.insertAt_boundary
 
-#print Cube.boundary /-
-/-- The points of the `n`-dimensional cube with at least one projection equal to 0 or 1.
--/
-def boundary (n : ℕ) : Set (I^ n) :=
-  {y | ∃ i, y i = 0 ∨ y i = 1}
-#align cube.boundary Cube.boundary
--/
+end Cube
 
-#print Cube.head /-
-/-- The first projection of a positive-dimensional cube.
--/
-@[simp]
-def head {n} : I^ (n + 1) → I := fun c => c 0
-#align cube.head Cube.head
--/
+variable (N X : Type _) [TopologicalSpace X] (x : X)
 
-@[continuity]
-theorem head.continuous {n} : Continuous (@head n) :=
-  proj_continuous 0
-#align cube.head.continuous Cube.head.continuous
+/-- The space of paths with both endpoints equal to a specified point `x : X`. -/
+@[reducible]
+def LoopSpace :=
+  Path x x
+#align loop_space LoopSpace
 
-#print Cube.tail /-
-/-- The projection to the last `n` coordinates from an `n+1` dimensional cube.
--/
-@[simp]
-def tail {n} : I^ (n + 1) → I^ n := fun c => Fin.tail c
-#align cube.tail Cube.tail
--/
+-- mathport name: exprΩ
+scoped[Topology] notation "Ω" => LoopSpace
 
-#print Cube.uniqueCube0 /-
-instance uniqueCube0 : Unique (I^ 0) :=
-  Pi.uniqueOfIsEmpty _
-#align cube.unique_cube0 Cube.uniqueCube0
--/
+instance LoopSpace.inhabited : Inhabited (Path x x) :=
+  ⟨Path.refl x⟩
+#align loop_space.inhabited LoopSpace.inhabited
 
-theorem one_char (f : I^ 1) : f = fun _ => f 0 := by convert eq_const_of_unique f
-#align cube.one_char Cube.one_char
+/-- The `n`-dimensional generalized loops based at `x` in a space `X` are
+  continuous functions `I^n → X` that sends the boundary to `x`.
+  We allow an arbitrary indexing type `N` in place of `fin n` here. -/
+def GenLoop : Set C(I^N, X) :=
+  {p | ∀ y ∈ Cube.boundary N, p y = x}
+#align gen_loop GenLoop
 
-end Cube
+-- mathport name: «exprΩ^»
+scoped[Topology] notation "Ω^" => GenLoop
 
-#print GenLoop /-
-/--
-The `n`-dimensional generalized loops; functions `I^n → X` that send the boundary to the base point.
--/
-structure GenLoop (n : ℕ) (x : X) extends C(I^ n, X) where
-  boundary : ∀ y ∈ Cube.boundary n, to_fun y = x
-#align gen_loop GenLoop
--/
+variable {N X x}
 
 namespace GenLoop
 
-#print GenLoop.funLike /-
-instance funLike : FunLike (GenLoop n x) (I^ n) fun _ => X
+/-- Copy of a `gen_loop` with a new map from the unit cube equal to the old one.
+  Useful to fix definitional equalities. -/
+def copy (f : Ω^ N X x) (g : (I^N) → X) (h : g = f) : Ω^ N X x :=
+  ⟨⟨g, h.symm ▸ f.1.2⟩, by convert f.2; ext1; simp_rw [h]; rfl⟩
+#align gen_loop.copy GenLoop.copy
+
+theorem coe_copy (f : Ω^ N X x) {g : (I^N) → X} (h : g = f) : ⇑(copy f g h) = g :=
+  rfl
+#align gen_loop.coe_copy GenLoop.coe_copy
+
+theorem copy_eq (f : Ω^ N X x) {g : (I^N) → X} (h : g = f) : copy f g h = f := by ext x;
+  exact congr_fun h x
+#align gen_loop.copy_eq GenLoop.copy_eq
+
+theorem boundary (f : Ω^ N X x) : ∀ y ∈ Cube.boundary N, f y = x :=
+  f.2
+#align gen_loop.boundary GenLoop.boundary
+
+instance funLike : FunLike (Ω^ N X x) (I^N) fun _ => X
     where
   coe f := f.1
   coe_injective' := fun ⟨⟨f, _⟩, _⟩ ⟨⟨g, _⟩, _⟩ h => by congr; exact h
 #align gen_loop.fun_like GenLoop.funLike
--/
 
-#print GenLoop.ext /-
 @[ext]
-theorem ext (f g : GenLoop n x) (H : ∀ y, f y = g y) : f = g :=
-  FunLike.ext f g H
+theorem ext (f g : Ω^ N X x) (H : ∀ y, f y = g y) : f = g :=
+  FunLike.coe_injective' (funext H)
 #align gen_loop.ext GenLoop.ext
--/
 
 @[simp]
-theorem mk_apply (f : C(I^ n, X)) (H y) : (⟨f, H⟩ : GenLoop n x) y = f y :=
+theorem mk_apply (f : C(I^N, X)) (H y) : (⟨f, H⟩ : Ω^ N X x) y = f y :=
   rfl
 #align gen_loop.mk_apply GenLoop.mk_apply
 
-#print GenLoop.const /-
-/-- The constant `gen_loop` at `x`.
--/
-def const : GenLoop n x :=
+/-- The constant `gen_loop` at `x`. -/
+def const : Ω^ N X x :=
   ⟨ContinuousMap.const _ x, fun _ _ => rfl⟩
 #align gen_loop.const GenLoop.const
--/
 
-#print GenLoop.inhabited /-
-instance inhabited : Inhabited (GenLoop n x) where default := const
+@[simp]
+theorem const_apply {t} : (@const N X _ x) t = x :=
+  rfl
+#align gen_loop.const_apply GenLoop.const_apply
+
+instance inhabited : Inhabited (Ω^ N X x) :=
+  ⟨const⟩
 #align gen_loop.inhabited GenLoop.inhabited
--/
 
-#print GenLoop.Homotopic /-
-/-- The "homotopy relative to boundary" relation between `gen_loop`s.
--/
-def Homotopic (f g : GenLoop n x) : Prop :=
-  f.toContinuousMap.HomotopicRel g.toContinuousMap (Cube.boundary n)
+/-- The "homotopic relative to boundary" relation between `gen_loop`s. -/
+def Homotopic (f g : Ω^ N X x) : Prop :=
+  f.1.HomotopicRel g.1 (Cube.boundary N)
 #align gen_loop.homotopic GenLoop.Homotopic
--/
 
 namespace homotopic
 
-section
-
-variable {f g h : GenLoop n x}
+variable {f g h : Ω^ N X x}
 
-#print GenLoop.Homotopic.refl /-
 @[refl]
-theorem refl (f : GenLoop n x) : Homotopic f f :=
+theorem refl (f : Ω^ N X x) : Homotopic f f :=
   ContinuousMap.HomotopicRel.refl _
 #align gen_loop.homotopic.refl GenLoop.Homotopic.refl
--/
 
-#print GenLoop.Homotopic.symm /-
 @[symm]
-theorem symm (H : f.Homotopic g) : g.Homotopic f :=
+theorem symm (H : Homotopic f g) : Homotopic g f :=
   H.symm
 #align gen_loop.homotopic.symm GenLoop.Homotopic.symm
--/
 
-#print GenLoop.Homotopic.trans /-
 @[trans]
-theorem trans (H0 : f.Homotopic g) (H1 : g.Homotopic h) : f.Homotopic h :=
+theorem trans (H0 : Homotopic f g) (H1 : Homotopic g h) : Homotopic f h :=
   H0.trans H1
 #align gen_loop.homotopic.trans GenLoop.Homotopic.trans
--/
 
-#print GenLoop.Homotopic.equiv /-
-theorem equiv : Equivalence (@Homotopic X _ n x) :=
+theorem equiv : Equivalence (@Homotopic N X _ x) :=
   ⟨Homotopic.refl, fun _ _ => Homotopic.symm, fun _ _ _ => Homotopic.trans⟩
 #align gen_loop.homotopic.equiv GenLoop.Homotopic.equiv
--/
 
-#print GenLoop.Homotopic.setoid /-
-instance setoid (n : ℕ) (x : X) : Setoid (GenLoop n x) :=
+instance setoid (N) (x : X) : Setoid (Ω^ N X x) :=
   ⟨Homotopic, equiv⟩
 #align gen_loop.homotopic.setoid GenLoop.Homotopic.setoid
--/
-
-end
 
 end homotopic
 
+section LoopHomeo
+
+variable [DecidableEq N]
+
+/-- Loop from a generalized loop by currying $I^N → X$ into $I → (I^{N\setminus\{j\}} → X)$. -/
+@[simps]
+def toLoop (i : N) (p : Ω^ N X x) : Ω (Ω^ { j // j ≠ i } X x) const
+    where
+  toFun t :=
+    ⟨(p.val.comp (Cube.insertAt i).toContinuousMap).curry t, fun y yH =>
+      p.property (Cube.insertAt i (t, y)) (Cube.insertAt_boundary i <| Or.inr yH)⟩
+  source' := by ext t; refine' p.property (Cube.insertAt i (0, t)) ⟨i, Or.inl _⟩; simp
+  target' := by ext t; refine' p.property (Cube.insertAt i (1, t)) ⟨i, Or.inr _⟩; simp
+#align gen_loop.to_loop GenLoop.toLoop
+
+theorem continuous_toLoop (i : N) : Continuous (@toLoop N X _ x _ i) :=
+  Path.continuous_uncurry_iff.1 <|
+    Continuous.subtype_mk
+      (ContinuousMap.continuous_eval'.comp <|
+        Continuous.prod_map
+          (ContinuousMap.continuous_curry.comp <|
+            (ContinuousMap.continuous_comp_left _).comp continuous_subtype_val)
+          continuous_id)
+      _
+#align gen_loop.continuous_to_loop GenLoop.continuous_toLoop
+
+/-- Generalized loop from a loop by uncurrying $I → (I^{N\setminus\{j\}} → X)$ into $I^N → X$. -/
+@[simps]
+def fromLoop (i : N) (p : Ω (Ω^ { j // j ≠ i } X x) const) : Ω^ N X x :=
+  ⟨(ContinuousMap.comp ⟨coe⟩ p.toContinuousMap).uncurry.comp (Cube.splitAt i).toContinuousMap,
+    by
+    rintro y ⟨j, Hj⟩
+    simp only [Subtype.val_eq_coe, ContinuousMap.comp_apply, to_continuous_map_apply,
+      fun_split_at_apply, ContinuousMap.uncurry_apply, ContinuousMap.coe_mk,
+      Function.uncurry_apply_pair]
+    obtain rfl | Hne := eq_or_ne j i
+    · cases Hj <;> simpa only [Hj, p.coe_to_continuous_map, p.source, p.target]
+    · exact GenLoop.boundary _ _ ⟨⟨j, Hne⟩, Hj⟩⟩
+#align gen_loop.from_loop GenLoop.fromLoop
+
+theorem continuous_fromLoop (i : N) : Continuous (@fromLoop N X _ x _ i) :=
+  ((ContinuousMap.continuous_comp_left _).comp <|
+        ContinuousMap.continuous_uncurry.comp <|
+          (ContinuousMap.continuous_comp _).comp continuous_induced_dom).subtype_mk
+    _
+#align gen_loop.continuous_from_loop GenLoop.continuous_fromLoop
+
+theorem to_from (i : N) (p : Ω (Ω^ { j // j ≠ i } X x) const) : toLoop i (fromLoop i p) = p :=
+  by
+  simp_rw [to_loop, from_loop, ContinuousMap.comp_assoc, to_continuous_map_as_coe,
+    to_continuous_map_comp_symm, ContinuousMap.comp_id]
+  ext; rfl
+#align gen_loop.to_from GenLoop.to_from
+
+/-- The `n+1`-dimensional loops are in bijection with the loops in the space of
+  `n`-dimensional loops with base point `const`.
+  We allow an arbitrary indexing type `N` in place of `fin n` here. -/
+@[simps]
+def loopHomeo (i : N) : Ω^ N X x ≃ₜ Ω (Ω^ { j // j ≠ i } X x) const
+    where
+  toFun := toLoop i
+  invFun := fromLoop i
+  left_inv p := by ext; exact congr_arg p (Equiv.apply_symm_apply _ _)
+  right_inv := to_from i
+  continuous_toFun := continuous_toLoop i
+  continuous_invFun := continuous_fromLoop i
+#align gen_loop.loop_homeo GenLoop.loopHomeo
+
+theorem toLoop_apply (i : N) {p : Ω^ N X x} {t} {tn} :
+    toLoop i p t tn = p (Cube.insertAt i ⟨t, tn⟩) :=
+  rfl
+#align gen_loop.to_loop_apply GenLoop.toLoop_apply
+
+theorem fromLoop_apply (i : N) {p : Ω (Ω^ { j // j ≠ i } X x) const} {t : I^N} :
+    fromLoop i p t = p (t i) (Cube.splitAt i t).snd :=
+  rfl
+#align gen_loop.from_loop_apply GenLoop.fromLoop_apply
+
+/-- Composition with `cube.insert_at` as a continuous map. -/
+@[reducible]
+def cCompInsert (i : N) : C(C(I^N, X), C(I × I^{ j // j ≠ i }, X)) :=
+  ⟨fun f => f.comp (Cube.insertAt i).toContinuousMap,
+    (Cube.insertAt i).toContinuousMap.continuous_comp_left⟩
+#align gen_loop.c_comp_insert GenLoop.cCompInsert
+
+/-- A homotopy between `n+1`-dimensional loops `p` and `q` constant on the boundary
+  seen as a homotopy between two paths in the space of `n`-dimensional paths. -/
+def homotopyTo (i : N) {p q : Ω^ N X x} (H : p.1.HomotopyRel q.1 (Cube.boundary N)) :
+    C(I × I, C(I^{ j // j ≠ i }, X)) :=
+  ((⟨_, ContinuousMap.continuous_curry⟩ : C(_, _)).comp <|
+      (cCompInsert i).comp H.toContinuousMap.curry).uncurry
+#align gen_loop.homotopy_to GenLoop.homotopyTo
+
+-- Should be generated with `@[simps]` but it times out.
+theorem homotopyTo_apply (i : N) {p q : Ω^ N X x} (H : p.1.HomotopyRel q.1 <| Cube.boundary N)
+    (t : I × I) (tₙ : I^{ j // j ≠ i }) :
+    homotopyTo i H t tₙ = H (t.fst, Cube.insertAt i (t.snd, tₙ)) :=
+  rfl
+#align gen_loop.homotopy_to_apply GenLoop.homotopyTo_apply
+
+theorem homotopic_to (i : N) {p q : Ω^ N X x} :
+    Homotopic p q → (toLoop i p).Homotopic (toLoop i q) :=
+  by
+  refine' Nonempty.map fun H => ⟨⟨⟨fun t => ⟨homotopy_to i H t, _⟩, _⟩, _, _⟩, _⟩
+  · rintro y ⟨i, iH⟩
+    rw [homotopy_to_apply, H.eq_fst, p.2]
+    all_goals apply Cube.insertAt_boundary; right; exact ⟨i, iH⟩
+  · continuity
+  show ∀ _ _ _, _
+  · intro t y yH
+    constructor <;> ext <;> erw [homotopy_to_apply]
+    apply H.eq_fst; on_goal 2 => apply H.eq_snd
+    all_goals use i; rw [fun_split_at_symm_apply, dif_pos rfl]; exact yH
+  all_goals intro; ext; erw [homotopy_to_apply, to_loop_apply]
+  exacts [H.apply_zero _, H.apply_one _]
+#align gen_loop.homotopic_to GenLoop.homotopic_to
+
+/-- The converse to `gen_loop.homotopy_to`: a homotopy between two loops in the space of
+  `n`-dimensional loops can be seen as a homotopy between two `n+1`-dimensional paths. -/
+def homotopyFrom (i : N) {p q : Ω^ N X x} (H : (toLoop i p).Homotopy (toLoop i q)) :
+    C(I × I^N, X) :=
+  (ContinuousMap.comp ⟨_, ContinuousMap.continuous_uncurry⟩
+          (ContinuousMap.comp ⟨coe⟩ H.toContinuousMap).curry).uncurry.comp <|
+    (ContinuousMap.id I).Prod_map (Cube.splitAt i).toContinuousMap
+#align gen_loop.homotopy_from GenLoop.homotopyFrom
+
+-- Should be generated with `@[simps]` but it times out.
+theorem homotopyFrom_apply (i : N) {p q : Ω^ N X x} (H : (toLoop i p).Homotopy (toLoop i q))
+    (t : I × I^N) : homotopyFrom i H t = H (t.fst, t.snd i) fun j => t.snd ↑j :=
+  rfl
+#align gen_loop.homotopy_from_apply GenLoop.homotopyFrom_apply
+
+theorem homotopicFrom (i : N) {p q : Ω^ N X x} :
+    (toLoop i p).Homotopic (toLoop i q) → Homotopic p q :=
+  by
+  refine' Nonempty.map fun H => ⟨⟨homotopy_from i H, _, _⟩, _⟩
+  show ∀ _ _ _, _
+  · rintro t y ⟨j, jH⟩
+    erw [homotopy_from_apply]
+    obtain rfl | h := eq_or_ne j i
+    · constructor
+      · rw [H.eq_fst]; exacts [congr_arg p (Equiv.right_inv _ _), jH]
+      · rw [H.eq_snd]; exacts [congr_arg q (Equiv.right_inv _ _), jH]
+    · rw [p.2 _ ⟨j, jH⟩, q.2 _ ⟨j, jH⟩]; constructor <;> · apply boundary; exact ⟨⟨j, h⟩, jH⟩
+  all_goals
+    intro
+    convert homotopy_from_apply _ _ _
+    first
+    | rw [H.apply_zero]
+    | rw [H.apply_one]
+    first
+    | apply congr_arg p
+    | apply congr_arg q
+    exact (Equiv.right_inv _ _).symm
+#align gen_loop.homotopic_from GenLoop.homotopicFrom
+
+/-- Concatenation of two `gen_loop`s along the `i`th coordinate. -/
+def transAt (i : N) (f g : Ω^ N X x) : Ω^ N X x :=
+  copy (fromLoop i <| (toLoop i f).trans <| toLoop i g)
+    (fun t =>
+      if (t i : ℝ) ≤ 1 / 2 then f (t.update i <| Set.projIcc 0 1 zero_le_one (2 * t i))
+      else g (t.update i <| Set.projIcc 0 1 zero_le_one (2 * t i - 1)))
+    (by
+      ext1; symm
+      dsimp only [Path.trans, from_loop, Path.coe_mk_mk, Function.comp_apply, mk_apply,
+        ContinuousMap.comp_apply, to_continuous_map_apply, fun_split_at_apply,
+        ContinuousMap.uncurry_apply, ContinuousMap.coe_mk, Function.uncurry_apply_pair]
+      split_ifs; change f _ = _; swap; change g _ = _
+      all_goals congr 1)
+#align gen_loop.trans_at GenLoop.transAt
+
+/-- Reversal of a `gen_loop` along the `i`th coordinate. -/
+def symmAt (i : N) (f : Ω^ N X x) : Ω^ N X x :=
+  (copy (fromLoop i (toLoop i f).symm) fun t => f fun j => if j = i then σ (t i) else t j) <| by
+    ext1; change _ = f _; congr; ext1; simp
+#align gen_loop.symm_at GenLoop.symmAt
+
+theorem transAt_distrib {i j : N} (h : i ≠ j) (a b c d : Ω^ N X x) :
+    transAt i (transAt j a b) (transAt j c d) = transAt j (transAt i a c) (transAt i b d) :=
+  by
+  ext; simp_rw [trans_at, coe_copy, Function.update_apply, if_neg h, if_neg h.symm]
+  split_ifs <;>
+    · congr 1; ext1; simp only [Function.update, eq_rec_constant, dite_eq_ite]
+      apply ite_ite_comm; rintro rfl; exact h.symm
+#align gen_loop.trans_at_distrib GenLoop.transAt_distrib
+
+theorem fromLoop_trans_toLoop {i : N} {p q : Ω^ N X x} :
+    fromLoop i ((toLoop i p).trans <| toLoop i q) = transAt i p q :=
+  (copy_eq _ _).symm
+#align gen_loop.from_loop_trans_to_loop GenLoop.fromLoop_trans_toLoop
+
+theorem fromLoop_symm_toLoop {i : N} {p : Ω^ N X x} : fromLoop i (toLoop i p).symm = symmAt i p :=
+  (copy_eq _ _).symm
+#align gen_loop.from_loop_symm_to_loop GenLoop.fromLoop_symm_toLoop
+
+end LoopHomeo
+
 end GenLoop
 
-#print HomotopyGroup /-
-/-- The `n`th homotopy group at `x` defined as the quotient of `gen_loop n x` by the
-`homotopic` relation.
--/
-def HomotopyGroup (n : ℕ) (x : X) : Type _ :=
-  Quotient (GenLoop.Homotopic.setoid n x)
+/-- The `n`th homotopy group at `x` defined as the quotient of `Ω^n x` by the
+  `gen_loop.homotopic` relation. -/
+def HomotopyGroup (N) (X : Type _) [TopologicalSpace X] (x : X) : Type _ :=
+  Quotient (GenLoop.Homotopic.setoid N x)
 deriving Inhabited
 #align homotopy_group HomotopyGroup
--/
 
--- mathport name: exprπ
-local notation "π" => HomotopyGroup
+variable [DecidableEq N]
 
-#print genLoopZeroEquiv /-
-/-- The 0-dimensional generalized loops based at `x` are in 1-1 correspondence with `X`. -/
-def genLoopZeroEquiv : GenLoop 0 x ≃ X where
+open GenLoop
+
+/-- Equivalence between the homotopy group of X and the fundamental group of
+  `Ω^{j // j ≠ i} x`. -/
+def homotopyGroupEquivFundamentalGroup (i : N) :
+    HomotopyGroup N X x ≃ FundamentalGroup (Ω^ { j // j ≠ i } X x) const :=
+  by
+  refine' Equiv.trans _ (CategoryTheory.Groupoid.isoEquivHom _ _).symm
+  apply Quotient.congr (loop_homeo i).toEquiv
+  exact fun p q => ⟨homotopic_to i, homotopic_from i⟩
+#align homotopy_group_equiv_fundamental_group homotopyGroupEquivFundamentalGroup
+
+/-- Homotopy group of finite index. -/
+@[reducible]
+def HomotopyGroup.Pi (n) (X : Type _) [TopologicalSpace X] (x : X) :=
+  HomotopyGroup (Fin n) _ x
+#align homotopy_group.pi HomotopyGroup.Pi
+
+-- mathport name: exprπ_
+scoped[Topology] notation "π_" => HomotopyGroup.Pi
+
+/-- The 0-dimensional generalized loops based at `x` are in bijection with `X`. -/
+def genLoopHomeoOfIsEmpty (N x) [IsEmpty N] : Ω^ N X x ≃ₜ X
+    where
   toFun f := f 0
-  invFun x := ⟨ContinuousMap.const _ x, fun _ ⟨f0, _⟩ => f0.elim0ₓ⟩
-  left_inv f := by ext1; exact congr_arg f (Subsingleton.elim _ _)
+  invFun y := ⟨ContinuousMap.const _ y, fun _ ⟨i, _⟩ => isEmptyElim i⟩
+  left_inv f := by ext; exact congr_arg f (Subsingleton.elim _ _)
   right_inv _ := rfl
-#align gen_loop_zero_equiv genLoopZeroEquiv
--/
-
-#print pi0EquivPathComponents /-
-/-- The 0th homotopy "group" is equivalent to the path components of `X`, aka the `zeroth_homotopy`.
--/
-def pi0EquivPathComponents : π 0 x ≃ ZerothHomotopy X :=
-  Quotient.congr genLoopZeroEquiv
+  continuous_toFun := (ContinuousMap.continuous_eval_const' (0 : N → I)).comp continuous_induced_dom
+  continuous_invFun := ContinuousMap.const'.2.subtype_mk _
+#align gen_loop_homeo_of_is_empty genLoopHomeoOfIsEmpty
+
+/-- The homotopy "group" indexed by an empty type is in bijection with
+  the path components of `X`, aka the `zeroth_homotopy`. -/
+def homotopyGroupEquivZerothHomotopyOfIsEmpty (N x) [IsEmpty N] :
+    HomotopyGroup N X x ≃ ZerothHomotopy X :=
+  Quotient.congr (genLoopHomeoOfIsEmpty N x).toEquiv
     (by
       -- joined iff homotopic
       intros;
       constructor <;> rintro ⟨H⟩
       exacts
-        [⟨{ toFun := fun t => H ⟨t, Fin.elim0⟩
-            source' := (H.apply_zero _).trans (congr_arg a₁ matrix.zero_empty.symm)
-            target' := (H.apply_one _).trans (congr_arg a₂ matrix.zero_empty.symm) }⟩,
+        [⟨{ toFun := fun t => H ⟨t, isEmptyElim⟩
+            source' := (H.apply_zero _).trans (congr_arg a₁ <| Subsingleton.elim _ _)
+            target' := (H.apply_one _).trans (congr_arg a₂ <| Subsingleton.elim _ _) }⟩,
         ⟨{  toFun := fun t0 => H t0.fst
             map_zero_left' := fun _ => by convert H.source
             map_one_left' := fun _ => by convert H.target
-            prop' := fun _ _ ⟨i, _⟩ => i.elim0ₓ }⟩])
-#align pi0_equiv_path_components pi0EquivPathComponents
--/
+            prop' := fun _ _ ⟨i, _⟩ => isEmptyElim i }⟩])
+#align homotopy_group_equiv_zeroth_homotopy_of_is_empty homotopyGroupEquivZerothHomotopyOfIsEmpty
+
+/-- The 0th homotopy "group" is in bijection with `zeroth_homotopy`. -/
+def HomotopyGroup.pi0EquivZerothHomotopy : π_ 0 X x ≃ ZerothHomotopy X :=
+  homotopyGroupEquivZerothHomotopyOfIsEmpty (Fin 0) x
+#align homotopy_group.pi_0_equiv_zeroth_homotopy HomotopyGroup.pi0EquivZerothHomotopy
 
-#print genLoopOneEquivPathSelf /-
-/-- The 1-dimensional generalized loops based at `x` are in 1-1 correspondence with
-  paths from `x` to itself. -/
+/-- The 1-dimensional generalized loops based at `x` are in bijection with loops at `x`. -/
 @[simps]
-def genLoopOneEquivPathSelf : GenLoop 1 x ≃ Path x x
+def genLoopEquivOfUnique (N) [Unique N] : Ω^ N X x ≃ Ω X x
     where
   toFun p :=
-    Path.mk ⟨fun t => p fun _ => t, by continuity; exact p.1.2⟩
-      (p.boundary (fun _ => 0) ⟨0, Or.inl rfl⟩) (p.boundary (fun _ => 1) ⟨1, Or.inr rfl⟩)
+    Path.mk ⟨fun t => p fun _ => t, by continuity⟩
+      (GenLoop.boundary _ (fun _ => 0) ⟨default, Or.inl rfl⟩)
+      (GenLoop.boundary _ (fun _ => 1) ⟨default, Or.inr rfl⟩)
   invFun p :=
-    { toFun := fun c => p c.headI
-      boundary :=
-        by
-        rintro y ⟨i, iH | iH⟩ <;> cases Unique.eq_default i <;> apply (congr_arg p iH).trans
-        exacts [p.source, p.target] }
-  left_inv p := by ext1; exact congr_arg p y.one_char.symm
+    ⟨⟨fun c => p (c default), by continuity⟩,
+      by
+      rintro y ⟨i, iH | iH⟩ <;> cases Unique.eq_default i <;> apply (congr_arg p iH).trans
+      exacts [p.source, p.target]⟩
+  left_inv p := by ext; exact congr_arg p (eq_const_of_unique y).symm
   right_inv p := by ext; rfl
-#align gen_loop_one_equiv_path_self genLoopOneEquivPathSelf
--/
-
-#print pi1EquivFundamentalGroup /-
-/-- The first homotopy group at `x` is equivalent to the fundamental group,
-i.e. the loops based at `x` up to homotopy.
--/
-def pi1EquivFundamentalGroup : π 1 x ≃ FundamentalGroup X x :=
+#align gen_loop_equiv_of_unique genLoopEquivOfUnique
+
+/- TODO (?): deducing this from `homotopy_group_equiv_fundamental_group` would require
+  combination of `category_theory.functor.map_Aut` and
+  `fundamental_groupoid.fundamental_groupoid_functor` applied to `gen_loop_homeo_of_is_empty`,
+  with possibly worse defeq. -/
+/-- The homotopy group at `x` indexed by a singleton is in bijection with the fundamental group,
+  i.e. the loops based at `x` up to homotopy. -/
+def homotopyGroupEquivFundamentalGroupOfUnique (N) [Unique N] :
+    HomotopyGroup N X x ≃ FundamentalGroup X x :=
   by
   refine' Equiv.trans _ (CategoryTheory.Groupoid.isoEquivHom _ _).symm
-  refine' Quotient.congr genLoopOneEquivPathSelf _
-  -- homotopic iff homotopic
-  intros;
-  constructor <;> rintro ⟨H⟩
-  exacts
-    [⟨{ toFun := fun tx => H (tx.fst, fun _ => tx.snd)
-        map_zero_left' := fun _ => by convert H.apply_zero _
-        map_one_left' := fun _ => by convert H.apply_one _
-        prop' := fun t y iH => H.prop' _ _ ⟨0, iH⟩ }⟩,
-    ⟨{  toFun := fun tx => H (tx.fst, tx.snd.head)
-        map_zero_left' := fun y => by convert H.apply_zero _; exact y.one_char
-        map_one_left' := fun y => by convert H.apply_one _; exact y.one_char
-        prop' := fun t y ⟨i, iH⟩ => by
-          cases Unique.eq_default i; constructor
-          · convert H.eq_fst _ _; exacts [y.one_char, iH]
-          · convert H.eq_snd _ _; exacts [y.one_char, iH] }⟩]
-#align pi1_equiv_fundamental_group pi1EquivFundamentalGroup
--/
+  refine' Quotient.congr (genLoopEquivOfUnique N) _
+  intros; constructor <;> rintro ⟨H⟩
+  ·
+    exact
+      ⟨{  toFun := fun tx => H (tx.fst, fun _ => tx.snd)
+          map_zero_left' := fun _ => H.apply_zero _
+          map_one_left' := fun _ => H.apply_one _
+          prop' := fun t y iH => H.prop' _ _ ⟨default, iH⟩ }⟩
+  refine'
+    ⟨⟨⟨⟨fun tx => H (tx.fst, tx.snd default), H.continuous.comp _⟩, fun y => _, fun y => _⟩, _⟩⟩
+  · exact continuous_fst.prod_mk ((continuous_apply _).comp continuous_snd)
+  · convert H.apply_zero _; exact eq_const_of_unique y
+  · convert H.apply_one _; exact eq_const_of_unique y
+  · rintro t y ⟨i, iH⟩
+    cases Unique.eq_default i; constructor
+    · convert H.eq_fst _ _; exacts [eq_const_of_unique y, iH]
+    · convert H.eq_snd _ _; exacts [eq_const_of_unique y, iH]
+#align homotopy_group_equiv_fundamental_group_of_unique homotopyGroupEquivFundamentalGroupOfUnique
+
+/-- The first homotopy group at `x` is in bijection with the fundamental group. -/
+def HomotopyGroup.pi1EquivFundamentalGroup : π_ 1 X x ≃ FundamentalGroup X x :=
+  homotopyGroupEquivFundamentalGroupOfUnique (Fin 1)
+#align homotopy_group.pi_1_equiv_fundamental_group HomotopyGroup.pi1EquivFundamentalGroup
+
+namespace HomotopyGroup
+
+/-- Group structure on `homotopy_group N X x` for nonempty `N` (in particular `π_(n+1) X x`). -/
+instance group (N) [DecidableEq N] [Nonempty N] : Group (HomotopyGroup N X x) :=
+  (homotopyGroupEquivFundamentalGroup <| Classical.arbitrary N).Group
+#align homotopy_group.group HomotopyGroup.group
+
+/-- Group structure on `homotopy_group` obtained by pulling back path composition along the
+  `i`th direction. The group structures for two different `i j : N` distribute over each
+  other, and therefore are equal by the Eckmann-Hilton argument. -/
+@[reducible]
+def auxGroup (i : N) : Group (HomotopyGroup N X x) :=
+  (homotopyGroupEquivFundamentalGroup i).Group
+#align homotopy_group.aux_group HomotopyGroup.auxGroup
+
+theorem isUnital_auxGroup (i : N) :
+    EckmannHilton.IsUnital (auxGroup i).mul (⟦const⟧ : HomotopyGroup N X x) :=
+  ⟨⟨(auxGroup i).one_mul⟩, ⟨(auxGroup i).mul_one⟩⟩
+#align homotopy_group.is_unital_aux_group HomotopyGroup.isUnital_auxGroup
+
+theorem auxGroup_indep (i j : N) : (auxGroup i : Group (HomotopyGroup N X x)) = auxGroup j :=
+  by
+  by_cases h : i = j; · rw [h]
+  refine' Group.ext (EckmannHilton.mul (is_unital_aux_group i) (is_unital_aux_group j) _)
+  rintro ⟨a⟩ ⟨b⟩ ⟨c⟩ ⟨d⟩
+  change Quotient.mk' _ = _
+  apply congr_arg Quotient.mk'
+  simp only [from_loop_trans_to_loop, trans_at_distrib h, coe_to_equiv, loop_homeo_apply,
+    coe_symm_to_equiv, loop_homeo_symm_apply]
+#align homotopy_group.aux_group_indep HomotopyGroup.auxGroup_indep
+
+theorem transAt_indep {i} (j) (f g : Ω^ N X x) : ⟦transAt i f g⟧ = ⟦transAt j f g⟧ :=
+  by
+  simp_rw [← from_loop_trans_to_loop]
+  have := congr_arg (@Group.mul _) (aux_group_indep i j)
+  exact congr_fun₂ this ⟦g⟧ ⟦f⟧
+#align homotopy_group.trans_at_indep HomotopyGroup.transAt_indep
+
+theorem symmAt_indep {i} (j) (f : Ω^ N X x) : ⟦symmAt i f⟧ = ⟦symmAt j f⟧ :=
+  by
+  simp_rw [← from_loop_symm_to_loop]
+  have := congr_arg (@Group.inv _) (aux_group_indep i j)
+  exact congr_fun this ⟦f⟧
+#align homotopy_group.symm_at_indep HomotopyGroup.symmAt_indep
+
+/-- Characterization of multiplicative identity -/
+theorem one_def [Nonempty N] : (1 : HomotopyGroup N X x) = ⟦const⟧ :=
+  rfl
+#align homotopy_group.one_def HomotopyGroup.one_def
+
+/-- Characterization of multiplication -/
+theorem mul_spec [Nonempty N] {i} {p q : Ω^ N X x} :
+    (⟦p⟧ * ⟦q⟧ : HomotopyGroup N X x) = ⟦transAt i q p⟧ := by
+  rw [trans_at_indep _ q, ← from_loop_trans_to_loop]; apply Quotient.sound; rfl
+#align homotopy_group.mul_spec HomotopyGroup.mul_spec
+
+/-- Characterization of multiplicative inverse -/
+theorem inv_spec [Nonempty N] {i} {p : Ω^ N X x} : ((⟦p⟧)⁻¹ : HomotopyGroup N X x) = ⟦symmAt i p⟧ :=
+  by rw [symm_at_indep _ p, ← from_loop_symm_to_loop]; apply Quotient.sound; rfl
+#align homotopy_group.inv_spec HomotopyGroup.inv_spec
+
+/-- Multiplication on `homotopy_group N X x` is commutative for nontrivial `N`.
+  In particular, multiplication on `π_(n+2)` is commutative. -/
+instance commGroup [Nontrivial N] : CommGroup (HomotopyGroup N X x) :=
+  let h := exists_ne (Classical.arbitrary N)
+  @EckmannHilton.commGroup (HomotopyGroup N X x) _ 1 (isUnital_auxGroup h.some) _
+    (by
+      rintro ⟨a⟩ ⟨b⟩ ⟨c⟩ ⟨d⟩
+      apply congr_arg Quotient.mk'
+      simp only [from_loop_trans_to_loop, trans_at_distrib h.some_spec, coe_to_equiv,
+        loop_homeo_apply, coe_symm_to_equiv, loop_homeo_symm_apply])
+#align homotopy_group.comm_group HomotopyGroup.commGroup
+
+end HomotopyGroup
 
Diff
@@ -70,7 +70,7 @@ theorem proj_continuous (i : Fin n) : Continuous fun f : I^ n => f i :=
 /-- The points of the `n`-dimensional cube with at least one projection equal to 0 or 1.
 -/
 def boundary (n : ℕ) : Set (I^ n) :=
-  { y | ∃ i, y i = 0 ∨ y i = 1 }
+  {y | ∃ i, y i = 0 ∨ y i = 1}
 #align cube.boundary Cube.boundary
 -/
 
Diff
@@ -4,7 +4,7 @@ Released under Apache 2.0 license as described in the file LICENSE.
 Authors: Roberto Alvarez
 
 ! This file was ported from Lean 3 source module topology.homotopy.homotopy_group
-! leanprover-community/mathlib commit f2ce6086713c78a7f880485f7917ea547a215982
+! leanprover-community/mathlib commit 599fffe78f0e11eb6a034e834ec51882167b9688
 ! Please do not edit these lines, except to modify the commit id
 ! if you have ported upstream changes.
 -/
@@ -13,6 +13,9 @@ import Mathbin.AlgebraicTopology.FundamentalGroupoid.FundamentalGroup
 /-!
 # `n`th homotopy group
 
+> THIS FILE IS SYNCHRONIZED WITH MATHLIB4.
+> Any changes to this file require a corresponding PR to mathlib4.
+
 We define the `n`th homotopy group at `x`, `π n x`, as the equivalence classes
 of functions from the nth dimensional cube to the topological space `X`
 that send the boundary to the base point `x`, up to homotopic equivalence.
Diff
@@ -48,7 +48,8 @@ variable {n : ℕ} {x : X}
 /-- The `n`-dimensional cube.
 -/
 def Cube (n : ℕ) : Type :=
-  Fin n → I deriving Zero, One, TopologicalSpace
+  Fin n → I
+deriving Zero, One, TopologicalSpace
 #align cube Cube
 -/
 
@@ -117,7 +118,7 @@ namespace GenLoop
 instance funLike : FunLike (GenLoop n x) (I^ n) fun _ => X
     where
   coe f := f.1
-  coe_injective' := fun ⟨⟨f, _⟩, _⟩ ⟨⟨g, _⟩, _⟩ h => by congr ; exact h
+  coe_injective' := fun ⟨⟨f, _⟩, _⟩ ⟨⟨g, _⟩, _⟩ h => by congr; exact h
 #align gen_loop.fun_like GenLoop.funLike
 -/
 
@@ -204,7 +205,8 @@ end GenLoop
 `homotopic` relation.
 -/
 def HomotopyGroup (n : ℕ) (x : X) : Type _ :=
-  Quotient (GenLoop.Homotopic.setoid n x)deriving Inhabited
+  Quotient (GenLoop.Homotopic.setoid n x)
+deriving Inhabited
 #align homotopy_group HomotopyGroup
 -/
 
@@ -228,10 +230,10 @@ def pi0EquivPathComponents : π 0 x ≃ ZerothHomotopy X :=
   Quotient.congr genLoopZeroEquiv
     (by
       -- joined iff homotopic
-      intros ;
+      intros;
       constructor <;> rintro ⟨H⟩
-      exacts[⟨{
-            toFun := fun t => H ⟨t, Fin.elim0⟩
+      exacts
+        [⟨{ toFun := fun t => H ⟨t, Fin.elim0⟩
             source' := (H.apply_zero _).trans (congr_arg a₁ matrix.zero_empty.symm)
             target' := (H.apply_one _).trans (congr_arg a₂ matrix.zero_empty.symm) }⟩,
         ⟨{  toFun := fun t0 => H t0.fst
@@ -255,7 +257,7 @@ def genLoopOneEquivPathSelf : GenLoop 1 x ≃ Path x x
       boundary :=
         by
         rintro y ⟨i, iH | iH⟩ <;> cases Unique.eq_default i <;> apply (congr_arg p iH).trans
-        exacts[p.source, p.target] }
+        exacts [p.source, p.target] }
   left_inv p := by ext1; exact congr_arg p y.one_char.symm
   right_inv p := by ext; rfl
 #align gen_loop_one_equiv_path_self genLoopOneEquivPathSelf
@@ -270,10 +272,10 @@ def pi1EquivFundamentalGroup : π 1 x ≃ FundamentalGroup X x :=
   refine' Equiv.trans _ (CategoryTheory.Groupoid.isoEquivHom _ _).symm
   refine' Quotient.congr genLoopOneEquivPathSelf _
   -- homotopic iff homotopic
-  intros ;
+  intros;
   constructor <;> rintro ⟨H⟩
-  exacts[⟨{
-        toFun := fun tx => H (tx.fst, fun _ => tx.snd)
+  exacts
+    [⟨{ toFun := fun tx => H (tx.fst, fun _ => tx.snd)
         map_zero_left' := fun _ => by convert H.apply_zero _
         map_one_left' := fun _ => by convert H.apply_one _
         prop' := fun t y iH => H.prop' _ _ ⟨0, iH⟩ }⟩,
@@ -282,8 +284,8 @@ def pi1EquivFundamentalGroup : π 1 x ≃ FundamentalGroup X x :=
         map_one_left' := fun y => by convert H.apply_one _; exact y.one_char
         prop' := fun t y ⟨i, iH⟩ => by
           cases Unique.eq_default i; constructor
-          · convert H.eq_fst _ _; exacts[y.one_char, iH]
-          · convert H.eq_snd _ _; exacts[y.one_char, iH] }⟩]
+          · convert H.eq_fst _ _; exacts [y.one_char, iH]
+          · convert H.eq_snd _ _; exacts [y.one_char, iH] }⟩]
 #align pi1_equiv_fundamental_group pi1EquivFundamentalGroup
 -/
 
Diff
@@ -44,11 +44,13 @@ variable {X : Type u} [TopologicalSpace X]
 
 variable {n : ℕ} {x : X}
 
+#print Cube /-
 /-- The `n`-dimensional cube.
 -/
 def Cube (n : ℕ) : Type :=
   Fin n → I deriving Zero, One, TopologicalSpace
 #align cube Cube
+-/
 
 -- mathport name: «exprI^»
 local notation "I^" => Cube
@@ -60,77 +62,97 @@ theorem proj_continuous (i : Fin n) : Continuous fun f : I^ n => f i :=
   continuous_apply i
 #align cube.proj_continuous Cube.proj_continuous
 
+#print Cube.boundary /-
 /-- The points of the `n`-dimensional cube with at least one projection equal to 0 or 1.
 -/
 def boundary (n : ℕ) : Set (I^ n) :=
   { y | ∃ i, y i = 0 ∨ y i = 1 }
 #align cube.boundary Cube.boundary
+-/
 
+#print Cube.head /-
 /-- The first projection of a positive-dimensional cube.
 -/
 @[simp]
 def head {n} : I^ (n + 1) → I := fun c => c 0
 #align cube.head Cube.head
+-/
 
 @[continuity]
 theorem head.continuous {n} : Continuous (@head n) :=
   proj_continuous 0
 #align cube.head.continuous Cube.head.continuous
 
+#print Cube.tail /-
 /-- The projection to the last `n` coordinates from an `n+1` dimensional cube.
 -/
 @[simp]
 def tail {n} : I^ (n + 1) → I^ n := fun c => Fin.tail c
 #align cube.tail Cube.tail
+-/
 
+#print Cube.uniqueCube0 /-
 instance uniqueCube0 : Unique (I^ 0) :=
   Pi.uniqueOfIsEmpty _
 #align cube.unique_cube0 Cube.uniqueCube0
+-/
 
 theorem one_char (f : I^ 1) : f = fun _ => f 0 := by convert eq_const_of_unique f
 #align cube.one_char Cube.one_char
 
 end Cube
 
+#print GenLoop /-
 /--
 The `n`-dimensional generalized loops; functions `I^n → X` that send the boundary to the base point.
 -/
 structure GenLoop (n : ℕ) (x : X) extends C(I^ n, X) where
   boundary : ∀ y ∈ Cube.boundary n, to_fun y = x
 #align gen_loop GenLoop
+-/
 
 namespace GenLoop
 
+#print GenLoop.funLike /-
 instance funLike : FunLike (GenLoop n x) (I^ n) fun _ => X
     where
   coe f := f.1
   coe_injective' := fun ⟨⟨f, _⟩, _⟩ ⟨⟨g, _⟩, _⟩ h => by congr ; exact h
 #align gen_loop.fun_like GenLoop.funLike
+-/
 
+#print GenLoop.ext /-
 @[ext]
 theorem ext (f g : GenLoop n x) (H : ∀ y, f y = g y) : f = g :=
   FunLike.ext f g H
 #align gen_loop.ext GenLoop.ext
+-/
 
 @[simp]
 theorem mk_apply (f : C(I^ n, X)) (H y) : (⟨f, H⟩ : GenLoop n x) y = f y :=
   rfl
 #align gen_loop.mk_apply GenLoop.mk_apply
 
+#print GenLoop.const /-
 /-- The constant `gen_loop` at `x`.
 -/
 def const : GenLoop n x :=
   ⟨ContinuousMap.const _ x, fun _ _ => rfl⟩
 #align gen_loop.const GenLoop.const
+-/
 
+#print GenLoop.inhabited /-
 instance inhabited : Inhabited (GenLoop n x) where default := const
 #align gen_loop.inhabited GenLoop.inhabited
+-/
 
+#print GenLoop.Homotopic /-
 /-- The "homotopy relative to boundary" relation between `gen_loop`s.
 -/
 def Homotopic (f g : GenLoop n x) : Prop :=
   f.toContinuousMap.HomotopicRel g.toContinuousMap (Cube.boundary n)
 #align gen_loop.homotopic GenLoop.Homotopic
+-/
 
 namespace homotopic
 
@@ -138,28 +160,38 @@ section
 
 variable {f g h : GenLoop n x}
 
+#print GenLoop.Homotopic.refl /-
 @[refl]
 theorem refl (f : GenLoop n x) : Homotopic f f :=
   ContinuousMap.HomotopicRel.refl _
 #align gen_loop.homotopic.refl GenLoop.Homotopic.refl
+-/
 
+#print GenLoop.Homotopic.symm /-
 @[symm]
 theorem symm (H : f.Homotopic g) : g.Homotopic f :=
   H.symm
 #align gen_loop.homotopic.symm GenLoop.Homotopic.symm
+-/
 
+#print GenLoop.Homotopic.trans /-
 @[trans]
 theorem trans (H0 : f.Homotopic g) (H1 : g.Homotopic h) : f.Homotopic h :=
   H0.trans H1
 #align gen_loop.homotopic.trans GenLoop.Homotopic.trans
+-/
 
+#print GenLoop.Homotopic.equiv /-
 theorem equiv : Equivalence (@Homotopic X _ n x) :=
   ⟨Homotopic.refl, fun _ _ => Homotopic.symm, fun _ _ _ => Homotopic.trans⟩
 #align gen_loop.homotopic.equiv GenLoop.Homotopic.equiv
+-/
 
+#print GenLoop.Homotopic.setoid /-
 instance setoid (n : ℕ) (x : X) : Setoid (GenLoop n x) :=
   ⟨Homotopic, equiv⟩
 #align gen_loop.homotopic.setoid GenLoop.Homotopic.setoid
+-/
 
 end
 
@@ -167,16 +199,19 @@ end homotopic
 
 end GenLoop
 
+#print HomotopyGroup /-
 /-- The `n`th homotopy group at `x` defined as the quotient of `gen_loop n x` by the
 `homotopic` relation.
 -/
 def HomotopyGroup (n : ℕ) (x : X) : Type _ :=
   Quotient (GenLoop.Homotopic.setoid n x)deriving Inhabited
 #align homotopy_group HomotopyGroup
+-/
 
 -- mathport name: exprπ
 local notation "π" => HomotopyGroup
 
+#print genLoopZeroEquiv /-
 /-- The 0-dimensional generalized loops based at `x` are in 1-1 correspondence with `X`. -/
 def genLoopZeroEquiv : GenLoop 0 x ≃ X where
   toFun f := f 0
@@ -184,7 +219,9 @@ def genLoopZeroEquiv : GenLoop 0 x ≃ X where
   left_inv f := by ext1; exact congr_arg f (Subsingleton.elim _ _)
   right_inv _ := rfl
 #align gen_loop_zero_equiv genLoopZeroEquiv
+-/
 
+#print pi0EquivPathComponents /-
 /-- The 0th homotopy "group" is equivalent to the path components of `X`, aka the `zeroth_homotopy`.
 -/
 def pi0EquivPathComponents : π 0 x ≃ ZerothHomotopy X :=
@@ -202,7 +239,9 @@ def pi0EquivPathComponents : π 0 x ≃ ZerothHomotopy X :=
             map_one_left' := fun _ => by convert H.target
             prop' := fun _ _ ⟨i, _⟩ => i.elim0ₓ }⟩])
 #align pi0_equiv_path_components pi0EquivPathComponents
+-/
 
+#print genLoopOneEquivPathSelf /-
 /-- The 1-dimensional generalized loops based at `x` are in 1-1 correspondence with
   paths from `x` to itself. -/
 @[simps]
@@ -220,7 +259,9 @@ def genLoopOneEquivPathSelf : GenLoop 1 x ≃ Path x x
   left_inv p := by ext1; exact congr_arg p y.one_char.symm
   right_inv p := by ext; rfl
 #align gen_loop_one_equiv_path_self genLoopOneEquivPathSelf
+-/
 
+#print pi1EquivFundamentalGroup /-
 /-- The first homotopy group at `x` is equivalent to the fundamental group,
 i.e. the loops based at `x` up to homotopy.
 -/
@@ -244,4 +285,5 @@ def pi1EquivFundamentalGroup : π 1 x ≃ FundamentalGroup X x :=
           · convert H.eq_fst _ _; exacts[y.one_char, iH]
           · convert H.eq_snd _ _; exacts[y.one_char, iH] }⟩]
 #align pi1_equiv_fundamental_group pi1EquivFundamentalGroup
+-/
 
Diff
@@ -34,7 +34,7 @@ TODO: show that `π n x` is a group when `n > 0` and abelian when `n > 1`. Show
 -/
 
 
-open unitInterval Topology
+open scoped unitInterval Topology
 
 noncomputable section
 
Diff
@@ -104,10 +104,7 @@ namespace GenLoop
 instance funLike : FunLike (GenLoop n x) (I^ n) fun _ => X
     where
   coe f := f.1
-  coe_injective' := fun ⟨⟨f, _⟩, _⟩ ⟨⟨g, _⟩, _⟩ h =>
-    by
-    congr
-    exact h
+  coe_injective' := fun ⟨⟨f, _⟩, _⟩ ⟨⟨g, _⟩, _⟩ h => by congr ; exact h
 #align gen_loop.fun_like GenLoop.funLike
 
 @[ext]
@@ -184,9 +181,7 @@ local notation "π" => HomotopyGroup
 def genLoopZeroEquiv : GenLoop 0 x ≃ X where
   toFun f := f 0
   invFun x := ⟨ContinuousMap.const _ x, fun _ ⟨f0, _⟩ => f0.elim0ₓ⟩
-  left_inv f := by
-    ext1
-    exact congr_arg f (Subsingleton.elim _ _)
+  left_inv f := by ext1; exact congr_arg f (Subsingleton.elim _ _)
   right_inv _ := rfl
 #align gen_loop_zero_equiv genLoopZeroEquiv
 
@@ -214,10 +209,7 @@ def pi0EquivPathComponents : π 0 x ≃ ZerothHomotopy X :=
 def genLoopOneEquivPathSelf : GenLoop 1 x ≃ Path x x
     where
   toFun p :=
-    Path.mk
-      ⟨fun t => p fun _ => t, by
-        continuity
-        exact p.1.2⟩
+    Path.mk ⟨fun t => p fun _ => t, by continuity; exact p.1.2⟩
       (p.boundary (fun _ => 0) ⟨0, Or.inl rfl⟩) (p.boundary (fun _ => 1) ⟨1, Or.inr rfl⟩)
   invFun p :=
     { toFun := fun c => p c.headI
@@ -225,12 +217,8 @@ def genLoopOneEquivPathSelf : GenLoop 1 x ≃ Path x x
         by
         rintro y ⟨i, iH | iH⟩ <;> cases Unique.eq_default i <;> apply (congr_arg p iH).trans
         exacts[p.source, p.target] }
-  left_inv p := by
-    ext1
-    exact congr_arg p y.one_char.symm
-  right_inv p := by
-    ext
-    rfl
+  left_inv p := by ext1; exact congr_arg p y.one_char.symm
+  right_inv p := by ext; rfl
 #align gen_loop_one_equiv_path_self genLoopOneEquivPathSelf
 
 /-- The first homotopy group at `x` is equivalent to the fundamental group,
@@ -249,17 +237,11 @@ def pi1EquivFundamentalGroup : π 1 x ≃ FundamentalGroup X x :=
         map_one_left' := fun _ => by convert H.apply_one _
         prop' := fun t y iH => H.prop' _ _ ⟨0, iH⟩ }⟩,
     ⟨{  toFun := fun tx => H (tx.fst, tx.snd.head)
-        map_zero_left' := fun y => by
-          convert H.apply_zero _
-          exact y.one_char
-        map_one_left' := fun y => by
-          convert H.apply_one _
-          exact y.one_char
+        map_zero_left' := fun y => by convert H.apply_zero _; exact y.one_char
+        map_one_left' := fun y => by convert H.apply_one _; exact y.one_char
         prop' := fun t y ⟨i, iH⟩ => by
           cases Unique.eq_default i; constructor
-          · convert H.eq_fst _ _
-            exacts[y.one_char, iH]
-          · convert H.eq_snd _ _
-            exacts[y.one_char, iH] }⟩]
+          · convert H.eq_fst _ _; exacts[y.one_char, iH]
+          · convert H.eq_snd _ _; exacts[y.one_char, iH] }⟩]
 #align pi1_equiv_fundamental_group pi1EquivFundamentalGroup
 
Diff
@@ -135,7 +135,7 @@ def Homotopic (f g : GenLoop n x) : Prop :=
   f.toContinuousMap.HomotopicRel g.toContinuousMap (Cube.boundary n)
 #align gen_loop.homotopic GenLoop.Homotopic
 
-namespace Homotopic
+namespace homotopic
 
 section
 
@@ -166,7 +166,7 @@ instance setoid (n : ℕ) (x : X) : Setoid (GenLoop n x) :=
 
 end
 
-end Homotopic
+end homotopic
 
 end GenLoop
 

Changes in mathlib4

mathlib3
mathlib4
chore: remove mathport name: <expression> lines (#11928)

Quoting [@digama0](https://github.com/digama0):

These were actually never meant to go in the file, they are basically debugging information and only useful on significantly broken mathport files. You can safely remove all of them.

Diff
@@ -90,7 +90,6 @@ def LoopSpace :=
   Path x x
 #align loop_space LoopSpace
 
--- mathport name: exprΩ
 scoped[Topology.Homotopy] notation "Ω" => LoopSpace
 
 instance LoopSpace.inhabited : Inhabited (Path x x) :=
@@ -415,7 +414,6 @@ def HomotopyGroup.Pi (n) (X : Type*) [TopologicalSpace X] (x : X) :=
   HomotopyGroup (Fin n) _ x
 #align homotopy_group.pi HomotopyGroup.Pi
 
--- mathport name: exprπ_
 scoped[Topology] notation "π_" => HomotopyGroup.Pi
 
 /-- The 0-dimensional generalized loops based at `x` are in bijection with `X`. -/
chore: classify todo porting notes (#11216)

Classifies by adding issue number #11215 to porting notes claiming "TODO".

Diff
@@ -552,7 +552,7 @@ theorem one_def [Nonempty N] : (1 : HomotopyGroup N X x) = ⟦const⟧ :=
 
 /-- Characterization of multiplication -/
 theorem mul_spec [Nonempty N] {i} {p q : Ω^ N X x} :
-    -- Porting note: TODO: introduce `HomotopyGroup.mk` and remove defeq abuse.
+    -- Porting note (#11215): TODO: introduce `HomotopyGroup.mk` and remove defeq abuse.
     ((· * ·) : _ → _ → HomotopyGroup N X x) ⟦p⟧ ⟦q⟧ = ⟦transAt i q p⟧ := by
   rw [transAt_indep _ q, ← fromLoop_trans_toLoop]; apply Quotient.sound; rfl
 #align homotopy_group.mul_spec HomotopyGroup.mul_spec
style: homogenise porting notes (#11145)

Homogenises porting notes via capitalisation and addition of whitespace.

It makes the following changes:

  • converts "--porting note" into "-- Porting note";
  • converts "porting note" into "Porting note".
Diff
@@ -392,7 +392,7 @@ def HomotopyGroup (N X : Type*) [TopologicalSpace X] (x : X) : Type _ :=
   Quotient (GenLoop.Homotopic.setoid N x)
 #align homotopy_group HomotopyGroup
 
--- porting note: in Lean 3 this instance was derived
+-- Porting note: in Lean 3 this instance was derived
 instance : Inhabited (HomotopyGroup N X x) :=
   inferInstanceAs <| Inhabited <| Quotient (GenLoop.Homotopic.setoid N x)
 
@@ -552,7 +552,7 @@ theorem one_def [Nonempty N] : (1 : HomotopyGroup N X x) = ⟦const⟧ :=
 
 /-- Characterization of multiplication -/
 theorem mul_spec [Nonempty N] {i} {p q : Ω^ N X x} :
-    -- porting note: TODO: introduce `HomotopyGroup.mk` and remove defeq abuse.
+    -- Porting note: TODO: introduce `HomotopyGroup.mk` and remove defeq abuse.
     ((· * ·) : _ → _ → HomotopyGroup N X x) ⟦p⟧ ⟦q⟧ = ⟦transAt i q p⟧ := by
   rw [transAt_indep _ q, ← fromLoop_trans_toLoop]; apply Quotient.sound; rfl
 #align homotopy_group.mul_spec HomotopyGroup.mul_spec
chore: classify slow / slower porting notes (#11084)

Classifies by adding issue number #11083 to porting notes claiming anything semantically equivalent to:

  • "very slow; improve performance?"
  • "quite slow; improve performance?"
  • "`tactic" was slow"
  • "removed attribute because it caused extremely slow tactic"
  • "proof was rewritten, because it was too slow"
  • "doing this make things very slow"
  • "slower implementation"
Diff
@@ -289,7 +289,7 @@ def homotopyTo (i : N) {p q : Ω^ N X x} (H : p.1.HomotopyRel q.1 (Cube.boundary
       (cCompInsert i).comp H.toContinuousMap.curry).uncurry
 #align gen_loop.homotopy_to GenLoop.homotopyTo
 
--- porting note: `@[simps]` no longer too slow in Lean 4 but does not generate this lemma.
+-- porting note (#11083): `@[simps]` no longer too slow in Lean 4 but does not generate this lemma.
 theorem homotopyTo_apply (i : N) {p q : Ω^ N X x} (H : p.1.HomotopyRel q.1 <| Cube.boundary N)
     (t : I × I) (tₙ : I^{ j // j ≠ i }) :
     homotopyTo i H t tₙ = H (t.fst, Cube.insertAt i (t.snd, tₙ)) :=
@@ -320,7 +320,7 @@ theorem homotopicTo (i : N) {p q : Ω^ N X x} :
           (ContinuousMap.comp ⟨Subtype.val, by continuity⟩ H.toContinuousMap).curry).uncurry.comp <|
     (ContinuousMap.id I).prodMap (Cube.splitAt i).toContinuousMap
 #align gen_loop.homotopy_from GenLoop.homotopyFrom
--- porting note: @[simps!] no longer too slow in Lean 4.
+-- porting note (#11083): @[simps!] no longer too slow in Lean 4.
 #align gen_loop.homotopy_from_apply GenLoop.homotopyFrom_apply
 
 theorem homotopicFrom (i : N) {p q : Ω^ N X x} :
refactor(Data/FunLike): use unbundled inheritance from FunLike (#8386)

The FunLike hierarchy is very big and gets scanned through each time we need a coercion (via the CoeFun instance). It looks like unbundled inheritance suits Lean 4 better here. The only class that still extends FunLike is EquivLike, since that has a custom coe_injective' field that is easier to implement. All other classes should take FunLike or EquivLike as a parameter.

Zulip thread

Important changes

Previously, morphism classes would be Type-valued and extend FunLike:

/-- `MyHomClass F A B` states that `F` is a type of `MyClass.op`-preserving morphisms.
You should extend this class when you extend `MyHom`. -/
class MyHomClass (F : Type*) (A B : outParam <| Type*) [MyClass A] [MyClass B]
  extends FunLike F A B :=
(map_op : ∀ (f : F) (x y : A), f (MyClass.op x y) = MyClass.op (f x) (f y))

After this PR, they should be Prop-valued and take FunLike as a parameter:

/-- `MyHomClass F A B` states that `F` is a type of `MyClass.op`-preserving morphisms.
You should extend this class when you extend `MyHom`. -/
class MyHomClass (F : Type*) (A B : outParam <| Type*) [MyClass A] [MyClass B]
  [FunLike F A B] : Prop :=
(map_op : ∀ (f : F) (x y : A), f (MyClass.op x y) = MyClass.op (f x) (f y))

(Note that A B stay marked as outParam even though they are not purely required to be so due to the FunLike parameter already filling them in. This is required to see through type synonyms, which is important in the category theory library. Also, I think keeping them as outParam is slightly faster.)

Similarly, MyEquivClass should take EquivLike as a parameter.

As a result, every mention of [MyHomClass F A B] should become [FunLike F A B] [MyHomClass F A B].

Remaining issues

Slower (failing) search

While overall this gives some great speedups, there are some cases that are noticeably slower. In particular, a failing application of a lemma such as map_mul is more expensive. This is due to suboptimal processing of arguments. For example:

variable [FunLike F M N] [Mul M] [Mul N] (f : F) (x : M) (y : M)

theorem map_mul [MulHomClass F M N] : f (x * y) = f x * f y

example [AddHomClass F A B] : f (x * y) = f x * f y := map_mul f _ _

Before this PR, applying map_mul f gives the goals [Mul ?M] [Mul ?N] [MulHomClass F ?M ?N]. Since M and N are out_params, [MulHomClass F ?M ?N] is synthesized first, supplies values for ?M and ?N and then the Mul M and Mul N instances can be found.

After this PR, the goals become [FunLike F ?M ?N] [Mul ?M] [Mul ?N] [MulHomClass F ?M ?N]. Now [FunLike F ?M ?N] is synthesized first, supplies values for ?M and ?N and then the Mul M and Mul N instances can be found, before trying MulHomClass F M N which fails. Since the Mul hierarchy is very big, this can be slow to fail, especially when there is no such Mul instance.

A long-term but harder to achieve solution would be to specify the order in which instance goals get solved. For example, we'd like to change the arguments to map_mul to look like [FunLike F M N] [Mul M] [Mul N] [highPriority <| MulHomClass F M N] because MulHomClass fails or succeeds much faster than the others.

As a consequence, the simpNF linter is much slower since by design it tries and fails to apply many map_ lemmas. The same issue occurs a few times in existing calls to simp [map_mul], where map_mul is tried "too soon" and fails. Thanks to the speedup of leanprover/lean4#2478 the impact is very limited, only in files that already were close to the timeout.

simp not firing sometimes

This affects map_smulₛₗ and related definitions. For simp lemmas Lean apparently uses a slightly different mechanism to find instances, so that rw can find every argument to map_smulₛₗ successfully but simp can't: leanprover/lean4#3701.

Missing instances due to unification failing

Especially in the category theory library, we might sometimes have a type A which is also accessible as a synonym (Bundled A hA).1. Instance synthesis doesn't always work if we have f : A →* B but x * y : (Bundled A hA).1 or vice versa. This seems to be mostly fixed by keeping A B as outParams in MulHomClass F A B. (Presumably because Lean will do a definitional check A =?= (Bundled A hA).1 instead of using the syntax in the discrimination tree.)

Workaround for issues

The timeouts can be worked around for now by specifying which map_mul we mean, either as map_mul f for some explicit f, or as e.g. MonoidHomClass.map_mul.

map_smulₛₗ not firing as simp lemma can be worked around by going back to the pre-FunLike situation and making LinearMap.map_smulₛₗ a simp lemma instead of the generic map_smulₛₗ. Writing simp [map_smulₛₗ _] also works.

Co-authored-by: Matthew Ballard <matt@mrb.email> Co-authored-by: Scott Morrison <scott.morrison@gmail.com> Co-authored-by: Scott Morrison <scott@tqft.net> Co-authored-by: Anne Baanen <Vierkantor@users.noreply.github.com>

Diff
@@ -258,7 +258,7 @@ def loopHomeo (i : N) : Ω^ N X x ≃ₜ Ω (Ω^ { j // j ≠ i } X x) const
     where
   toFun := toLoop i
   invFun := fromLoop i
-  left_inv p := by ext; exact congr_arg p (Equiv.apply_symm_apply _ _)
+  left_inv p := by ext; exact congr_arg p (by dsimp; exact Equiv.apply_symm_apply _ _)
   right_inv := to_from i
   continuous_toFun := continuous_toLoop i
   continuous_invFun := continuous_fromLoop i
chore: move to v4.6.0-rc1, merging adaptations from bump/v4.6.0 (#10176)

Co-authored-by: Scott Morrison <scott.morrison@gmail.com> Co-authored-by: Eric Wieser <wieser.eric@gmail.com> Co-authored-by: Joachim Breitner <mail@joachim-breitner.de>

Diff
@@ -332,18 +332,16 @@ theorem homotopicFrom (i : N) {p q : Ω^ N X x} :
     obtain rfl | h := eq_or_ne j i
     · rw [H.eq_fst]; exacts [congr_arg p ((Cube.splitAt j).left_inv _), jH]
     · rw [p.2 _ ⟨j, jH⟩]; apply boundary; exact ⟨⟨j, h⟩, jH⟩
-    /- porting note: the following is indented two spaces more than it should be due to
-      strange behavior of `erw` -/
-    all_goals
-      intro
-      apply (homotopyFrom_apply _ _ _).trans
-      first
-      | rw [H.apply_zero]
-      | rw [H.apply_one]
-      first
-      | apply congr_arg p
-      | apply congr_arg q
-      apply (Cube.splitAt i).left_inv
+  all_goals
+    intro
+    apply (homotopyFrom_apply _ _ _).trans
+    first
+    | rw [H.apply_zero]
+    | rw [H.apply_one]
+    first
+    | apply congr_arg p
+    | apply congr_arg q
+    apply (Cube.splitAt i).left_inv
 #align gen_loop.homotopic_from GenLoop.homotopicFrom
 
 /-- Concatenation of two `GenLoop`s along the `i`th coordinate. -/
@@ -517,8 +515,10 @@ def auxGroup (i : N) : Group (HomotopyGroup N X x) :=
 #align homotopy_group.aux_group HomotopyGroup.auxGroup
 
 theorem isUnital_auxGroup (i : N) :
-    EckmannHilton.IsUnital (auxGroup i).mul (⟦const⟧ : HomotopyGroup N X x) :=
-  ⟨⟨(auxGroup i).one_mul⟩, ⟨(auxGroup i).mul_one⟩⟩
+    EckmannHilton.IsUnital (auxGroup i).mul (⟦const⟧ : HomotopyGroup N X x) := {
+    left_id := (auxGroup i).one_mul,
+    right_id := (auxGroup i).mul_one
+  }
 #align homotopy_group.is_unital_aux_group HomotopyGroup.isUnital_auxGroup
 
 theorem auxGroup_indep (i j : N) : (auxGroup i : Group (HomotopyGroup N X x)) = auxGroup j := by
doc: @[inherit_doc] on notations (#9942)

Make all the notations that unambiguously should inherit the docstring of their definition actually inherit it.

Also write a few docstrings by hand. I only wrote the ones I was competent to write and which I was sure of. Some docstrings come from mathlib3 as they were lost during the early port.

This PR is only intended as a first pass There are many more docstrings to add.

Diff
@@ -104,7 +104,7 @@ def GenLoop : Set C(I^N, X) :=
   {p | ∀ y ∈ Cube.boundary N, p y = x}
 #align gen_loop GenLoop
 
-scoped[Topology.Homotopy] notation "Ω^" => GenLoop
+@[inherit_doc] scoped[Topology.Homotopy] notation "Ω^" => GenLoop
 
 open Topology.Homotopy
 
refactor(*): abbreviation for non-dependent FunLike (#9833)

This follows up from #9785, which renamed FunLike to DFunLike, by introducing a new abbreviation FunLike F α β := DFunLike F α (fun _ => β), to make the non-dependent use of FunLike easier.

I searched for the pattern DFunLike.*fun and DFunLike.*λ in all files to replace expressions of the form DFunLike F α (fun _ => β) with FunLike F α β. I did this everywhere except for extends clauses for two reasons: it would conflict with #8386, and more importantly extends must directly refer to a structure with no unfolding of defs or abbrevs.

Diff
@@ -112,10 +112,10 @@ variable {N X x}
 
 namespace GenLoop
 
-instance instDFunLike : DFunLike (Ω^ N X x) (I^N) fun _ => X where
+instance instFunLike : FunLike (Ω^ N X x) (I^N) X where
   coe f := f.1
   coe_injective' := fun ⟨⟨f, _⟩, _⟩ ⟨⟨g, _⟩, _⟩ _ => by congr
-#align gen_loop.fun_like GenLoop.instDFunLike
+#align gen_loop.fun_like GenLoop.instFunLike
 
 @[ext]
 theorem ext (f g : Ω^ N X x) (H : ∀ y, f y = g y) : f = g :=
@@ -133,7 +133,7 @@ def copy (f : Ω^ N X x) (g : (I^N) → X) (h : g = f) : Ω^ N X x :=
   ⟨⟨g, h.symm ▸ f.1.2⟩, by convert f.2⟩
 #align gen_loop.copy GenLoop.copy
 
-/- porting note: this now requires the `instDFunLike` instance,
+/- porting note: this now requires the `instFunLike` instance,
   so the instance is now put before `copy`. -/
 theorem coe_copy (f : Ω^ N X x) {g : (I^N) → X} (h : g = f) : ⇑(copy f g h) = g :=
   rfl
chore(*): rename FunLike to DFunLike (#9785)

This prepares for the introduction of a non-dependent synonym of FunLike, which helps a lot with keeping #8386 readable.

This is entirely search-and-replace in 680197f combined with manual fixes in 4145626, e900597 and b8428f8. The commands that generated this change:

sed -i 's/\bFunLike\b/DFunLike/g' {Archive,Counterexamples,Mathlib,test}/**/*.lean
sed -i 's/\btoFunLike\b/toDFunLike/g' {Archive,Counterexamples,Mathlib,test}/**/*.lean
sed -i 's/import Mathlib.Data.DFunLike/import Mathlib.Data.FunLike/g' {Archive,Counterexamples,Mathlib,test}/**/*.lean
sed -i 's/\bHom_FunLike\b/Hom_DFunLike/g' {Archive,Counterexamples,Mathlib,test}/**/*.lean     
sed -i 's/\binstFunLike\b/instDFunLike/g' {Archive,Counterexamples,Mathlib,test}/**/*.lean
sed -i 's/\bfunLike\b/instDFunLike/g' {Archive,Counterexamples,Mathlib,test}/**/*.lean
sed -i 's/\btoo many metavariables to apply `fun_like.has_coe_to_fun`/too many metavariables to apply `DFunLike.hasCoeToFun`/g' {Archive,Counterexamples,Mathlib,test}/**/*.lean

Co-authored-by: Anne Baanen <Vierkantor@users.noreply.github.com>

Diff
@@ -112,14 +112,14 @@ variable {N X x}
 
 namespace GenLoop
 
-instance funLike : FunLike (Ω^ N X x) (I^N) fun _ => X where
+instance instDFunLike : DFunLike (Ω^ N X x) (I^N) fun _ => X where
   coe f := f.1
   coe_injective' := fun ⟨⟨f, _⟩, _⟩ ⟨⟨g, _⟩, _⟩ _ => by congr
-#align gen_loop.fun_like GenLoop.funLike
+#align gen_loop.fun_like GenLoop.instDFunLike
 
 @[ext]
 theorem ext (f g : Ω^ N X x) (H : ∀ y, f y = g y) : f = g :=
-  FunLike.coe_injective' (funext H)
+  DFunLike.coe_injective' (funext H)
 #align gen_loop.ext GenLoop.ext
 
 @[simp]
@@ -133,7 +133,7 @@ def copy (f : Ω^ N X x) (g : (I^N) → X) (h : g = f) : Ω^ N X x :=
   ⟨⟨g, h.symm ▸ f.1.2⟩, by convert f.2⟩
 #align gen_loop.copy GenLoop.copy
 
-/- porting note: this now requires the `funLike` instance,
+/- porting note: this now requires the `instDFunLike` instance,
   so the instance is now put before `copy`. -/
 theorem coe_copy (f : Ω^ N X x) {g : (I^N) → X} (h : g = f) : ⇑(copy f g h) = g :=
   rfl
feat(CompactOpen): unify 2 continuous_eval lemmas (#9264)

Introduce a typeclass LocallyCompactPair that allows us to unify different versions of ContinuousMap.continuous_eval and similar lemmas.

Diff
@@ -214,7 +214,7 @@ def toLoop (i : N) (p : Ω^ N X x) : Ω (Ω^ { j // j ≠ i } X x) const
 theorem continuous_toLoop (i : N) : Continuous (@toLoop N X _ x _ i) :=
   Path.continuous_uncurry_iff.1 <|
     Continuous.subtype_mk
-      (ContinuousMap.continuous_eval'.comp <|
+      (ContinuousMap.continuous_eval.comp <|
         Continuous.prod_map
           (ContinuousMap.continuous_curry.comp <|
             (ContinuousMap.continuous_comp_left _).comp continuous_subtype_val)
refactor: remove redundant condition in HomotopyRel (#7848)

For a homotopy F between f₀ and f₁ to be a homotopy relative to a set S, it suffices that F (t, x) = f₀ x for all x ∈ S and t : I, from which F (t, x) = f₁ x can be derived.

Also add HomotopyRel.compContinuousMap.

Diff
@@ -307,9 +307,9 @@ theorem homotopicTo (i : N) {p q : Ω^ N X x} :
   · apply H.apply_zero
   · apply H.apply_one
   intro t y yH
-  constructor <;> ext <;> erw [homotopyTo_apply]
-  apply H.eq_fst; on_goal 2 => apply H.eq_snd
-  all_goals use i; rw [funSplitAt_symm_apply, dif_pos rfl]; exact yH
+  ext; erw [homotopyTo_apply]
+  apply H.eq_fst; use i
+  rw [funSplitAt_symm_apply, dif_pos rfl]; exact yH
 #align gen_loop.homotopic_to GenLoop.homotopicTo
 
 /-- The converse to `GenLoop.homotopyTo`: a homotopy between two loops in the space of
@@ -330,10 +330,8 @@ theorem homotopicFrom (i : N) {p q : Ω^ N X x} :
   · rintro t y ⟨j, jH⟩
     erw [homotopyFrom_apply]
     obtain rfl | h := eq_or_ne j i
-    · constructor
-      · rw [H.eq_fst]; exacts [congr_arg p ((Cube.splitAt j).left_inv _), jH]
-      · rw [H.eq_snd]; exacts [congr_arg q ((Cube.splitAt j).left_inv _), jH]
-    · rw [p.2 _ ⟨j, jH⟩, q.2 _ ⟨j, jH⟩]; constructor <;> · apply boundary; exact ⟨⟨j, h⟩, jH⟩
+    · rw [H.eq_fst]; exacts [congr_arg p ((Cube.splitAt j).left_inv _), jH]
+    · rw [p.2 _ ⟨j, jH⟩]; apply boundary; exact ⟨⟨j, h⟩, jH⟩
     /- porting note: the following is indented two spaces more than it should be due to
       strange behavior of `erw` -/
     all_goals
@@ -494,9 +492,8 @@ def homotopyGroupEquivFundamentalGroupOfUnique (N) [Unique N] :
   · exact (H.apply_zero _).trans (congr_arg a₁ (eq_const_of_unique y).symm)
   · exact (H.apply_one _).trans (congr_arg a₂ (eq_const_of_unique y).symm)
   · rintro t y ⟨i, iH⟩
-    cases Unique.eq_default i; constructor
-    · exact (H.eq_fst _ iH).trans (congr_arg a₁ (eq_const_of_unique y).symm)
-    · exact (H.eq_snd _ iH).trans (congr_arg a₂ (eq_const_of_unique y).symm)
+    cases Unique.eq_default i
+    exact (H.eq_fst _ iH).trans (congr_arg a₁ (eq_const_of_unique y).symm)
 #align homotopy_group_equiv_fundamental_group_of_unique homotopyGroupEquivFundamentalGroupOfUnique
 
 /-- The first homotopy group at `x` is in bijection with the fundamental group. -/
chore: only four spaces for subsequent lines (#7286)

Co-authored-by: Moritz Firsching <firsching@google.com>

Diff
@@ -555,7 +555,7 @@ theorem one_def [Nonempty N] : (1 : HomotopyGroup N X x) = ⟦const⟧ :=
 
 /-- Characterization of multiplication -/
 theorem mul_spec [Nonempty N] {i} {p q : Ω^ N X x} :
-  -- porting note: TODO: introduce `HomotopyGroup.mk` and remove defeq abuse.
+    -- porting note: TODO: introduce `HomotopyGroup.mk` and remove defeq abuse.
     ((· * ·) : _ → _ → HomotopyGroup N X x) ⟦p⟧ ⟦q⟧ = ⟦transAt i q p⟧ := by
   rw [transAt_indep _ q, ← fromLoop_trans_toLoop]; apply Quotient.sound; rfl
 #align homotopy_group.mul_spec HomotopyGroup.mul_spec
chore: banish Type _ and Sort _ (#6499)

We remove all possible occurences of Type _ and Sort _ in favor of Type* and Sort*.

This has nice performance benefits.

Diff
@@ -53,11 +53,11 @@ scoped[Topology] notation "I^" N => N → I
 namespace Cube
 
 /-- The points in a cube with at least one projection equal to 0 or 1. -/
-def boundary (N : Type _) : Set (I^N) :=
+def boundary (N : Type*) : Set (I^N) :=
   {y | ∃ i, y i = 0 ∨ y i = 1}
 #align cube.boundary Cube.boundary
 
-variable {N : Type _} [DecidableEq N]
+variable {N : Type*} [DecidableEq N]
 
 /-- The forward direction of the homeomorphism
   between the cube $I^N$ and $I × I^{N\setminus\{j\}}$. -/
@@ -82,7 +82,7 @@ theorem insertAt_boundary (i : N) {t₀ : I} {t}
 
 end Cube
 
-variable (N X : Type _) [TopologicalSpace X] (x : X)
+variable (N X : Type*) [TopologicalSpace X] (x : X)
 
 /-- The space of paths with both endpoints equal to a specified point `x : X`. -/
 @[reducible]
@@ -392,7 +392,7 @@ end GenLoop
 
 /-- The `n`th homotopy group at `x` defined as the quotient of `Ω^n x` by the
   `GenLoop.Homotopic` relation. -/
-def HomotopyGroup (N X : Type _) [TopologicalSpace X] (x : X) : Type _ :=
+def HomotopyGroup (N X : Type*) [TopologicalSpace X] (x : X) : Type _ :=
   Quotient (GenLoop.Homotopic.setoid N x)
 #align homotopy_group HomotopyGroup
 
@@ -415,7 +415,7 @@ def homotopyGroupEquivFundamentalGroup (i : N) :
 
 /-- Homotopy group of finite index. -/
 @[reducible]
-def HomotopyGroup.Pi (n) (X : Type _) [TopologicalSpace X] (x : X) :=
+def HomotopyGroup.Pi (n) (X : Type*) [TopologicalSpace X] (x : X) :=
   HomotopyGroup (Fin n) _ x
 #align homotopy_group.pi HomotopyGroup.Pi
 
chore: script to replace headers with #align_import statements (#5979)

Open in Gitpod

Co-authored-by: Eric Wieser <wieser.eric@gmail.com> Co-authored-by: Scott Morrison <scott.morrison@gmail.com>

Diff
@@ -2,17 +2,14 @@
 Copyright (c) 2021 Roberto Alvarez. All rights reserved.
 Released under Apache 2.0 license as described in the file LICENSE.
 Authors: Roberto Alvarez
-
-! This file was ported from Lean 3 source module topology.homotopy.homotopy_group
-! leanprover-community/mathlib commit 4c3e1721c58ef9087bbc2c8c38b540f70eda2e53
-! Please do not edit these lines, except to modify the commit id
-! if you have ported upstream changes.
 -/
 import Mathlib.AlgebraicTopology.FundamentalGroupoid.FundamentalGroup
 import Mathlib.GroupTheory.EckmannHilton
 import Mathlib.Logic.Equiv.TransferInstance
 import Mathlib.Algebra.Group.Ext
 
+#align_import topology.homotopy.homotopy_group from "leanprover-community/mathlib"@"4c3e1721c58ef9087bbc2c8c38b540f70eda2e53"
+
 /-!
 # `n`th homotopy group
 
chore: rescope Ω notation for LoopSpace (#5714)
Diff
@@ -94,7 +94,7 @@ def LoopSpace :=
 #align loop_space LoopSpace
 
 -- mathport name: exprΩ
-scoped[Topology] notation "Ω" => LoopSpace
+scoped[Topology.Homotopy] notation "Ω" => LoopSpace
 
 instance LoopSpace.inhabited : Inhabited (Path x x) :=
   ⟨Path.refl x⟩
@@ -107,7 +107,9 @@ def GenLoop : Set C(I^N, X) :=
   {p | ∀ y ∈ Cube.boundary N, p y = x}
 #align gen_loop GenLoop
 
-scoped[Topology] notation "Ω^" => GenLoop
+scoped[Topology.Homotopy] notation "Ω^" => GenLoop
+
+open Topology.Homotopy
 
 variable {N X x}
 
feat: generalize&merge ContinuousMap.continuous_eval_const{,'} (#5649)

We had continuity of fun f : C(X, Y) ↦ f a in two cases:

  • X is a locally compact space;
  • X is a compact space and Y is a metric space.

In fact, it is true in general topological spaces.

Diff
@@ -429,7 +429,7 @@ def genLoopHomeoOfIsEmpty (N x) [IsEmpty N] : Ω^ N X x ≃ₜ X where
   invFun y := ⟨ContinuousMap.const _ y, fun _ ⟨i, _⟩ => isEmptyElim i⟩
   left_inv f := by ext; exact congr_arg f (Subsingleton.elim _ _)
   right_inv _ := rfl
-  continuous_toFun := (ContinuousMap.continuous_eval_const' (0 : N → I)).comp continuous_induced_dom
+  continuous_toFun := (ContinuousMap.continuous_eval_const (0 : N → I)).comp continuous_induced_dom
   continuous_invFun := ContinuousMap.const'.2.subtype_mk _
 #align gen_loop_homeo_of_is_empty genLoopHomeoOfIsEmpty
 
chore: tidy various files (#5104)
Diff
@@ -51,7 +51,6 @@ open Homeomorph
 
 noncomputable section
 
--- mathport name: «exprI^ »
 scoped[Topology] notation "I^" N => N → I
 
 namespace Cube
@@ -108,15 +107,13 @@ def GenLoop : Set C(I^N, X) :=
   {p | ∀ y ∈ Cube.boundary N, p y = x}
 #align gen_loop GenLoop
 
--- mathport name: «exprΩ^»
 scoped[Topology] notation "Ω^" => GenLoop
 
 variable {N X x}
 
 namespace GenLoop
 
-instance funLike : FunLike (Ω^ N X x) (I^N) fun _ => X
-    where
+instance funLike : FunLike (Ω^ N X x) (I^N) fun _ => X where
   coe f := f.1
   coe_injective' := fun ⟨⟨f, _⟩, _⟩ ⟨⟨g, _⟩, _⟩ _ => by congr
 #align gen_loop.fun_like GenLoop.funLike
@@ -256,7 +253,7 @@ theorem to_from (i : N) (p : Ω (Ω^ { j // j ≠ i } X x) const) : toLoop i (fr
 
 /-- The `n+1`-dimensional loops are in bijection with the loops in the space of
   `n`-dimensional loops with base point `const`.
-  We allow an arbitrary indexing type `N` in place of `fin n` here. -/
+  We allow an arbitrary indexing type `N` in place of `Fin n` here. -/
 @[simps]
 def loopHomeo (i : N) : Ω^ N X x ≃ₜ Ω (Ω^ { j // j ≠ i } X x) const
     where
@@ -278,7 +275,7 @@ theorem fromLoop_apply (i : N) {p : Ω (Ω^ { j // j ≠ i } X x) const} {t : I^
   rfl
 #align gen_loop.from_loop_apply GenLoop.fromLoop_apply
 
-/-- Composition with `cube.insert_at` as a continuous map. -/
+/-- Composition with `Cube.insertAt` as a continuous map. -/
 @[reducible]
 def cCompInsert (i : N) : C(C(I^N, X), C(I × I^{ j // j ≠ i }, X)) :=
   ⟨fun f => f.comp (Cube.insertAt i).toContinuousMap,
@@ -427,8 +424,7 @@ def HomotopyGroup.Pi (n) (X : Type _) [TopologicalSpace X] (x : X) :=
 scoped[Topology] notation "π_" => HomotopyGroup.Pi
 
 /-- The 0-dimensional generalized loops based at `x` are in bijection with `X`. -/
-def genLoopHomeoOfIsEmpty (N x) [IsEmpty N] : Ω^ N X x ≃ₜ X
-    where
+def genLoopHomeoOfIsEmpty (N x) [IsEmpty N] : Ω^ N X x ≃ₜ X where
   toFun f := f 0
   invFun y := ⟨ContinuousMap.const _ y, fun _ ⟨i, _⟩ => isEmptyElim i⟩
   left_inv f := by ext; exact congr_arg f (Subsingleton.elim _ _)
@@ -438,7 +434,7 @@ def genLoopHomeoOfIsEmpty (N x) [IsEmpty N] : Ω^ N X x ≃ₜ X
 #align gen_loop_homeo_of_is_empty genLoopHomeoOfIsEmpty
 
 /-- The homotopy "group" indexed by an empty type is in bijection with
-  the path components of `X`, aka the `zeroth_homotopy`. -/
+  the path components of `X`, aka the `ZerothHomotopy`. -/
 def homotopyGroupEquivZerothHomotopyOfIsEmpty (N x) [IsEmpty N] :
     HomotopyGroup N X x ≃ ZerothHomotopy X :=
   Quotient.congr (genLoopHomeoOfIsEmpty N x).toEquiv
@@ -456,14 +452,13 @@ def homotopyGroupEquivZerothHomotopyOfIsEmpty (N x) [IsEmpty N] :
             prop' := fun _ _ ⟨i, _⟩ => isEmptyElim i }⟩])
 #align homotopy_group_equiv_zeroth_homotopy_of_is_empty homotopyGroupEquivZerothHomotopyOfIsEmpty
 
-/-- The 0th homotopy "group" is in bijection with `zeroth_homotopy`. -/
+/-- The 0th homotopy "group" is in bijection with `ZerothHomotopy`. -/
 def HomotopyGroup.pi0EquivZerothHomotopy : π_ 0 X x ≃ ZerothHomotopy X :=
   homotopyGroupEquivZerothHomotopyOfIsEmpty (Fin 0) x
 #align homotopy_group.pi_0_equiv_zeroth_homotopy HomotopyGroup.pi0EquivZerothHomotopy
 
 /-- The 1-dimensional generalized loops based at `x` are in bijection with loops at `x`. -/
-def genLoopEquivOfUnique (N) [Unique N] : Ω^ N X x ≃ Ω X x
-    where
+def genLoopEquivOfUnique (N) [Unique N] : Ω^ N X x ≃ Ω X x where
   toFun p :=
     Path.mk ⟨fun t => p fun _ => t, by continuity⟩
       (GenLoop.boundary _ (fun _ => 0) ⟨default, Or.inl rfl⟩)
chore: forward-port leanprover-community/mathlib#15681 (complete) (#4939)

Removed diagonal stuff which is no longer needed.

Co-authored-by: Junyan Xu <junyanxu.math@gmail.com>

Diff
@@ -4,283 +4,583 @@ Released under Apache 2.0 license as described in the file LICENSE.
 Authors: Roberto Alvarez
 
 ! This file was ported from Lean 3 source module topology.homotopy.homotopy_group
-! leanprover-community/mathlib commit f2ce6086713c78a7f880485f7917ea547a215982
+! leanprover-community/mathlib commit 4c3e1721c58ef9087bbc2c8c38b540f70eda2e53
 ! Please do not edit these lines, except to modify the commit id
 ! if you have ported upstream changes.
 -/
 import Mathlib.AlgebraicTopology.FundamentalGroupoid.FundamentalGroup
+import Mathlib.GroupTheory.EckmannHilton
+import Mathlib.Logic.Equiv.TransferInstance
+import Mathlib.Algebra.Group.Ext
 
 /-!
 # `n`th homotopy group
 
-We define the `n`th homotopy group at `x`, `π n x`, as the equivalence classes
-of functions from the nth dimensional cube to the topological space `X`
+We define the `n`th homotopy group at `x : X`, `π_n X x`, as the equivalence classes
+of functions from the `n`-dimensional cube to the topological space `X`
 that send the boundary to the base point `x`, up to homotopic equivalence.
-Note that such functions are generalized loops `GenLoop n x`, in particular
-`GenLoop 1 x ≃ Path x x`
+Note that such functions are generalized loops `GenLoop (Fin n) x`; in particular
+`GenLoop (Fin 1) x ≃ Path x x`.
 
-We show that `π 0 x` is equivalent to the path-connected components, and
-that `π 1 x` is equivalent to the fundamental group at `x`.
+We show that `π_0 X x` is equivalent to the path-connected components, and
+that `π_1 X x` is equivalent to the fundamental group at `x`.
+We provide a group instance using path composition and show commutativity when `n > 1`.
 
 ## definitions
 
-* `GenLoop n x` is the type of continuous functions `I^n → X` that send the boundary to `x`
-* `HomotopyGroup n x` denoted `π n x` is the quotient of `GenLoop n x` by homotopy relative
-  to the boundary
+* `GenLoop N x` is the type of continuous functions `I^N → X` that send the boundary to `x`,
+* `HomotopyGroup.Pi n X x` denoted `π_ n X x` is the quotient of `GenLoop (Fin n) x` by
+  homotopy relative to the boundary,
+* group instance `Group (π_(n+1) X x)`,
+* commutative group instance `CommGroup (π_(n+2) X x)`.
 
-TODO: show that `π n x` is a group when `n > 0` and abelian when `n > 1`. Show that
-`pi1EquivFundamentalGroup` is an isomorphism of groups.
+TODO:
+* `Ω^M (Ω^N X) ≃ₜ Ω^(M⊕N) X`, and `Ω^M X ≃ₜ Ω^N X` when `M ≃ N`. Similarly for `π_`.
+* Path-induced homomorphisms. Show that `HomotopyGroup.pi1EquivFundamentalGroup`
+  is a group isomorphism.
+* Examples with `𝕊^n`: `π_n (𝕊^n) = ℤ`, `π_m (𝕊^n)` trivial for `m < n`.
+* Actions of π_1 on π_n.
+* Lie algebra: `⁅π_(n+1), π_(m+1)⁆` contained in `π_(n+m+1)`.
 
 -/
 
 
 open scoped unitInterval Topology
 
-noncomputable section
-
-universe u
-
-variable {X : Type u} [TopologicalSpace X]
-
-variable {n : ℕ} {x : X}
-
-/-- The `n`-dimensional cube.
--/
-def Cube (n : ℕ) : Type :=
-  Fin n → I
-#align cube Cube
+open Homeomorph
 
-local notation "I^" => Cube
+noncomputable section
 
--- porting note: the next 3 instances were derived in Lean 3
-instance : Zero (I^ n) := inferInstanceAs (Zero (Fin n → I))
-instance : One (I^ n) := inferInstanceAs (One (Fin n → I))
-instance : TopologicalSpace (I^ n) := inferInstanceAs (TopologicalSpace (Fin n → I))
+-- mathport name: «exprI^ »
+scoped[Topology] notation "I^" N => N → I
 
 namespace Cube
 
-@[continuity]
-theorem proj_continuous (i : Fin n) : Continuous fun f : I^ n => f i :=
-  continuous_apply i
-#align cube.proj_continuous Cube.proj_continuous
-
-/-- The points of the `n`-dimensional cube with at least one projection equal to 0 or 1.
--/
-def boundary (n : ℕ) : Set (I^ n) :=
-  { y | ∃ i : Fin n, y i = 0 ∨ y i = 1 }
+/-- The points in a cube with at least one projection equal to 0 or 1. -/
+def boundary (N : Type _) : Set (I^N) :=
+  {y | ∃ i, y i = 0 ∨ y i = 1}
 #align cube.boundary Cube.boundary
 
-theorem mem_boundary {y : I^ n} : y ∈ boundary n ↔ ∃ i, y i = 0 ∨ y i = 1 := Iff.rfl
+variable {N : Type _} [DecidableEq N]
+
+/-- The forward direction of the homeomorphism
+  between the cube $I^N$ and $I × I^{N\setminus\{j\}}$. -/
+@[reducible]
+def splitAt (i : N) : (I^N) ≃ₜ I × I^{ j // j ≠ i } :=
+  funSplitAt I i
+#align cube.split_at Cube.splitAt
+
+/-- The backward direction of the homeomorphism
+  between the cube $I^N$ and $I × I^{N\setminus\{j\}}$. -/
+@[reducible]
+def insertAt (i : N) : (I × I^{ j // j ≠ i }) ≃ₜ I^N :=
+  (funSplitAt I i).symm
+#align cube.insert_at Cube.insertAt
+
+theorem insertAt_boundary (i : N) {t₀ : I} {t}
+    (H : (t₀ = 0 ∨ t₀ = 1) ∨ t ∈ boundary { j // j ≠ i }) : insertAt i ⟨t₀, t⟩ ∈ boundary N := by
+  obtain H | ⟨j, H⟩ := H
+  · use i; rwa [funSplitAt_symm_apply, dif_pos rfl]
+  · use j; rwa [funSplitAt_symm_apply, dif_neg j.prop, Subtype.coe_eta]
+#align cube.insert_at_boundary Cube.insertAt_boundary
 
-theorem zero_mem_boundary [NeZero n] : 0 ∈ boundary n := ⟨0, .inl rfl⟩
-theorem one_mem_boundary [NeZero n] : 1 ∈ boundary n := ⟨0, .inr rfl⟩
-
-/-- The first projection of a positive-dimensional cube.
--/
-@[simp]
-def head {n} : I^ (n + 1) → I := fun c => c 0
-#align cube.head Cube.head
-
-@[continuity]
-theorem head.continuous {n} : Continuous (@head n) :=
-  proj_continuous 0
-#align cube.head.continuous Cube.head.continuous
-
-/-- The projection to the last `n` coordinates from an `n+1` dimensional cube.
--/
-@[simp]
-def tail {n} : I^ (n + 1) → I^ n := fun c => Fin.tail c
-#align cube.tail Cube.tail
+end Cube
 
-instance uniqueCube0 : Unique (I^ 0) :=
-  Pi.uniqueOfIsEmpty _
-#align cube.unique_cube0 Cube.uniqueCube0
+variable (N X : Type _) [TopologicalSpace X] (x : X)
 
-theorem one_char (f : I^ 1) : f = fun _ => f 0 := eq_const_of_unique f
-#align cube.one_char Cube.one_char
+/-- The space of paths with both endpoints equal to a specified point `x : X`. -/
+@[reducible]
+def LoopSpace :=
+  Path x x
+#align loop_space LoopSpace
 
-open Set
+-- mathport name: exprΩ
+scoped[Topology] notation "Ω" => LoopSpace
 
-@[simp] -- porting note: new lemma
-theorem boundary_zero : boundary 0 = ∅ :=
-  eq_empty_of_forall_not_mem <| fun _ ⟨i, _⟩ ↦ i.elim0
+instance LoopSpace.inhabited : Inhabited (Path x x) :=
+  ⟨Path.refl x⟩
+#align loop_space.inhabited LoopSpace.inhabited
 
-@[simp] -- porting note: new lemma
-theorem boundary_one : boundary 1 = {0, 1} := by
-  ext x
-  rw [mem_boundary, Fin.exists_fin_one, mem_insert_iff, mem_singleton_iff,
-    Function.funext_iff, Function.funext_iff, Fin.forall_fin_one, Fin.forall_fin_one]
-  rfl
+/-- The `n`-dimensional generalized loops based at `x` in a space `X` are
+  continuous functions `I^n → X` that sends the boundary to `x`.
+  We allow an arbitrary indexing type `N` in place of `Fin n` here. -/
+def GenLoop : Set C(I^N, X) :=
+  {p | ∀ y ∈ Cube.boundary N, p y = x}
+#align gen_loop GenLoop
 
-end Cube
+-- mathport name: «exprΩ^»
+scoped[Topology] notation "Ω^" => GenLoop
 
-/--
-The `n`-dimensional generalized loops; functions `I^n → X` that send the boundary to the base point.
--/
-structure GenLoop (n : ℕ) (x : X) extends C(I^ n, X) where
-  boundary : ∀ y ∈ Cube.boundary n, toFun y = x
-#align gen_loop GenLoop
+variable {N X x}
 
 namespace GenLoop
 
-instance funLike : FunLike (GenLoop n x) (I^ n) fun _ => X where
+instance funLike : FunLike (Ω^ N X x) (I^N) fun _ => X
+    where
   coe f := f.1
   coe_injective' := fun ⟨⟨f, _⟩, _⟩ ⟨⟨g, _⟩, _⟩ _ => by congr
 #align gen_loop.fun_like GenLoop.funLike
 
 @[ext]
-theorem ext (f g : GenLoop n x) (H : ∀ y, f y = g y) : f = g :=
-  FunLike.ext f g H
+theorem ext (f g : Ω^ N X x) (H : ∀ y, f y = g y) : f = g :=
+  FunLike.coe_injective' (funext H)
 #align gen_loop.ext GenLoop.ext
 
 @[simp]
-theorem mk_apply (f : C(I^ n, X)) (H y) : (⟨f, H⟩ : GenLoop n x) y = f y :=
+theorem mk_apply (f : C(I^N, X)) (H y) : (⟨f, H⟩ : Ω^ N X x) y = f y :=
   rfl
 #align gen_loop.mk_apply GenLoop.mk_apply
 
-@[simp] -- porting note: new lemma
-theorem toContinuousMap_apply (f : GenLoop n x) (y) : f.toContinuousMap y = f y := rfl
+/-- Copy of a `GenLoop` with a new map from the unit cube equal to the old one.
+  Useful to fix definitional equalities. -/
+def copy (f : Ω^ N X x) (g : (I^N) → X) (h : g = f) : Ω^ N X x :=
+  ⟨⟨g, h.symm ▸ f.1.2⟩, by convert f.2⟩
+#align gen_loop.copy GenLoop.copy
 
-initialize_simps_projections GenLoop (toFun → apply)
+/- porting note: this now requires the `funLike` instance,
+  so the instance is now put before `copy`. -/
+theorem coe_copy (f : Ω^ N X x) {g : (I^N) → X} (h : g = f) : ⇑(copy f g h) = g :=
+  rfl
+#align gen_loop.coe_copy GenLoop.coe_copy
 
-/-- The constant `GenLoop` at `x`.
--/
-@[simps!] -- porting note: new attr
-def const : GenLoop n x :=
+theorem copy_eq (f : Ω^ N X x) {g : (I^N) → X} (h : g = f) : copy f g h = f := by
+  ext x
+  exact congr_fun h x
+#align gen_loop.copy_eq GenLoop.copy_eq
+
+theorem boundary (f : Ω^ N X x) : ∀ y ∈ Cube.boundary N, f y = x :=
+  f.2
+#align gen_loop.boundary GenLoop.boundary
+
+/-- The constant `GenLoop` at `x`. -/
+def const : Ω^ N X x :=
   ⟨ContinuousMap.const _ x, fun _ _ => rfl⟩
 #align gen_loop.const GenLoop.const
 
-instance inhabited : Inhabited (GenLoop n x) where default := const
-#align gen_loop.inhabited GenLoop.inhabited
+@[simp]
+theorem const_apply {t} : (@const N X _ x) t = x :=
+  rfl
+#align gen_loop.const_apply GenLoop.const_apply
 
-/-- Restrict a `GenLoop n x` with `n ≠ 0` to the diagonal of the cube. -/
-@[simps!]
-def diagonal [NeZero n] (f : GenLoop n x) : Path x x where
-  toContinuousMap := f.comp <| .constPi _
-  source' := f.boundary _ Cube.zero_mem_boundary
-  target' := f.boundary _ Cube.one_mem_boundary
+instance inhabited : Inhabited (Ω^ N X x) :=
+  ⟨const⟩
 
-/-- The "homotopy relative to boundary" relation between `GenLoop`s.
--/
-def Homotopic (f g : GenLoop n x) : Prop :=
-  f.toContinuousMap.HomotopicRel g.toContinuousMap (Cube.boundary n)
+/-- The "homotopic relative to boundary" relation between `GenLoop`s. -/
+def Homotopic (f g : Ω^ N X x) : Prop :=
+  f.1.HomotopicRel g.1 (Cube.boundary N)
 #align gen_loop.homotopic GenLoop.Homotopic
 
 namespace Homotopic
 
-variable {f g h : GenLoop n x}
+variable {f g h : Ω^ N X x}
 
 @[refl]
-theorem refl (f : GenLoop n x) : Homotopic f f :=
+theorem refl (f : Ω^ N X x) : Homotopic f f :=
   ContinuousMap.HomotopicRel.refl _
 #align gen_loop.homotopic.refl GenLoop.Homotopic.refl
 
 @[symm]
-nonrec theorem symm (H : f.Homotopic g) : g.Homotopic f :=
+nonrec theorem symm (H : Homotopic f g) : Homotopic g f :=
   H.symm
 #align gen_loop.homotopic.symm GenLoop.Homotopic.symm
 
 @[trans]
-nonrec theorem trans (H0 : f.Homotopic g) (H1 : g.Homotopic h) : f.Homotopic h :=
+nonrec theorem trans (H0 : Homotopic f g) (H1 : Homotopic g h) : Homotopic f h :=
   H0.trans H1
 #align gen_loop.homotopic.trans GenLoop.Homotopic.trans
 
-theorem equiv : Equivalence (@Homotopic X _ n x) :=
+theorem equiv : Equivalence (@Homotopic N X _ x) :=
   ⟨Homotopic.refl, Homotopic.symm, Homotopic.trans⟩
 #align gen_loop.homotopic.equiv GenLoop.Homotopic.equiv
 
-instance setoid (n : ℕ) (x : X) : Setoid (GenLoop n x) :=
+instance setoid (N) (x : X) : Setoid (Ω^ N X x) :=
   ⟨Homotopic, equiv⟩
 #align gen_loop.homotopic.setoid GenLoop.Homotopic.setoid
 
-protected theorem diagonal [NeZero n] (H : f.Homotopic g) :
-    f.diagonal.Homotopic g.diagonal := by
-  rcases H with ⟨H⟩
-  refine ⟨H.compContinuousMap _, ?_⟩
-  rintro t _ (rfl | rfl)
-  · exact H.prop' _ _ Cube.zero_mem_boundary
-  · exact H.prop' _ _ Cube.one_mem_boundary
-
 end Homotopic
 
+section LoopHomeo
+
+variable [DecidableEq N]
+
+/-- Loop from a generalized loop by currying $I^N → X$ into $I → (I^{N\setminus\{j\}} → X)$. -/
+@[simps]
+def toLoop (i : N) (p : Ω^ N X x) : Ω (Ω^ { j // j ≠ i } X x) const
+    where
+  toFun t :=
+    ⟨(p.val.comp (Cube.insertAt i).toContinuousMap).curry t, fun y yH =>
+      p.property (Cube.insertAt i (t, y)) (Cube.insertAt_boundary i <| Or.inr yH)⟩
+  source' := by ext t; refine' p.property (Cube.insertAt i (0, t)) ⟨i, Or.inl _⟩; simp
+  target' := by ext t; refine' p.property (Cube.insertAt i (1, t)) ⟨i, Or.inr _⟩; simp
+#align gen_loop.to_loop GenLoop.toLoop
+
+
+theorem continuous_toLoop (i : N) : Continuous (@toLoop N X _ x _ i) :=
+  Path.continuous_uncurry_iff.1 <|
+    Continuous.subtype_mk
+      (ContinuousMap.continuous_eval'.comp <|
+        Continuous.prod_map
+          (ContinuousMap.continuous_curry.comp <|
+            (ContinuousMap.continuous_comp_left _).comp continuous_subtype_val)
+          continuous_id)
+      _
+#align gen_loop.continuous_to_loop GenLoop.continuous_toLoop
+
+/-- Generalized loop from a loop by uncurrying $I → (I^{N\setminus\{j\}} → X)$ into $I^N → X$. -/
+@[simps]
+def fromLoop (i : N) (p : Ω (Ω^ { j // j ≠ i } X x) const) : Ω^ N X x :=
+  ⟨(ContinuousMap.comp ⟨Subtype.val, by continuity⟩ p.toContinuousMap).uncurry.comp
+    (Cube.splitAt i).toContinuousMap,
+    by
+    rintro y ⟨j, Hj⟩
+    simp only [ContinuousMap.comp_apply, toContinuousMap_apply,
+      funSplitAt_apply, ContinuousMap.uncurry_apply, ContinuousMap.coe_mk,
+      Function.uncurry_apply_pair]
+    obtain rfl | Hne := eq_or_ne j i
+    · cases' Hj with Hj Hj <;> simp only [Hj, p.coe_toContinuousMap, p.source, p.target] <;> rfl
+    · exact GenLoop.boundary _ _ ⟨⟨j, Hne⟩, Hj⟩⟩
+#align gen_loop.from_loop GenLoop.fromLoop
+
+theorem continuous_fromLoop (i : N) : Continuous (@fromLoop N X _ x _ i) :=
+  ((ContinuousMap.continuous_comp_left _).comp <|
+        ContinuousMap.continuous_uncurry.comp <|
+          (ContinuousMap.continuous_comp _).comp continuous_induced_dom).subtype_mk
+    _
+#align gen_loop.continuous_from_loop GenLoop.continuous_fromLoop
+
+theorem to_from (i : N) (p : Ω (Ω^ { j // j ≠ i } X x) const) : toLoop i (fromLoop i p) = p := by
+  simp_rw [toLoop, fromLoop, ContinuousMap.comp_assoc,
+    toContinuousMap_comp_symm, ContinuousMap.comp_id]
+  ext; rfl
+#align gen_loop.to_from GenLoop.to_from
+
+/-- The `n+1`-dimensional loops are in bijection with the loops in the space of
+  `n`-dimensional loops with base point `const`.
+  We allow an arbitrary indexing type `N` in place of `fin n` here. -/
+@[simps]
+def loopHomeo (i : N) : Ω^ N X x ≃ₜ Ω (Ω^ { j // j ≠ i } X x) const
+    where
+  toFun := toLoop i
+  invFun := fromLoop i
+  left_inv p := by ext; exact congr_arg p (Equiv.apply_symm_apply _ _)
+  right_inv := to_from i
+  continuous_toFun := continuous_toLoop i
+  continuous_invFun := continuous_fromLoop i
+#align gen_loop.loop_homeo GenLoop.loopHomeo
+
+theorem toLoop_apply (i : N) {p : Ω^ N X x} {t} {tn} :
+    toLoop i p t tn = p (Cube.insertAt i ⟨t, tn⟩) :=
+  rfl
+#align gen_loop.to_loop_apply GenLoop.toLoop_apply
+
+theorem fromLoop_apply (i : N) {p : Ω (Ω^ { j // j ≠ i } X x) const} {t : I^N} :
+    fromLoop i p t = p (t i) (Cube.splitAt i t).snd :=
+  rfl
+#align gen_loop.from_loop_apply GenLoop.fromLoop_apply
+
+/-- Composition with `cube.insert_at` as a continuous map. -/
+@[reducible]
+def cCompInsert (i : N) : C(C(I^N, X), C(I × I^{ j // j ≠ i }, X)) :=
+  ⟨fun f => f.comp (Cube.insertAt i).toContinuousMap,
+    (Cube.insertAt i).toContinuousMap.continuous_comp_left⟩
+#align gen_loop.c_comp_insert GenLoop.cCompInsert
+
+/-- A homotopy between `n+1`-dimensional loops `p` and `q` constant on the boundary
+  seen as a homotopy between two paths in the space of `n`-dimensional paths. -/
+def homotopyTo (i : N) {p q : Ω^ N X x} (H : p.1.HomotopyRel q.1 (Cube.boundary N)) :
+    C(I × I, C(I^{ j // j ≠ i }, X)) :=
+  ((⟨_, ContinuousMap.continuous_curry⟩ : C(_, _)).comp <|
+      (cCompInsert i).comp H.toContinuousMap.curry).uncurry
+#align gen_loop.homotopy_to GenLoop.homotopyTo
+
+-- porting note: `@[simps]` no longer too slow in Lean 4 but does not generate this lemma.
+theorem homotopyTo_apply (i : N) {p q : Ω^ N X x} (H : p.1.HomotopyRel q.1 <| Cube.boundary N)
+    (t : I × I) (tₙ : I^{ j // j ≠ i }) :
+    homotopyTo i H t tₙ = H (t.fst, Cube.insertAt i (t.snd, tₙ)) :=
+  rfl
+#align gen_loop.homotopy_to_apply GenLoop.homotopyTo_apply
+
+theorem homotopicTo (i : N) {p q : Ω^ N X x} :
+    Homotopic p q → (toLoop i p).Homotopic (toLoop i q) := by
+  refine' Nonempty.map fun H => ⟨⟨⟨fun t => ⟨homotopyTo i H t, _⟩, _⟩, _, _⟩, _⟩
+  · rintro y ⟨i, iH⟩
+    rw [homotopyTo_apply, H.eq_fst, p.2]
+    all_goals apply Cube.insertAt_boundary; right; exact ⟨i, iH⟩
+  · continuity
+  iterate 2 intro; ext; erw [homotopyTo_apply, toLoop_apply]; swap
+  · apply H.apply_zero
+  · apply H.apply_one
+  intro t y yH
+  constructor <;> ext <;> erw [homotopyTo_apply]
+  apply H.eq_fst; on_goal 2 => apply H.eq_snd
+  all_goals use i; rw [funSplitAt_symm_apply, dif_pos rfl]; exact yH
+#align gen_loop.homotopic_to GenLoop.homotopicTo
+
+/-- The converse to `GenLoop.homotopyTo`: a homotopy between two loops in the space of
+  `n`-dimensional loops can be seen as a homotopy between two `n+1`-dimensional paths. -/
+@[simps!] def homotopyFrom (i : N) {p q : Ω^ N X x} (H : (toLoop i p).Homotopy (toLoop i q)) :
+    C(I × I^N, X) :=
+  (ContinuousMap.comp ⟨_, ContinuousMap.continuous_uncurry⟩
+          (ContinuousMap.comp ⟨Subtype.val, by continuity⟩ H.toContinuousMap).curry).uncurry.comp <|
+    (ContinuousMap.id I).prodMap (Cube.splitAt i).toContinuousMap
+#align gen_loop.homotopy_from GenLoop.homotopyFrom
+-- porting note: @[simps!] no longer too slow in Lean 4.
+#align gen_loop.homotopy_from_apply GenLoop.homotopyFrom_apply
+
+theorem homotopicFrom (i : N) {p q : Ω^ N X x} :
+    (toLoop i p).Homotopic (toLoop i q) → Homotopic p q := by
+  refine' Nonempty.map fun H => ⟨⟨homotopyFrom i H, _, _⟩, _⟩
+  pick_goal 3
+  · rintro t y ⟨j, jH⟩
+    erw [homotopyFrom_apply]
+    obtain rfl | h := eq_or_ne j i
+    · constructor
+      · rw [H.eq_fst]; exacts [congr_arg p ((Cube.splitAt j).left_inv _), jH]
+      · rw [H.eq_snd]; exacts [congr_arg q ((Cube.splitAt j).left_inv _), jH]
+    · rw [p.2 _ ⟨j, jH⟩, q.2 _ ⟨j, jH⟩]; constructor <;> · apply boundary; exact ⟨⟨j, h⟩, jH⟩
+    /- porting note: the following is indented two spaces more than it should be due to
+      strange behavior of `erw` -/
+    all_goals
+      intro
+      apply (homotopyFrom_apply _ _ _).trans
+      first
+      | rw [H.apply_zero]
+      | rw [H.apply_one]
+      first
+      | apply congr_arg p
+      | apply congr_arg q
+      apply (Cube.splitAt i).left_inv
+#align gen_loop.homotopic_from GenLoop.homotopicFrom
+
+/-- Concatenation of two `GenLoop`s along the `i`th coordinate. -/
+def transAt (i : N) (f g : Ω^ N X x) : Ω^ N X x :=
+  copy (fromLoop i <| (toLoop i f).trans <| toLoop i g)
+    (fun t => if (t i : ℝ) ≤ 1 / 2
+      then f (Function.update t i <| Set.projIcc 0 1 zero_le_one (2 * t i))
+      else g (Function.update t i <| Set.projIcc 0 1 zero_le_one (2 * t i - 1)))
+    (by
+      ext1; symm
+      dsimp only [Path.trans, fromLoop, Path.coe_mk_mk, Function.comp_apply, mk_apply,
+        ContinuousMap.comp_apply, toContinuousMap_apply, funSplitAt_apply,
+        ContinuousMap.uncurry_apply, ContinuousMap.coe_mk, Function.uncurry_apply_pair]
+      split_ifs; change f _ = _; swap; change g _ = _
+      all_goals congr 1)
+#align gen_loop.trans_at GenLoop.transAt
+
+/-- Reversal of a `GenLoop` along the `i`th coordinate. -/
+def symmAt (i : N) (f : Ω^ N X x) : Ω^ N X x :=
+  (copy (fromLoop i (toLoop i f).symm) fun t => f fun j => if j = i then σ (t i) else t j) <| by
+    ext1; change _ = f _; congr; ext1; simp
+#align gen_loop.symm_at GenLoop.symmAt
+
+theorem transAt_distrib {i j : N} (h : i ≠ j) (a b c d : Ω^ N X x) :
+    transAt i (transAt j a b) (transAt j c d) = transAt j (transAt i a c) (transAt i b d) := by
+  ext; simp_rw [transAt, coe_copy, Function.update_apply, if_neg h, if_neg h.symm]
+  split_ifs <;>
+    · congr 1; ext1; simp only [Function.update, eq_rec_constant, dite_eq_ite]
+      apply ite_ite_comm; rintro rfl; exact h.symm
+#align gen_loop.trans_at_distrib GenLoop.transAt_distrib
+
+theorem fromLoop_trans_toLoop {i : N} {p q : Ω^ N X x} :
+    fromLoop i ((toLoop i p).trans <| toLoop i q) = transAt i p q :=
+  (copy_eq _ _).symm
+#align gen_loop.from_loop_trans_to_loop GenLoop.fromLoop_trans_toLoop
+
+theorem fromLoop_symm_toLoop {i : N} {p : Ω^ N X x} : fromLoop i (toLoop i p).symm = symmAt i p :=
+  (copy_eq _ _).symm
+#align gen_loop.from_loop_symm_to_loop GenLoop.fromLoop_symm_toLoop
+
+end LoopHomeo
+
 end GenLoop
 
-/-- The `n`th homotopy group at `x` defined as the quotient of `GenLoop n x` by the
-`homotopic` relation.
--/
-def HomotopyGroup (n : ℕ) (x : X) : Type _ :=
-  Quotient (GenLoop.Homotopic.setoid n x)
+/-- The `n`th homotopy group at `x` defined as the quotient of `Ω^n x` by the
+  `GenLoop.Homotopic` relation. -/
+def HomotopyGroup (N X : Type _) [TopologicalSpace X] (x : X) : Type _ :=
+  Quotient (GenLoop.Homotopic.setoid N x)
 #align homotopy_group HomotopyGroup
 
-local notation "π" => HomotopyGroup
-
 -- porting note: in Lean 3 this instance was derived
-instance : Inhabited (π n x) :=
-  inferInstanceAs <| Inhabited <| Quotient (GenLoop.Homotopic.setoid n x)
-
-/-- The 0-dimensional generalized loops based at `x` are in 1-1 correspondence with `X`. -/
-@[simps!]
-def genLoopZeroEquiv : GenLoop 0 x ≃ X where
+instance : Inhabited (HomotopyGroup N X x) :=
+  inferInstanceAs <| Inhabited <| Quotient (GenLoop.Homotopic.setoid N x)
+
+variable [DecidableEq N]
+
+open GenLoop
+
+/-- Equivalence between the homotopy group of X and the fundamental group of
+  `Ω^{j // j ≠ i} x`. -/
+def homotopyGroupEquivFundamentalGroup (i : N) :
+    HomotopyGroup N X x ≃ FundamentalGroup (Ω^ { j // j ≠ i } X x) const := by
+  refine' Equiv.trans _ (CategoryTheory.Groupoid.isoEquivHom _ _).symm
+  apply Quotient.congr (loopHomeo i).toEquiv
+  exact fun p q => ⟨homotopicTo i, homotopicFrom i⟩
+#align homotopy_group_equiv_fundamental_group homotopyGroupEquivFundamentalGroup
+
+/-- Homotopy group of finite index. -/
+@[reducible]
+def HomotopyGroup.Pi (n) (X : Type _) [TopologicalSpace X] (x : X) :=
+  HomotopyGroup (Fin n) _ x
+#align homotopy_group.pi HomotopyGroup.Pi
+
+-- mathport name: exprπ_
+scoped[Topology] notation "π_" => HomotopyGroup.Pi
+
+/-- The 0-dimensional generalized loops based at `x` are in bijection with `X`. -/
+def genLoopHomeoOfIsEmpty (N x) [IsEmpty N] : Ω^ N X x ≃ₜ X
+    where
   toFun f := f 0
-  invFun x := ⟨ContinuousMap.const _ x, fun _ ⟨f0, _⟩ => f0.elim0⟩
-  left_inv f := by ext1; exact congr_arg f (Subsingleton.elim _ _)
+  invFun y := ⟨ContinuousMap.const _ y, fun _ ⟨i, _⟩ => isEmptyElim i⟩
+  left_inv f := by ext; exact congr_arg f (Subsingleton.elim _ _)
   right_inv _ := rfl
-#align gen_loop_zero_equiv genLoopZeroEquiv
-
-open ContinuousMap in
-@[simp] -- porting note: new lemma
-theorem homotopic_genLoopZeroEquiv_symm_iff {a b : X} :
-    (genLoopZeroEquiv.symm a : GenLoop 0 x).Homotopic (genLoopZeroEquiv.symm b) ↔ Joined a b := by
-  rw [GenLoop.Homotopic, Cube.boundary_zero, homotopicRel_empty]
-  exact homotopic_const_iff
-
--- porting note: new lemma    
-theorem joined_genLoopZeroEquiv_iff {f g : GenLoop 0 x} :
-    Joined (genLoopZeroEquiv f) (genLoopZeroEquiv g) ↔ f.Homotopic g := by
-  rw [← homotopic_genLoopZeroEquiv_symm_iff, Equiv.symm_apply_apply, Equiv.symm_apply_apply]
-
-/-- The 0th homotopy "group" is equivalent to the path components of `X`, aka the `ZerothHomotopy`.
--/
-def pi0EquivPathComponents : π 0 x ≃ ZerothHomotopy X :=
-  Quotient.congr (genLoopZeroEquiv (x := x)) <| fun _ _ ↦ joined_genLoopZeroEquiv_iff.symm
-#align pi0_equiv_path_components pi0EquivPathComponents
-
-/-- The 1-dimensional generalized loops based at `x` are in 1-1 correspondence with
-  paths from `x` to itself. -/
-@[simps!] -- porting note: TODO: `symm_apply_apply` doesn't unfold `↑(ContinuousMap.eval 0)`
-def genLoopOneEquivPathSelf : GenLoop 1 x ≃ Path x x where
-  toFun p := p.diagonal
+  continuous_toFun := (ContinuousMap.continuous_eval_const' (0 : N → I)).comp continuous_induced_dom
+  continuous_invFun := ContinuousMap.const'.2.subtype_mk _
+#align gen_loop_homeo_of_is_empty genLoopHomeoOfIsEmpty
+
+/-- The homotopy "group" indexed by an empty type is in bijection with
+  the path components of `X`, aka the `zeroth_homotopy`. -/
+def homotopyGroupEquivZerothHomotopyOfIsEmpty (N x) [IsEmpty N] :
+    HomotopyGroup N X x ≃ ZerothHomotopy X :=
+  Quotient.congr (genLoopHomeoOfIsEmpty N x).toEquiv
+    (by
+      -- joined iff homotopic
+      intros a₁ a₂;
+      constructor <;> rintro ⟨H⟩
+      exacts
+        [⟨{ toFun := fun t => H ⟨t, isEmptyElim⟩
+            source' := (H.apply_zero _).trans (congr_arg a₁ <| Subsingleton.elim _ _)
+            target' := (H.apply_one _).trans (congr_arg a₂ <| Subsingleton.elim _ _) }⟩,
+        ⟨{  toFun := fun t0 => H t0.fst
+            map_zero_left := fun _ => H.source.trans (congr_arg a₁ <| Subsingleton.elim _ _)
+            map_one_left := fun _ => H.target.trans (congr_arg a₂ <| Subsingleton.elim _ _)
+            prop' := fun _ _ ⟨i, _⟩ => isEmptyElim i }⟩])
+#align homotopy_group_equiv_zeroth_homotopy_of_is_empty homotopyGroupEquivZerothHomotopyOfIsEmpty
+
+/-- The 0th homotopy "group" is in bijection with `zeroth_homotopy`. -/
+def HomotopyGroup.pi0EquivZerothHomotopy : π_ 0 X x ≃ ZerothHomotopy X :=
+  homotopyGroupEquivZerothHomotopyOfIsEmpty (Fin 0) x
+#align homotopy_group.pi_0_equiv_zeroth_homotopy HomotopyGroup.pi0EquivZerothHomotopy
+
+/-- The 1-dimensional generalized loops based at `x` are in bijection with loops at `x`. -/
+def genLoopEquivOfUnique (N) [Unique N] : Ω^ N X x ≃ Ω X x
+    where
+  toFun p :=
+    Path.mk ⟨fun t => p fun _ => t, by continuity⟩
+      (GenLoop.boundary _ (fun _ => 0) ⟨default, Or.inl rfl⟩)
+      (GenLoop.boundary _ (fun _ => 1) ⟨default, Or.inr rfl⟩)
   invFun p :=
-    { toContinuousMap := p.comp <| .eval 0
-      boundary := by
-        rw [Cube.boundary_one]
-        rintro _ (rfl | rfl)
-        exacts [p.source, p.target] }
-  left_inv p := by ext1 y; exact congr_arg p y.one_char.symm
-  right_inv p := rfl
-#align gen_loop_one_equiv_path_self genLoopOneEquivPathSelf
-
--- porting note: new theorem
-theorem genLoopOneEquivPathSelf_symm_homotopic_iff {f g : Path x x} :
-    (genLoopOneEquivPathSelf.symm f).Homotopic (genLoopOneEquivPathSelf.symm g) ↔
-      f.Homotopic g := by
-  refine ⟨GenLoop.Homotopic.diagonal, ?_⟩
-  rintro ⟨H⟩
-  refine ⟨H.1.compContinuousMap _, ?_⟩
-  rw [Cube.boundary_one]
-  rintro t _ (rfl | rfl)
-  · exact H.prop' _ _ (.inl rfl)
-  · exact H.prop' _ _ (.inr rfl)
-
--- porting note: new theorem
-theorem genLoopOneEquivPathSelf_homotopic_iff {f g : GenLoop 1 x} :
-    (genLoopOneEquivPathSelf f).Homotopic (genLoopOneEquivPathSelf g) ↔ f.Homotopic g := by
-  rw [← genLoopOneEquivPathSelf_symm_homotopic_iff, Equiv.symm_apply_apply, Equiv.symm_apply_apply]
-
-/-- The first homotopy group at `x` is equivalent to the fundamental group,
-i.e. the loops based at `x` up to homotopy.
--/
-def pi1EquivFundamentalGroup : π 1 x ≃ FundamentalGroup X x := by
-  refine Equiv.trans ?_ (CategoryTheory.Groupoid.isoEquivHom _ _).symm
-  exact Quotient.congr genLoopOneEquivPathSelf fun _ _ ↦ genLoopOneEquivPathSelf_homotopic_iff.symm
-#align pi1_equiv_fundamental_group pi1EquivFundamentalGroup
+    ⟨⟨fun c => p (c default), by continuity⟩,
+      by
+      rintro y ⟨i, iH | iH⟩ <;> cases Unique.eq_default i <;> apply (congr_arg p iH).trans
+      exacts [p.source, p.target]⟩
+  left_inv p := by ext y; exact congr_arg p (eq_const_of_unique y).symm
+  right_inv p := by ext; rfl
+
+#align gen_loop_equiv_of_unique genLoopEquivOfUnique
+
+/- TODO (?): deducing this from `homotopyGroupEquivFundamentalGroup` would require
+  combination of `CategoryTheory.Functor.mapAut` and
+  `FundamentalGroupoid.fundamentalGroupoidFunctor` applied to `genLoopHomeoOfIsEmpty`,
+  with possibly worse defeq. -/
+/-- The homotopy group at `x` indexed by a singleton is in bijection with the fundamental group,
+  i.e. the loops based at `x` up to homotopy. -/
+def homotopyGroupEquivFundamentalGroupOfUnique (N) [Unique N] :
+    HomotopyGroup N X x ≃ FundamentalGroup X x := by
+  refine' Equiv.trans _ (CategoryTheory.Groupoid.isoEquivHom _ _).symm
+  refine' Quotient.congr (genLoopEquivOfUnique N) _
+  intros a₁ a₂; constructor <;> rintro ⟨H⟩
+  · exact
+      ⟨{  toFun := fun tx => H (tx.fst, fun _ => tx.snd)
+          map_zero_left := fun _ => H.apply_zero _
+          map_one_left := fun _ => H.apply_one _
+          prop' := fun t y iH => H.prop' _ _ ⟨default, iH⟩ }⟩
+  refine'
+    ⟨⟨⟨⟨fun tx => H (tx.fst, tx.snd default), H.continuous.comp _⟩, fun y => _, fun y => _⟩, _⟩⟩
+  · exact continuous_fst.prod_mk ((continuous_apply _).comp continuous_snd)
+  · exact (H.apply_zero _).trans (congr_arg a₁ (eq_const_of_unique y).symm)
+  · exact (H.apply_one _).trans (congr_arg a₂ (eq_const_of_unique y).symm)
+  · rintro t y ⟨i, iH⟩
+    cases Unique.eq_default i; constructor
+    · exact (H.eq_fst _ iH).trans (congr_arg a₁ (eq_const_of_unique y).symm)
+    · exact (H.eq_snd _ iH).trans (congr_arg a₂ (eq_const_of_unique y).symm)
+#align homotopy_group_equiv_fundamental_group_of_unique homotopyGroupEquivFundamentalGroupOfUnique
+
+/-- The first homotopy group at `x` is in bijection with the fundamental group. -/
+def HomotopyGroup.pi1EquivFundamentalGroup : π_ 1 X x ≃ FundamentalGroup X x :=
+  homotopyGroupEquivFundamentalGroupOfUnique (Fin 1)
+#align homotopy_group.pi_1_equiv_fundamental_group HomotopyGroup.pi1EquivFundamentalGroup
+
+namespace HomotopyGroup
+
+/-- Group structure on `HomotopyGroup N X x` for nonempty `N` (in particular `π_(n+1) X x`). -/
+instance group (N) [DecidableEq N] [Nonempty N] : Group (HomotopyGroup N X x) :=
+  (homotopyGroupEquivFundamentalGroup <| Classical.arbitrary N).group
+#align homotopy_group.group HomotopyGroup.group
+
+/-- Group structure on `HomotopyGroup` obtained by pulling back path composition along the
+  `i`th direction. The group structures for two different `i j : N` distribute over each
+  other, and therefore are equal by the Eckmann-Hilton argument. -/
+@[reducible]
+def auxGroup (i : N) : Group (HomotopyGroup N X x) :=
+  (homotopyGroupEquivFundamentalGroup i).group
+#align homotopy_group.aux_group HomotopyGroup.auxGroup
+
+theorem isUnital_auxGroup (i : N) :
+    EckmannHilton.IsUnital (auxGroup i).mul (⟦const⟧ : HomotopyGroup N X x) :=
+  ⟨⟨(auxGroup i).one_mul⟩, ⟨(auxGroup i).mul_one⟩⟩
+#align homotopy_group.is_unital_aux_group HomotopyGroup.isUnital_auxGroup
+
+theorem auxGroup_indep (i j : N) : (auxGroup i : Group (HomotopyGroup N X x)) = auxGroup j := by
+  by_cases h : i = j; · rw [h]
+  refine' Group.ext (EckmannHilton.mul (isUnital_auxGroup i) (isUnital_auxGroup j) _)
+  rintro ⟨a⟩ ⟨b⟩ ⟨c⟩ ⟨d⟩
+  change Quotient.mk' _ = _
+  apply congr_arg Quotient.mk'
+  simp only [fromLoop_trans_toLoop, transAt_distrib h, coe_toEquiv, loopHomeo_apply,
+    coe_symm_toEquiv, loopHomeo_symm_apply]
+#align homotopy_group.aux_group_indep HomotopyGroup.auxGroup_indep
+
+theorem transAt_indep {i} (j) (f g : Ω^ N X x) :
+    (⟦transAt i f g⟧ : HomotopyGroup N X x) = ⟦transAt j f g⟧ := by
+  simp_rw [← fromLoop_trans_toLoop]
+  let m := fun (G) (_ : Group G) => ((· * ·) : G → G → G)
+  exact congr_fun₂ (congr_arg (m <| HomotopyGroup N X x) <| auxGroup_indep i j) ⟦g⟧ ⟦f⟧
+#align homotopy_group.trans_at_indep HomotopyGroup.transAt_indep
+
+theorem symmAt_indep {i} (j) (f : Ω^ N X x) :
+    (⟦symmAt i f⟧ : HomotopyGroup N X x) = ⟦symmAt j f⟧ := by
+  simp_rw [← fromLoop_symm_toLoop]
+  let inv := fun (G) (_ : Group G) => ((·⁻¹) : G → G)
+  exact congr_fun (congr_arg (inv <| HomotopyGroup N X x) <| auxGroup_indep i j) ⟦f⟧
+#align homotopy_group.symm_at_indep HomotopyGroup.symmAt_indep
+
+/-- Characterization of multiplicative identity -/
+theorem one_def [Nonempty N] : (1 : HomotopyGroup N X x) = ⟦const⟧ :=
+  rfl
+#align homotopy_group.one_def HomotopyGroup.one_def
+
+/-- Characterization of multiplication -/
+theorem mul_spec [Nonempty N] {i} {p q : Ω^ N X x} :
+  -- porting note: TODO: introduce `HomotopyGroup.mk` and remove defeq abuse.
+    ((· * ·) : _ → _ → HomotopyGroup N X x) ⟦p⟧ ⟦q⟧ = ⟦transAt i q p⟧ := by
+  rw [transAt_indep _ q, ← fromLoop_trans_toLoop]; apply Quotient.sound; rfl
+#align homotopy_group.mul_spec HomotopyGroup.mul_spec
+
+/-- Characterization of multiplicative inverse -/
+theorem inv_spec [Nonempty N] {i} {p : Ω^ N X x} : ((⟦p⟧)⁻¹ : HomotopyGroup N X x) = ⟦symmAt i p⟧ :=
+  by rw [symmAt_indep _ p, ← fromLoop_symm_toLoop]; apply Quotient.sound; rfl
+#align homotopy_group.inv_spec HomotopyGroup.inv_spec
+
+/-- Multiplication on `HomotopyGroup N X x` is commutative for nontrivial `N`.
+  In particular, multiplication on `π_(n+2)` is commutative. -/
+instance commGroup [Nontrivial N] : CommGroup (HomotopyGroup N X x) :=
+  let h := exists_ne (Classical.arbitrary N)
+  @EckmannHilton.commGroup (HomotopyGroup N X x) _ 1 (isUnital_auxGroup <| Classical.choose h) _
+    (by
+      rintro ⟨a⟩ ⟨b⟩ ⟨c⟩ ⟨d⟩
+      apply congr_arg Quotient.mk'
+      simp only [fromLoop_trans_toLoop, transAt_distrib <| Classical.choose_spec h, coe_toEquiv,
+        loopHomeo_apply, coe_symm_toEquiv, loopHomeo_symm_apply])
+#align homotopy_group.comm_group HomotopyGroup.commGroup
+
+end HomotopyGroup
chore: fix many typos (#4967)

These are all doc fixes

Diff
@@ -19,7 +19,7 @@ that send the boundary to the base point `x`, up to homotopic equivalence.
 Note that such functions are generalized loops `GenLoop n x`, in particular
 `GenLoop 1 x ≃ Path x x`
 
-We show that `π 0 x` is equivalent to the path-conected components, and
+We show that `π 0 x` is equivalent to the path-connected components, and
 that `π 1 x` is equivalent to the fundamental group at `x`.
 
 ## definitions
chore: fix many typos (#4535)

Run codespell Mathlib and keep some suggestions.

Diff
@@ -24,7 +24,7 @@ that `π 1 x` is equivalent to the fundamental group at `x`.
 
 ## definitions
 
-* `GenLoop n x` is the type of continous fuctions `I^n → X` that send the boundary to `x`
+* `GenLoop n x` is the type of continuous functions `I^n → X` that send the boundary to `x`
 * `HomotopyGroup n x` denoted `π n x` is the quotient of `GenLoop n x` by homotopy relative
   to the boundary
 
feat: port Topology.Homotopy.HomotopyGroup (#4485)

Dependencies 10 + 643

644 files ported (98.5%)
266995 lines ported (98.1%)
Show graph

The unported dependencies are

The following 1 dependencies have changed in mathlib3 since they were ported, which may complicate porting this file