Lecture 2-9: Contractible Maps

In this Lecture we prove two crucial facts about equivalences:

  • Being an equivalence is a proposition about a map, rather than extra structure. That is:

    isProp-isEquiv : (f : A → B) → isProp (isEquiv f)
    
  • The function path→equiv is an equivalence, completing the proof of the univalence principle that we started in Lecture 2-6:

    univalence : (A ≡ B) ≃ (A ≃ B)
    

To prove each of these facts, it turns out to be easiest to detour through an alternative definition of equivalence: the notion of “contractible map”.

Being an Isomorphism is Not Propositional

First, why have we forced ourselves to put up with this clunky notion of equivalence the whole time? Recall that an isomorphism is a map with a single map going the other way that is both a section and a retract.

record isIso {A : Type } {B : Type ℓ'} (f : A  B) : Type (ℓ-max  ℓ') where
  constructor isIsoData
  field
    isoInv : B  A
    sectionProof : isSection f isoInv
    retractProof : isRetract f isoInv

open isIso

Sadly, this type is not always a proposition. This feels strange, because in ordinary set-based mathematics, this defect is impossible to see:

Iso-inv-unique : {f : A  B}  (i₁ i₂ : isIso f)
   (b : B)  i₁ .isoInv b  i₂ .isoInv b
Iso-inv-unique {f = f} i₁ i₂ b
  = i₁ .isoInv b                  ≡⟨ sym (i₂ .retractProof (i₁ .isoInv b)) 
    i₂ .isoInv (f (i₁ .isoInv b)) ≡⟨ ap (i₂ .isoInv) (i₁ .sectionProof b) 
    i₂ .isoInv b 

isSet→isProp-isIso : {f : A  B}
   isSet A  isSet B
   isProp (isIso f)
isSet→isProp-isIso isA isB iso₁ iso₂ i .isoInv b = Iso-inv-unique iso₁ iso₂ b i
isSet→isProp-isIso {f = f} isA isB iso₁ iso₂ i .sectionProof b
  = isB _ _
    (transport-filler  j  f (Iso-inv-unique iso₁ iso₂ b j)  b)     (iso₁ .sectionProof b) i)
    (transport-filler  j  f (Iso-inv-unique iso₁ iso₂ b (~ j))  b) (iso₂ .sectionProof b) (~ i))
    i
isSet→isProp-isIso {f = f} isA isB iso₁ iso₂ i .retractProof a
  = isA _ _
    (transport-filler  j  Iso-inv-unique iso₁ iso₂ (f a) j  a)     (iso₁ .retractProof a) i)
    (transport-filler  j  Iso-inv-unique iso₁ iso₂ (f a) (~ j)  a) (iso₂ .retractProof a) (~ i))
    i

In the world of higher types, however, the information contained in isSection and isRetract could hold extra data.

Consider the type of ways to show that the identity function X → X is an isomorphism: that is, the type of functions f : X → X such that s : (x : X) → f x ≡ x and r : (x : X) → f x ≡ x. By gluing these together, we can get a path x ≡ x for any x : X.

isIso→center : isIso idfun  (x : A)  (x  x)
isIso→center i x = sym (i .sectionProof x)  i .retractProof x

For sets this poses no problem, but if X is a higher type, there may be lots of non-equal elements of (x : A) → (x ≡ x). Indeed, we already see this for the simplest higher type, .

refl≢rotate-loop : ¬ ((λ _  refl)  rotate-loop)
-- Exercise:
-- refl≢rotate-loop p = {!!}

But now, here are two ways of showing that the identity function on is an isomorphism.

S¹-refl-iso : isIso (idfun {A = })
S¹-refl-iso .isoInv = idfun
S¹-refl-iso .sectionProof = λ _  refl
S¹-refl-iso .retractProof = λ _  refl

S¹-rotate-loop-iso : isIso (idfun {A = })
S¹-rotate-loop-iso .isoInv = idfun
S¹-rotate-loop-iso .sectionProof = λ _  refl
S¹-rotate-loop-iso .retractProof = rotate-loop

If isIso idfun were a proposition, there would be a path between these. This would imply that (λ _ → refl) ≡ rotate-loop, which we’ve just shown cannot be.

¬isProp-isIso : ¬ isProp (isIso (idfun {A = }))
-- Exercise:
-- ¬isProp-isIso p = refl≢rotate-loop {!!}

Contractible Maps

Our goal is to show that, by contrast, isEquiv is a proposition. As explained in the introduction, we will do this by detouring through the notion of “contractible map”.

In ordinary mathematics, a bijection between sets and is a function where for every , there is a unique so that . We can translate this definition directly into type theory, using isContr as our interpretation of “unique existence”.

isBijection : {A : Type }  {B : Type ℓ'}
   (f : A  B)
   Type (ℓ-max  ℓ')
isBijection {A = A} {B = B} f 
  = (b : B)  isContr (Σ[ a  A ] (b  f a))

That type inside isContr comes up a lot, so it is given a name. The fiber of a function over an element is the inverse image of that element, so all elements of whose image under is equal to . In homotopy theory, this would be more accurately called the “homotopy fiber”.

fiber : {A : Type }  {B : Type ℓ'}
   (f : A  B)  (y : B)
   Type (ℓ-max  ℓ')
fiber {A = A} f b = Σ[ a  A ] (b  f a)

Then, a bijection is exactly a map that has contractible fibers (just unfolding the definitions).

_ = λ { ℓ' : Level} {A : Type } {B : Type ℓ'} (f : A  B)
   test-identical 
    (isBijection f)
    ((y : B)  isContr (fiber f y))

This shape of definition comes up fairly often. We have a property of types (in this case contractibility), and we use it to define a property of maps by testing that property is satisfied by all the fibers. So, looking forward to other definitions of this sort, we’re going to rename the property of being a bijection to being a contractible map.

isContractibleMap : {A : Type } {B : Type ℓ'} (f : A  B)  Type (ℓ-max  ℓ')
isContractibleMap = isBijection

Now, because a type being contractible is a proposition, a map being contractible is also a proposition.

isProp-isContractibleMap : (f : A  B)  isProp (isContractibleMap f)
-- Exercise:
-- isProp-isContractibleMap f = {!!}

Equivalences are Contractible Maps

It is easy to show that any contractible map is an equivalence. Each fiber is contractible, in particular, each fiber has a point, and we can use these to define an inverse function B → A.

isContractibleMap→isEquiv : {f : A  B}  isContractibleMap f  isEquiv f
isContractibleMap→isEquiv {A = A} {B = B} {f = f} cf
  = packIsEquiv inv to-fro inv fro-to
  where
    inv : B  A
--  Exercise:
--  inv b = {!!}

    to-fro : isSection f inv
--  Exercise: (Hint: Use the path provided by the fiber.)
--  to-fro = {!!}

    fro-to : isRetract f inv
--  Exercise: (Hint: Use the path provided by isContr.)
--  fro-to = {!!}

We can also show that any equivalence is a contractible map, but the process is more involved.

Here’s the setup: starting with an equivalence e : A ≃ B, we are going to show that the provided section of e is a contractible map, and then use this to show that the original map underlying e is contractible.

It will be easy to produce a point of each fiber, so with isProp-with-point→isContr in mind, our immediate goal is to show that each fiber of e .proof .section .map is a proposition.

Fix an element y : A, and two elements of the fiber over it, (x₀, p₀) and (x₁ , p₁):

private module _ where
 module _
    (e : A  B) (y : A)
    (x₀ : B) (p₀ : y  e .proof .section .map x₀)
    (x₁ : B) (p₁ : y  e .proof .section .map x₁) where

Our goal is to give a path between these pairs. Let’s temporarily give shorter names to the components of the equivalence e.

  private
    f : A  B
    f = e .map

    g : B  A
    g = e .proof .section .map

    s : isSection f g
    s = e .proof .section .proof

    g' : B  A
    g' = e .proof .retract .map

    r : isRetract f g'
    r = e .proof .retract .proof

First we need to produce a path x₀ ≡ x₁ to use in the first component. This is easy enough, using the fact that g is a section of f.

  path₀ : f y  x₀
  -- Exercise:
  -- path₀ = {!!}

  path₁ : f y  x₁
  -- Exercise:
  -- path₁ = {!!}

  path : x₀  x₁
  -- Exercise:
  -- path₂ = {!!} ∙∙ refl ∙∙ {!!}

You’ll see very shortly why defining path in this symmetrical way using ∙∙ is beneficial.

Now, a path between the points x₀ and x₁ in B is not enough, we also need a path-over showing that the paths p₀ and p₁ are equal. That is, we need a square

square : Square refl (ap g path) p₀ p₁

First, we’ll compose the following cube, where the top face is nearly what we want:

                    f p₁
           f y  — — — — — — > f (g x₁)
          / ^                 / ^
        /   |               /   |
      /     | f p₀        /     |
   f y  — — — — — — > f (g x₀)  | s x₁
    ^       |           ^       |                    ^   j
    |       |           |       |                  k | /
    |       |           | s x₀  |                    ∙ — >
    |       |           |       |                      i
    |      f y  — — — — | — — > x₁
    |     /       path₁ |     /
    |   /               |   / path
    | /                 | /
   f y  — — — — — — — > x₀
            path₀
  square-f : Square (ap f refl) (ap f (ap g path)) (ap f p₀) (ap f p₁)
  square-f i j = hcomp ( i   j) faces
    where faces : (k : I)  Partial ( i   j  ~ k) B
          -- Exercise:
          -- faces k = {!!}

We just need to kill the extra ap f on all the sides of this square. For this, we use the fact that g' is a retract of f: we’ll add an extra g', then use r to cancel both g' and f out.

  square : Square refl (ap g path) p₀ p₁
  -- Exercise: (Hint: `homotopy-Square` is nice here.)
  -- square = {!!}

So combining path and square, we get the path of between pairs that we wanted.

  lemEquiv : (x₀ , p₀)  (x₁ , p₁)
  -- Exercise:
  -- lemEquiv i = {!!}

The hard work is now done: every fiber of the section map is a proposition. And we can easily find a point of each fiber, so every fiber is contractible.

isEquiv→secIsContractibleMap : (e : A  B)  isContractibleMap (e .proof .section .map)
-- Exercise:
-- isEquiv→secIsContractibleMap e y = isProp-with-point→isContr {!!} {!!}

We usually want to know that the actual map underlying an equivalence is contractible, rather than the section map. To get this, we just invert the equivalence and apply what we’ve just proven, because the section in the inverse is exactly the original map!

isEquiv→isContractibleMap : {f : A  B}  isEquiv f  isContractibleMap f
isEquiv→isContractibleMap isE = isEquiv→secIsContractibleMap (invEquiv (equiv _ isE))

Being an Equivalence is Propositional

We now turn to the first of the goals that we listed up at the top: showing that isEquiv is always a proposition. We’ll do this by showing that if we do have an element of isEquiv f, then in fact isEquiv f is contractible. Our definition of isEquiv is as the pair of a section and a retract, so the way we’ll do it is showing those two pieces are contractible separately.

First, some generalities about equivalences. These can both be proven by defining an equivalence directly.

isEquiv→isEquiv-postComp : {f : A  B}  isEquiv f  isEquiv  (d : C  A)  f  d)
-- Exercise:
-- isEquiv→isEquiv-postComp e = {!!}

sectionOf≃fiber : (f : A  B)  SectionOf f  (fiber  (d : B  A)  f  d) idfun)
sectionOf≃fiber {A = A} {B = B} f = inv→equiv fun inv  _  refl)  _  refl)
  where
    fun : SectionOf f  (fiber  (d : B  A)  f  d) idfun)
    fun s .fst = s .map 
    fun s .snd i b = s .proof b (~ i)

    inv : (fiber  (d : B  A)  f  d) idfun)  SectionOf f 
    inv (g , s) .map = g 
    inv (g , s) .proof b i = s (~ i) b

The strategy should be clear: sectionOf f is equivalent to one of the fibers of an equivalence, and because any equivalence is a contractible map, that fiber is contractible. Put it together:

isEquiv→isContrSectionOf : {f : A  B}  isEquiv f  isContr (SectionOf f)
-- Exercise:
-- isEquiv→isContrSectionOf {f = f} isE = {!!}

Caution: We have just shown that if f is an equivalence, its type of sections is contractible. It is definitely not the case in general that the type of sections of a map is contractible.

A symmetrical argument works for the retract, feel free to copy-paste as appropriate.

isEquiv→isEquiv-preComp  : {f : A  B}  isEquiv f  isEquiv  (d : B  C)  d  f)
-- Exercise:
-- isEquiv→isEquiv-preComp e = {!!}

retractOf≃fiber : (f : A  B)  RetractOf f  (fiber  (d : B  A)  d  f) idfun)
retractOf≃fiber {A = A} {B = B} f = inv→equiv fun inv  _  refl)  _  refl)
  where
    fun : RetractOf f  (fiber  (d : B  A)  d  f) idfun)
    fun s .fst = s .map
    fun s .snd i b = s .proof b (~ i)

    inv : (fiber  (d : B  A)  d  f) idfun)  RetractOf f 
    inv (g , s) .map = g
    inv (g , s) .proof b i = s (~ i) b

isEquiv→isContrRetractOf : {f : A  B}  isEquiv f  isContr (RetractOf f)
-- Exercise:
-- isEquiv→isContrRetractOf {f = f} isE = {!!}

Now just glue them together! (Using explode-isEquiv to pull it apart will be helpful here).

isProp-isEquiv : (f : A  B)  isProp (isEquiv f)
-- Exercise:
-- isProp-isEquiv f = with-point-isContr→isProp {!!}

As we showed in ≡-in-subtype at the end of Lecture 2-7, paths in subtypes can be calculated in the underlying type. Since the type A ≃ B of equivalences is a subtype of the type of functions A → B (because we have just shown isEquiv is a proposition), we can compute paths between equivalences on their underlying functions.

equiv≡ : {e f : A  B}  (h : e .map  f .map)  e  f
-- Exercise:
-- equiv≡ {e = e} {f = f} h = {!!}

We knew already that univalence ua has a retract au. But we can now use equiv≡ to show that au is also a section, and so ua is an equivalence.

au-ua : (e : A  B)  au (ua e)  e
-- Exercise: (Hint: ua-comp)
-- au-ua e = {!!}

And finally prove univalence in all its glory:

univalence : (A  B)  (A  B)
univalence = inv→equiv au ua au-ua ua-au

References and Further Reading

  • Talk Slides by Martín Hötzel Escardó on subtleties in the statement of Univalence.