Skip to content

Instantly share code, notes, and snippets.

@alreadydone
Last active October 23, 2023 05:48
Show Gist options
  • Save alreadydone/c88265c99c73fa822d48e1abfd01282f to your computer and use it in GitHub Desktop.
Save alreadydone/c88265c99c73fa822d48e1abfd01282f to your computer and use it in GitHub Desktop.
The homotopy lifting property of covering maps.
/-
Copyright (c) 2023 Junyan Xu. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Junyan Xu
-/
import Mathlib.Topology.Covering
import Mathlib.Topology.Homotopy.Path
import Mathlib.Topology.UnitInterval
/-!
# The Homotopy lifting property of covering maps
Currently, this file only proves uniqueness of lifting, not existence,
but under some more general conditions than covering maps, in order to
apply to situations such as the monodromy theorem for analytic continuations.
-/
open Topology unitInterval
section Separated
variable {X Y} [TopologicalSpace X]
def SeparatedMap (f : X → Y) : Prop := ∀ x₁ x₂, f x₁ = f x₂ →
x₁ ≠ x₂ → ∃ s₁ s₂, IsOpen s₁ ∧ IsOpen s₂ ∧ x₁ ∈ s₁ ∧ x₂ ∈ s₂ ∧ Disjoint s₁ s₂
lemma t2space_iff_separatedMap (y : Y) : T2Space X ↔ SeparatedMap fun _ : X ↦ y :=
⟨fun ⟨t2⟩ x₁ x₂ _ hne ↦ t2 x₁ x₂ hne, fun sep ↦ ⟨fun x₁ x₂ hne ↦ sep x₁ x₂ rfl hne⟩⟩
lemma T2Space.separatedMap [T2Space X] (f : X → Y) : SeparatedMap f := fun _ _ _ ↦ t2_separation
lemma separatedMap_iff_disjoint_nhds {f : X → Y} : SeparatedMap f ↔
∀ x₁ x₂, f x₁ = f x₂ → x₁ ≠ x₂ → Disjoint (𝓝 x₁) (𝓝 x₂) :=
forall₃_congr fun x x' _ ↦ by simp only [(nhds_basis_opens x).disjoint_iff (nhds_basis_opens x'),
exists_prop, ← exists_and_left, and_assoc, and_comm, and_left_comm]
lemma separatedMap_iff_nhds {f : X → Y} : SeparatedMap f ↔
∀ x₁ x₂, f x₁ = f x₂ → x₁ ≠ x₂ → ∃ s₁ ∈ 𝓝 x₁, ∃ s₂ ∈ 𝓝 x₂, Disjoint s₁ s₂ := by
simp_rw [separatedMap_iff_disjoint_nhds, Filter.disjoint_iff]
abbrev Pullback {Z} (f : X → Y) (g : Z → Y) := {p : X × Z // f p.1 = g p.2}
open Set Filter in
theorem separatedMap_iff_isClosed_diagonal {f : X → Y} : SeparatedMap f ↔
IsClosed {p : Pullback f f | p.val.1 = p.val.2} := by
simp_rw [separatedMap_iff_nhds, ← isOpen_compl_iff, isOpen_iff_mem_nhds,
Subtype.forall, Prod.forall, nhds_induced, nhds_prod_eq]
refine forall₄_congr fun x₁ x₂ _ _ ↦ ⟨fun h ↦ ?_, fun ⟨t, ht, t_sub⟩ ↦ ?_⟩
· simp_rw [← Filter.disjoint_iff, ← compl_diagonal_mem_prod] at h
exact ⟨_, h, subset_rfl⟩
· obtain ⟨s₁, h₁, s₂, h₂, s_sub⟩ := mem_prod_iff.mp ht
exact ⟨s₁, h₁, s₂, h₂, disjoint_left.2 fun x h₁ h₂ ↦ @t_sub ⟨(x, x), rfl⟩ (s_sub ⟨h₁, h₂⟩) rfl⟩
theorem separatedMap_iff_isClosedMap {f : X → Y} :
SeparatedMap f ↔ IsClosedMap fun x ↦ (⟨(x, x), rfl⟩ : Pullback f f) :=
separatedMap_iff_isClosed_diagonal.trans <| by
refine ⟨fun diag_closed s s_closed ↦ ?_, fun closed_map ↦ ?_⟩
· convert diag_closed.inter ((s_closed.prod s_closed).preimage continuous_subtype_val)
ext ⟨⟨x₁, x₂⟩, he⟩; constructor
· rintro ⟨x, hx, he⟩
simp_rw [Subtype.ext_iff, Prod.mk.inj_iff] at he
cases he.1; cases he.2; exact ⟨rfl, hx, hx⟩
· rintro ⟨(rfl : x₁ = x₂), h, _⟩; exact ⟨x₁, h, rfl⟩
· convert closed_map _ isClosed_univ
ext ⟨⟨x₁, x₂⟩, he⟩; constructor
· rintro (rfl : x₁ = x₂); exact ⟨x₁, trivial, rfl⟩
· rintro ⟨x, -, he⟩
simp_rw [Subtype.ext_iff, Prod.mk.inj_iff] at he
exact he.1.symm.trans he.2
theorem t2space_iff_isClosedMap : T2Space X ↔ IsClosedMap fun x : X ↦ (x, x) := by
let f := fun _ : X ↦ Unit.unit
let H : {p : X × X // f p.1 = f p.2} ≃ₜ X × X :=
(Homeomorph.setCongr <| Set.eq_univ_of_forall fun p ↦ rfl).trans (Homeomorph.Set.univ _)
rw [t2space_iff_separatedMap Unit.unit, separatedMap_iff_isClosedMap]
exact ⟨fun h ↦ H.isClosedMap.comp h, fun h ↦ H.symm.isClosedMap.comp h⟩
theorem separatedMap.pullback {f : X → Y} (sep : SeparatedMap f) {Z} [TopologicalSpace Z]
(g : Z → Y) : SeparatedMap (fun p : Pullback f g ↦ p.1.2) := by
rw [separatedMap_iff_isClosed_diagonal] at sep ⊢
let f' := fun p : Pullback f g ↦ p.1.2
let proj : Pullback f' f' → Pullback f f := fun p ↦ ⟨(p.val.1.val.1, p.val.2.val.1),
(p.val.1.prop.trans <| congr_arg g p.2).trans p.val.2.prop.symm⟩
have : Continuous proj
· refine continuous_induced_rng.mpr (continuous_prod_mk.mpr ⟨?_, ?_⟩) <;>
apply_rules [continuous_fst, continuous_snd, continuous_subtype_val, Continuous.comp]
convert sep.preimage this; ext ⟨⟨p₁, p₂⟩, he⟩
simp_rw [Set.mem_setOf, Subtype.ext_iff, Prod.ext_iff]
exact and_iff_left he
end Separated
variable {E X A : Type*} [TopologicalSpace E] [TopologicalSpace X] [TopologicalSpace A] {p : E → X}
namespace SeparatedMap
variable [PreconnectedSpace A] (sep : SeparatedMap p) (hp : ∀ e : E, ∃ U ∈ 𝓝 e, U.InjOn p)
-- connectedComponent version, without assume PreconnectedSpace ..
/-- If `p` is a locally injective separated map, and `A` is a connected space,
then two lifts `g₁, g₂ : A → E` of a map `f : A → X` are equal if they agree at one point. -/
theorem eq_of_comp_eq (g₁ g₂ : C(A,E))
(he : p ∘ g₁ = p ∘ g₂) (a : A) (ha : g₁ a = g₂ a) : g₁ = g₂ := by
have := IsClopen.eq_univ (s := {a | g₁ a = g₂ a}) ⟨?_, ?_⟩ ⟨a, ha⟩
· ext a; apply this.symm ▸ Set.mem_univ a
/- Since A is connected and s := {a | g₁ a = g₂ a} inhabited by a,
we just need to show that s is open and closed. -/
· refine isOpen_iff_mem_nhds.mpr (fun a ha ↦ ?_)
/- Given a point a where g₁ and g₂ agree,
take an open neighborhood U of g₁(a) = g₂(a) on which p is injective.
Then g₁ and g₂ agree on the open neighborhood g₁⁻¹(U) ∩ g₂⁻¹(U) of a. -/
obtain ⟨U, hU, hi⟩ := hp (g₁ a)
apply Filter.mem_of_superset (Filter.inter_mem (g₁.2.continuousAt.preimage_mem_nhds hU) <|
g₂.2.continuousAt.preimage_mem_nhds <| Set.mem_setOf.mp ha ▸ hU)
exact fun a' ha ↦ hi ha.1 ha.2 (congr_fun he a')
· simp_rw [← isOpen_compl_iff, isOpen_iff_mem_nhds]
intro a ha
/- Given a point a where g₁ and g₂ doesn't agree,
take disjoint neighborhoods U₁ of g₁(a) and U₂ of g₂(a),
then g₁ and g₂ doesn't agree on any point in the neighborhood g₁⁻¹(U₁) ∩ g₂⁻¹(U₂) of a. -/
obtain ⟨U₁, h₁, U₂, h₂, disj⟩ := separatedMap_iff_nhds.mp sep _ _ (congr_fun he a) ha
apply Filter.mem_of_superset (Filter.inter_mem (g₁.2.continuousAt.preimage_mem_nhds h₁) <|
g₂.2.continuousAt.preimage_mem_nhds h₂) (fun a h ↦ Set.disjoint_iff_forall_ne.mp disj h.1 h.2)
theorem const_of_comp (g : C(A,E)) (he : ∀ a a', p (g a) = p (g a')) (a a') : g a = g a' :=
FunLike.congr_fun
(sep.eq_of_comp_eq hp g (ContinuousMap.const A (g a')) (funext fun a ↦ he a a') a' rfl) a
end SeparatedMap
theorem IsCoveringMap.separatedMap (hp : IsCoveringMap p) : SeparatedMap p :=
fun e₁ e₂ he hne ↦ by
obtain ⟨_, t, he₁⟩ := hp (p e₁)
have he₂ := he₁; simp_rw [he] at he₂; rw [← Set.mem_preimage, ← t.source_eq] at he₁ he₂
refine ⟨t.source ∩ (Prod.snd ∘ t) ⁻¹' {(t e₁).2}, t.source ∩ (Prod.snd ∘ t) ⁻¹' {(t e₂).2},
?_, ?_, ⟨he₁, rfl⟩, ⟨he₂, rfl⟩, Set.disjoint_left.mpr fun x h₁ h₂ ↦ hne (t.injOn he₁ he₂ ?_)⟩
iterate 2
exact t.continuous_toFun.preimage_open_of_open t.open_source
(continuous_snd.isOpen_preimage _ <| isOpen_discrete _)
refine Prod.ext ?_ (h₁.2.symm.trans h₂.2)
rwa [t.proj_toFun e₁ he₁, t.proj_toFun e₂ he₂]
lemma IsLocallyHomeomorph.injOn (hp : IsLocallyHomeomorph p) (e : E) :
∃ U ∈ 𝓝 e, U.InjOn p := by
obtain ⟨p, he, rfl⟩ := hp e; exact ⟨p.source, p.open_source.mem_nhds he, p.injOn⟩
theorem IsCoveringMap.eq_of_comp_eq [PreconnectedSpace A] (hp : IsCoveringMap p) (g₁ g₂ : C(A,E))
(he : p ∘ g₁ = p ∘ g₂) (a : A) (ha : g₁ a = g₂ a) : g₁ = g₂ :=
hp.separatedMap.eq_of_comp_eq hp.isLocallyHomeomorph.injOn g₁ g₂ he a ha
theorem IsCoveringMap.const_of_comp [PreconnectedSpace A] (hp : IsCoveringMap p) (g : C(A,E))
(he : ∀ a a', p (g a) = p (g a')) (a a') : g a = g a' :=
hp.separatedMap.const_of_comp hp.isLocallyHomeomorph.injOn g he a a'
lemma lebesgue_number_lemma_unitInterval {ι} {c : ι → Set I} (hc₁ : ∀ i, IsOpen (c i))
(hc₂ : Set.univ ⊆ ⋃ i, c i) : ∃ t : ℕ → I, t 0 = 0 ∧ Monotone t ∧
(∀ n, ∃ i, Set.Icc (t n) (t <| n + 1) ⊆ c i) ∧ ∃ n, ∀ m ≥ n, t m = 1 := by
obtain ⟨δ, δ_pos, ball_subset⟩ := lebesgue_number_lemma_of_metric isCompact_univ hc₁ hc₂
refine ⟨fun n ↦ Set.projIcc 0 1 zero_le_one (n * (δ/2)), ?_, fun n m hnm ↦ ?_, fun n ↦ ?_, ?_⟩
· dsimp only; rw [Nat.cast_zero, zero_mul, Set.projIcc_left]; rfl
· apply Set.monotone_projIcc
rw [mul_le_mul_right (div_pos δ_pos zero_lt_two)]
exact_mod_cast hnm
· obtain ⟨i, hsub⟩ := ball_subset (Set.projIcc 0 1 zero_le_one (n * (δ/2))) trivial
refine ⟨i, fun x hx ↦ hsub ?_⟩
rw [Metric.mem_ball]
apply (abs_eq_self.mpr _).trans_lt
· apply (sub_le_sub_right _ _).trans_lt
on_goal 3 => exact hx.2
refine (max_sub_max_le_max _ _ _ _).trans_lt (max_lt (by rwa [sub_zero]) ?_)
refine ((le_abs_self _).trans <| abs_min_sub_min_le_max _ _ _ _).trans_lt (max_lt ?_ ?_)
· rwa [sub_self, abs_zero]
· rw [← sub_mul, Nat.cast_succ, add_sub_cancel', one_mul, abs_lt]
constructor <;> linarith only [δ_pos]
· exact sub_nonneg_of_le hx.1
· refine ⟨Nat.ceil (δ/2)⁻¹, fun n hn ↦ (Set.projIcc_eq_right zero_lt_one).mpr ?_⟩
rwa [GE.ge, Nat.ceil_le, inv_pos_le_iff_one_le_mul (div_pos δ_pos zero_lt_two)] at hn
instance : BoundedOrder I := Set.Icc.boundedOrder zero_le_one
-- generalize to IsLocallyHomeomorphOn?
/-- If `p : E → X` is a local homeomorphism, and if `g : I × A → E` is a lift of `f : C(I × A, X)`
continuous on `{0} × A ∪ I × {a}` for some `a : A`, then there exists a neighborhood `N ∈ 𝓝 a`
and `g' : I × A → E` continuous on `I × N` that agrees with `g` on `{0} × A ∪ I × {a}`.
The proof follows Hatcher, Proof of Theorem 1.7, p.30.
This lemma should also be true for an arbitrary space in place of `I` if `A` is locally connected
and `p` is a separated map, which guarantees uniqueness and therefore well-definedness
on the intersections. -/
theorem IsLocallyHomeomorph.exists_lift_nhds (hp : IsLocallyHomeomorph p)
{f : C(I × A, X)} {g : I × A → E} (g_lifts : p ∘ g = f)
(cont_0 : Continuous (g ⟨0, ·⟩)) (a : A) (cont_a : Continuous (g ⟨·, a⟩)) :
∃ N ∈ 𝓝 a, ∃ g' : I × A → E, ContinuousOn g' (Set.univ ×ˢ N) ∧ p ∘ g' = f ∧
(∀ a, g' (0, a) = g (0, a)) ∧ ∀ t, g' (t, a) = g (t, a) := by
/- For every `e : E`, we can upgrade `p` to a LocalHomeomorph `q e` around `e`. -/
choose q mem_source hpq using hp
obtain ⟨t, t_0, t_mono, t_sub, n_max, h_max⟩ := lebesgue_number_lemma_unitInterval
(fun e ↦ (q e).open_source.preimage cont_a)
fun t _ ↦ Set.mem_iUnion.mpr ⟨g (t, a), mem_source _⟩
suffices : ∀ n, ∃ N, a ∈ N ∧ IsOpen N ∧ ∃ g' : I × A → E, ContinuousOn g' (Set.Icc 0 (t n) ×ˢ N) ∧
p ∘ g' = f ∧ (∀ a, g' (0, a) = g (0, a)) ∧ ∀ t' ≤ t n, g' (t', a) = g (t', a)
· obtain ⟨N, haN, N_open, hN⟩ := this n_max
simp_rw [h_max _ le_rfl] at hN
refine ⟨N, N_open.mem_nhds haN, ?_⟩; convert hN
· rw [eq_comm, Set.eq_univ_iff_forall]; exact fun t ↦ ⟨bot_le, le_top⟩
· rw [imp_iff_right]; exact le_top
refine Nat.rec ⟨_, Set.mem_univ a, isOpen_univ, g, ?_, g_lifts, fun a ↦ rfl, fun _ _ ↦ rfl⟩
(fun n ⟨N, haN, N_open, g', cont_g', g'_lifts, g'_0, g'_a⟩ ↦ ?_)
· refine (cont_0.comp continuous_snd).continuousOn.congr (fun ta ⟨ht, _⟩ ↦ ?_)
rw [t_0, Set.Icc_self, Set.mem_singleton_iff] at ht; rw [← ta.eta, ht]; rfl
obtain ⟨e, h_sub⟩ := t_sub n
have : Set.Icc (t n) (t (n+1)) ×ˢ {a} ⊆ f ⁻¹' (q e).target
· rintro ⟨t0, a'⟩ ⟨ht, ha⟩
rw [Set.mem_singleton_iff] at ha; dsimp only at ha
rw [← g_lifts, hpq e, ha]
exact (q e).map_source (h_sub ht)
obtain ⟨u, v, -, v_open, hu, hav, huv⟩ := generalized_tube_lemma isClosed_Icc.isCompact
isCompact_singleton ((q e).open_target.preimage f.continuous) this
classical
refine ⟨_, ?_, v_open.inter <| (cont_g'.comp (Continuous.Prod.mk <| t n).continuousOn
fun a ha ↦ ⟨?_, ha⟩).preimage_open_of_open N_open (q e).open_source,
fun ta ↦ if ta.1 ≤ t n then g' ta else if f ta ∈ (q e).target then (q e).symm (f ta) else g ta,
ContinuousOn.if (fun ta ⟨⟨_, hav, _, ha⟩, hfr⟩ ↦ ?_) (cont_g'.mono fun ta ⟨hta, ht⟩ ↦ ?_) ?_,
?_, fun a ↦ ?_, fun t0 htn1 ↦ ?_⟩
· refine ⟨Set.singleton_subset_iff.mp hav, haN, ?_⟩
change g' (t n, a) ∈ (q e).source; rw [g'_a _ le_rfl]
exact h_sub ⟨le_rfl, t_mono n.le_succ⟩
· rw [← t_0]; exact ⟨t_mono n.zero_le, le_rfl⟩
· have ht := Set.mem_setOf.mp (frontier_le_subset_eq continuous_fst continuous_const hfr)
have : f ta ∈ (q e).target := huv ⟨hu (by rw [ht]; exact ⟨le_rfl, t_mono n.le_succ⟩), hav⟩
rw [if_pos this]
apply (q e).injOn (by rw [← ta.eta, ht]; exact ha) ((q e).map_target this)
rw [(q e).right_inv this, ← hpq e]; exact congr_fun g'_lifts ta
· rw [closure_le_eq continuous_fst continuous_const] at ht
exact ⟨⟨hta.1.1, ht⟩, hta.2.2.1⟩
· simp_rw [not_le]; exact (ContinuousOn.congr ((q e).continuous_invFun.comp f.2.continuousOn
fun _ h ↦ huv ⟨hu ⟨h.2, h.1.1.2⟩, h.1.2.1⟩)
fun _ h ↦ if_pos <| huv ⟨hu ⟨h.2, h.1.1.2⟩, h.1.2.1⟩).mono
(Set.inter_subset_inter_right _ <| closure_lt_subset_le continuous_const continuous_fst)
· ext ta; rw [Function.comp_apply]; split_ifs with _ hv
· exact congr_fun g'_lifts ta
· rw [hpq e, (q e).right_inv hv]
· exact congr_fun g_lifts ta
· rw [← g'_0]; exact if_pos bot_le
· dsimp only; split_ifs with htn hf
· exact g'_a t0 htn
· apply (q e).injOn ((q e).map_target hf) (h_sub ⟨le_of_not_ge htn, htn1⟩)
rw [(q e).right_inv hf, ← hpq e]; exact (congr_fun g_lifts _).symm
· rfl
theorem IsLocallyHomeomorph.continuous_lift (loc_homeo : IsLocallyHomeomorph p)
(sep : SeparatedMap p) (f : C(I × A, X)) {g : I × A → E} (g_lifts : p ∘ g = f)
(cont_0 : Continuous (g ⟨0, ·⟩)) (cont_A : ∀ a, Continuous (g ⟨·, a⟩)) : Continuous g := by
rw [continuous_iff_continuousAt]
intro ⟨t, a⟩
obtain ⟨N, haN, g', cont_g', g'_lifts, g'_0, -⟩ :=
loc_homeo.exists_lift_nhds g_lifts cont_0 a (cont_A a)
refine (cont_g'.congr fun ⟨t, a⟩ ⟨_, ha⟩ ↦ ?_).continuousAt (prod_mem_nhds Filter.univ_mem haN)
refine FunLike.congr_fun (sep.eq_of_comp_eq loc_homeo.injOn ⟨_, cont_A a⟩
⟨_, cont_g'.comp_continuous (Continuous.Prod.mk_left a) (fun _ ↦ ⟨trivial, ha⟩)⟩
?_ 0 (g'_0 a).symm) t
ext t; apply congr_fun (g_lifts.trans g'_lifts.symm)
namespace IsCoveringMap
variable (hp : IsCoveringMap p)
section path_lifting
variable (γ : C(I,X)) (e : E) (γ_0 : γ 0 = p e)
/-- The path lifting property (existence and uniqueness) for covering maps. -/
theorem exists_path_lifts : ∃ Γ : C(I,E), p ∘ Γ = γ ∧ Γ 0 = e := by
have := hp; choose _ q mem_base using this
obtain ⟨t, t_0, t_mono, t_sub, n_max, h_max⟩ := lebesgue_number_lemma_unitInterval
(fun x ↦ (q x).open_baseSet.preimage γ.continuous) fun t _ ↦ Set.mem_iUnion.2 ⟨γ t, mem_base _⟩
suffices : ∀ n, ∃ Γ : I → E, ContinuousOn Γ (Set.Icc 0 (t n)) ∧
(Set.Icc 0 (t n)).EqOn (p ∘ Γ) γ ∧ Γ 0 = e
· obtain ⟨Γ, cont, eqOn, Γ_0⟩ := this n_max; rw [h_max _ le_rfl] at cont eqOn
exact ⟨⟨Γ, continuous_iff_continuousOn_univ.mpr
(by convert cont; rw [eq_comm, Set.eq_univ_iff_forall]; exact fun t ↦ ⟨bot_le, le_top⟩)⟩,
funext fun _ ↦ eqOn ⟨bot_le, le_top⟩, Γ_0⟩
refine Nat.rec ⟨fun _ ↦ e, continuous_const.continuousOn, fun t ht ↦ ?_, rfl⟩
fun n ⟨Γ, cont, eqOn, Γ_0⟩ ↦ ?_
· rw [t_0, Set.Icc_self] at ht; cases ht; exact γ_0.symm
obtain ⟨x, t_sub⟩ := t_sub n
refine ⟨fun s ↦ if s ≤ t n then Γ s else (q x).invFun (γ s, (q x (Γ (t n))).2),
ContinuousOn.if (fun s hs ↦ ?_) (cont.mono fun _ h ↦ ?_) ?_, fun s hs ↦ ?_, ?_⟩
· have pΓtn : p (Γ (t n)) = γ (t n) := eqOn ⟨t_0 ▸ t_mono n.zero_le, le_rfl⟩
cases frontier_Iic_subset _ hs.2
rw [← pΓtn]
refine ((q x).symm_apply_mk_proj ?_).symm
rw [(q x).mem_source, pΓtn]
exact t_sub ⟨le_rfl, t_mono n.le_succ⟩
· rw [closure_le_eq continuous_id' continuous_const] at h; exact ⟨h.1.1, h.2⟩
· apply (q x).continuous_invFun.comp ((Continuous.Prod.mk_left _).comp γ.2).continuousOn
simp_rw [not_le, (q x).target_eq]; intro s h
exact ⟨t_sub ⟨closure_lt_subset_le continuous_const continuous_subtype_val h.2, h.1.2⟩, trivial⟩
· rw [Function.comp_apply]; split_ifs with h
exacts [eqOn ⟨hs.1, h⟩, (q x).proj_symm_apply' (t_sub ⟨le_of_not_le h, hs.2⟩)]
· dsimp only; rwa [if_pos (t_0 ▸ t_mono n.zero_le)]
noncomputable def liftPath : C(I,E) := (hp.exists_path_lifts γ e γ_0).choose
lemma liftPath_lifts : p ∘ hp.liftPath γ e γ_0 = γ := (hp.exists_path_lifts γ e γ_0).choose_spec.1
lemma liftPath_zero : hp.liftPath γ e γ_0 0 = e := (hp.exists_path_lifts γ e γ_0).choose_spec.2
variable {γ e}
lemma eq_liftPath_iff {Γ : I → E} : Γ = hp.liftPath γ e γ_0 ↔ Continuous Γ ∧ p ∘ Γ = γ ∧ Γ 0 = e :=
have lifts := hp.liftPath_lifts γ e γ_0
have zero := hp.liftPath_zero γ e γ_0
⟨fun h ↦ h ▸ ⟨(hp.liftPath γ e γ_0).2, lifts, zero⟩, fun ⟨Γ_cont, Γ_lifts, Γ_0⟩ ↦
FunLike.coe_fn_eq.mpr <| hp.eq_of_comp_eq ⟨Γ, Γ_cont⟩
(hp.liftPath γ e γ_0) (Γ_lifts ▸ lifts.symm) 0 (Γ_0 ▸ zero.symm)⟩
lemma eq_liftPath_iff' {Γ : C(I,E)} : Γ = hp.liftPath γ e γ_0 ↔ p ∘ Γ = γ ∧ Γ 0 = e := by
simp_rw [← FunLike.coe_fn_eq, eq_liftPath_iff, and_iff_right (ContinuousMap.continuous _)]
end path_lifting
section homotopy_lifting
variable (H : C(I × A, X)) (f : C(A, E)) (H_0 : ∀ a, H (0, a) = p (f a))
/-- The existence of `liftHomotopy` satisfying `liftHomotopy_lifts` and `liftHomotopy_zero` is
the homotopy lifting property for covering maps.
In other words, a covering map is a Hurewicz fibration. -/
@[simps] noncomputable def liftHomotopy : C(I × A, E) where
toFun ta := hp.liftPath (H.comp <| (ContinuousMap.id I).prodMk <| ContinuousMap.const I ta.2)
(f ta.2) (H_0 ta.2) ta.1
continuous_toFun := hp.isLocallyHomeomorph.continuous_lift hp.separatedMap H
(by ext ⟨t, a⟩; exact congr_fun (hp.liftPath_lifts _ _ _) t)
(by convert f.continuous with a; exact hp.liftPath_zero _ _ _)
fun a ↦ by dsimp only; exact (hp.liftPath _ _ _).2
lemma liftHomotopy_lifts : p ∘ hp.liftHomotopy H f H_0 = H :=
funext fun ⟨t, _⟩ ↦ congr_fun (hp.liftPath_lifts _ _ _) t
lemma liftHomotopy_zero (a : A) : hp.liftHomotopy H f H_0 (0, a) = f a := hp.liftPath_zero _ _ _
variable {H f}
lemma eq_liftHomotopy_iff (H' : I × A → E) : H' = hp.liftHomotopy H f H_0 ↔
(∀ a, Continuous (H' ⟨·, a⟩)) ∧ p ∘ H' = H ∧ ∀ a, H' (0, a) = f a := by
refine ⟨?_, fun ⟨H'_cont, H'_lifts, H'_0⟩ ↦ funext fun ⟨t, a⟩ ↦ ?_⟩
· rintro rfl; refine ⟨fun a ↦ ?_, hp.liftHomotopy_lifts H f H_0, hp.liftHomotopy_zero H f H_0⟩
simp_rw [liftHomotopy_apply]; exact (hp.liftPath _ _ <| H_0 a).2
· apply congr_fun ((hp.eq_liftPath_iff _).mpr ⟨H'_cont a, _, H'_0 a⟩) t
ext ⟨t, a⟩; exact congr_fun H'_lifts _
lemma eq_liftHomotopy_iff' (H' : C(I × A, E)) :
H' = hp.liftHomotopy H f H_0 ↔ p ∘ H' = H ∧ ∀ a, H' (0, a) = f a := by
simp_rw [← FunLike.coe_fn_eq, eq_liftHomotopy_iff]
exact and_iff_right fun a ↦ H'.2.comp (Continuous.Prod.mk_left a)
variable {f₀ f₁ : C(A, X)} {S : Set A} (F : f₀.HomotopyRel f₁ S)
open ContinuousMap in
noncomputable def liftHomotopyRel [PreconnectedSpace A]
{f₀' f₁' : C(A, E)} (he : ∃ a ∈ S, f₀' a = f₁' a)
(h₀ : p ∘ f₀' = f₀) (h₁ : p ∘ f₁' = f₁) : f₀'.HomotopyRel f₁' S :=
have F_0 : ∀ a, F (0, a) = p (f₀' a) := fun a ↦ (F.apply_zero a).trans (congr_fun h₀ a).symm
have rel : ∀ t, ∀ a ∈ S, hp.liftHomotopy F f₀' F_0 (t, a) = f₀' a := fun t a ha ↦ by
rw [liftHomotopy_apply, hp.const_of_comp _ _ t 0]
· apply hp.liftPath_zero
· intro t t'; simp_rw [← p.comp_apply, hp.liftPath_lifts]
exact (F.prop t a ha).trans (F.prop t' a ha).symm
{ toContinuousMap := hp.liftHomotopy F f₀' F_0
map_zero_left := hp.liftHomotopy_zero F f₀' F_0
map_one_left := by
obtain ⟨a, ha, he⟩ := he
simp_rw [toFun_eq_coe, ← curry_apply]
refine FunLike.congr_fun (hp.eq_of_comp_eq _ f₁' ?_ a <| (rel 1 a ha).trans he)
ext a; rw [h₁, Function.comp_apply, curry_apply]
exact (congr_fun (hp.liftHomotopy_lifts F f₀' _) (1, a)).trans (F.apply_one a)
prop' := rel }
/-- Two continuous maps from a preconnected space to the total space of a covering map
are homotopic relative to a set `S` if and only if their compositions with the covering map
are homotopic relative to `S`, assuming that they agree at a point in `S`. -/
theorem homotopicRel_iff_comp [PreconnectedSpace A] {f₀ f₁ : C(A, E)} {S : Set A}
(he : ∃ a ∈ S, f₀ a = f₁ a) : f₀.HomotopicRel f₁ S ↔
(ContinuousMap.comp ⟨p, hp.continuous⟩ f₀).HomotopicRel
(ContinuousMap.comp ⟨p, hp.continuous⟩ f₁) S :=
⟨Nonempty.map fun F ↦ F.compContinuousMap _, fun ⟨F⟩ ↦ ⟨hp.liftHomotopyRel F he rfl rfl⟩⟩
/-- Lifting two paths that are homotopic relative to {0,1}
starting from the same point upstairs result in the same other endpoint. -/
theorem liftPath_apply_one_eq_of_homotopicRel {γ₀ γ₁ : C(I, X)} (h : γ₀.HomotopicRel γ₁ {0,1})
(e : E) (h₀ : γ₀ 0 = p e) (h₁ : γ₁ 0 = p e) :
hp.liftPath γ₀ e h₀ 1 = hp.liftPath γ₁ e h₁ 1 := by
obtain ⟨H⟩ := h
have := hp.liftHomotopyRel (f₀' := hp.liftPath γ₀ e h₀) (f₁' := hp.liftPath γ₁ e h₁) H
⟨0, .inl rfl, by simp_rw [liftPath_zero]⟩ (liftPath_lifts _ _ _ _) (liftPath_lifts _ _ _ _)
rw [← this.eq_fst 0 (.inr rfl), ← this.eq_snd 0 (.inr rfl)]
/-- A covering map induces an injection on all Hom-sets of the fundamental groupoid,
in particular on the fundamental group. -/
lemma injective_path_homotopic_mapFn (e₀ e₁ : E) :
Function.Injective fun γ : Path.Homotopic.Quotient e₀ e₁ ↦ γ.mapFn ⟨p, hp.continuous⟩ := by
refine Quotient.ind fun γ₀ ↦ Quotient.ind fun γ₁ ↦ ?_
dsimp only
simp_rw [← Path.Homotopic.map_lift]
rw [@Quotient.eq _ (_), @Quotient.eq _ (_)]
exact (hp.homotopicRel_iff_comp ⟨0, .inl rfl, γ₀.source.trans γ₁.source.symm⟩).mpr
end homotopy_lifting
end IsCoveringMap
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment