module 1Lab.Equiv where
Equivalences🔗
The big idea of homotopy type theory is that isomorphic types can be
identified: the univalence
axiom. However, the notion of isomorphism
, is, in a sense, not “coherent”
enough to be used in the definition. For that, we need a coherent
definition of equivalence, where “being an equivalence” is a proposition.
To be more specific, what we need for a notion of equivalence to be “coherent” is:
Being an
isomorphism
implies being anequivalence
()Being an equivalence implies being an isomorphism (); Taken together with the first point we may summarise: “Being an equivalence and being an isomorphism are logically equivalent.”
Most importantly, being an equivalence must be a proposition.
The notion we adopt is due to Voevodsky: An equivalence is one that
has contractible
fibres
. Other definitions are possible (e.g.:
bi-invertible maps) — but
contractible fibres are “privileged” in Cubical Agda because for glueing to work, we need a proof
that equivalences have contractible fibres
anyway.
private
variable
: Level
ℓ₁ ℓ₂ : Type ℓ₁ A B
A homotopy fibre, fibre or preimage of a
function f
at a point y : B
is the collection
of all elements of A
that f
maps to
y
. Since many choices of name are possible, we settle on
the one that is shortest and most aesthetic: fibre
.
: (A → B) → B → Type _
fibre = Σ _ λ x → f x ≡ y fibre f y
A function f
is an equivalence if every one of its
fibres is contractible -
that is, for any element y
in the range, there is exactly
one element in the domain which f
maps to y
.
Using set-theoretic language, f
is an equivalence if the
preimage of every element of the codomain is a singleton.
record is-equiv (f : A → B) : Type (level-of A ⊔ level-of B) where
no-eta-equality
field
: (y : B) → is-contr (fibre f y)
is-eqv
open is-equiv public
_≃_ : ∀ {ℓ₁ ℓ₂} → Type ℓ₁ → Type ℓ₂ → Type _
_≃_ A B = Σ (A → B) is-equiv
: is-equiv {A = A} (λ x → x)
id-equiv .is-eqv y =
id-equiv (y , λ i → y)
contr λ { (y' , p) i → p (~ i) , λ j → p (~ i ∨ j) }
For Cubical Agda, the type of equivalences is distinguished, so we have to make a small wrapper to match the interface Agda expects. This is the geometric definition of contractibility, in terms of partial elements and extensibility.
{-# BUILTIN EQUIV _≃_ #-}
{-# BUILTIN EQUIVFUN fst #-}
: ∀ {a b} (A : Type a) (B : Type b)
is-eqv' → (w : A ≃ B) (a : B)
→ (ψ : I)
→ (p : Partial ψ (fibre (w .fst) a))
→ fibre (w .fst) a [ ψ ↦ p ]
(f , is-equiv) a ψ u0 = inS (
is-eqv' A B (∂ ψ) λ where
hcomp (ψ = i0) → c .centre
i (ψ = i1) → c .paths (u0 1=1) i
i (i = i0) → c .centre)
i where c = is-equiv .is-eqv a
{-# BUILTIN EQUIVPROOF is-eqv' #-}
is-equiv is propositional🔗
A function can be an equivalence in at most one way. This follows
from propositions being closed under dependent products, and is-contr
being a proposition.
module _ where private
: (f : A → B) → is-prop (is-equiv f)
is-equiv-is-prop .is-eqv p = is-contr-is-prop (x .is-eqv p) (y .is-eqv p) i is-equiv-is-prop f x y i
Even though the proof above works, we use the direct cubical proof in
this <details>
tag (lifted from the Cubical Agda
library) in the rest of the development for efficiency concerns.
: (f : A → B) → is-prop (is-equiv f)
is-equiv-is-prop .is-eqv y =
is-equiv-is-prop f p q i let p2 = p .is-eqv y .paths
= q .is-eqv y .paths
q2 in contr (p2 (q .is-eqv y .centre) i) λ w j → hcomp (∂ i ∨ ∂ j) λ where
(i = i0) → p2 w j
k (i = i1) → q2 w (j ∨ ~ k)
k (j = i0) → p2 (q2 w (~ k)) i
k (j = i1) → w
k (k = i0) → p2 w (i ∨ j) k
Isomorphisms from equivalences🔗
For this section, we need a definition of isomorphism. This is the same as ever! An isomorphism is a function that has a two-sided inverse. We first define what it means for a function to invert another on the left and on the right:
: (B → A) → (A → B) → Type _
is-left-inverse = (x : _) → g (f x) ≡ x
is-left-inverse g f
: (B → A) → (A → B) → Type _
is-right-inverse = (x : _) → f (g x) ≡ x is-right-inverse g f
A proof that a function is an isomorphism consists of a function in the other direction, together with homotopies exhibiting as a left- and right- inverse to .
record is-iso (f : A → B) : Type (level-of A ⊔ level-of B) where
no-eta-equality
constructor iso
field
: B → A
inv : is-right-inverse inv f
rinv : is-left-inverse inv f
linv
: is-iso inv
inverse = f
inv inverse = linv
rinv inverse = rinv
linv inverse
: ∀ {ℓ₁ ℓ₂} → Type ℓ₁ → Type ℓ₂ → Type _
Iso = Σ (A → B) is-iso Iso A B
Any function that is an equivalence is an isomorphism:
: {f : A → B} → is-equiv f → B → A
equiv→inverse = eqv .is-eqv y .centre .fst
equiv→inverse eqv y
equiv→counit: ∀ {f : A → B} (eqv : is-equiv f) x → f (equiv→inverse eqv x) ≡ x
= eqv .is-eqv y .centre .snd
equiv→counit eqv y
equiv→unit: ∀ {f : A → B} (eqv : is-equiv f) x → equiv→inverse eqv (f x) ≡ x
{f = f} eqv x i = eqv .is-eqv (f x) .paths (x , refl) i .fst
equiv→unit
equiv→zig: ∀ {f : A → B} (eqv : is-equiv f) x
→ ap f (equiv→unit eqv x) ≡ equiv→counit eqv (f x)
{f = f} eqv x i j = hcomp (∂ i ∨ ∂ j) λ where
equiv→zig (i = i0) → f (equiv→unit eqv x j)
k (i = i1) → equiv→counit eqv (f x) (j ∨ ~ k)
k (j = i0) → equiv→counit eqv (f x) (i ∧ ~ k)
k (j = i1) → f x
k (k = i0) → eqv .is-eqv (f x) .paths (x , refl) j .snd i
k
equiv→zag: ∀ {f : A → B} (eqv : is-equiv f) x
→ ap (equiv→inverse eqv) (equiv→counit eqv x)
(equiv→inverse eqv x)
≡ equiv→unit eqv {f = f} eqv b =
equiv→zag (λ b → ap g (ε b) ≡ η (g b)) (ε b) (helper (g b)) where
subst = equiv→inverse eqv
g = equiv→counit eqv
ε = equiv→unit eqv
η
: ∀ a → ap g (ε (f a)) ≡ η (g (f a))
helper = hcomp (∂ i ∨ ∂ j) λ where
helper a i j (i = i0) → g (ε (f a) (j ∨ ~ k))
k (i = i1) → η (η a (~ k)) j
k (j = i0) → g (equiv→zig eqv a (~ i) (~ k))
k (j = i1) → η a (i ∧ ~ k)
k (k = i0) → η a (i ∧ j)
k
: {f : A → B} → is-equiv f → is-iso f
is-equiv→is-iso .inv (is-equiv→is-iso eqv) = equiv→inverse eqv is-iso
We can get an element of x
from the proof that
f
is an equivalence - it’s the point of A
mapped to y
, which we get from centre of contraction for
the fibres of f
over y
.
.rinv (is-equiv→is-iso eqv) y =
is-iso.is-eqv y .centre .snd eqv
Similarly, that one fibre gives us a proof that the function above is
a right inverse to f
.
.linv (is-equiv→is-iso {f = f} eqv) x i =
is-iso.is-eqv (f x) .paths (x , refl) i .fst eqv
The proof that the function is a left inverse comes from the
fibres of f
over y
being contractible. Since
we have a fibre - namely, f
maps x
to
f x
by refl
- we can
get any other we want!
Equivalences from isomorphisms🔗
Any isomorphism can be upgraded into an equivalence, in a way that
preserves the function
,
its inverse
,
and the proof
that
is a right inverse to
.
We can not preserve everything, though, as is usual when making
something “more coherent”. Furthermore, if everything was preserved,
is-iso
would be a proposition, and
this is provably not the
case.
The argument presented here is done directly in cubical style, but a less direct proof is also available, by showing that every isomorphism is a half-adjoint equivalence, and that half-adjoint equivalences have contractible fibres.
module _ {f : A → B} (i : is-iso f) where
open is-iso i renaming ( inv to g
; rinv to s
; linv to t
)
Suppose, then, that
and
,
and we’re given witnesses
and
(named for section and retraction)
that
and
are inverses. We want to show that, for any
,
the fibre
of
over
is contractible. It suffices to show that the fibre is propositional,
and that it is inhabited.
We begin with showing that the fibre over
is propositional, since that’s the harder of the two arguments. Suppose
that we have
,
,
,
and
as below; Note that
and
are fibres of
over
.
What we need to show is that we have some
and
over
.
private module _ (y : B) (x0 x1 : A) (p0 : f x0 ≡ y) (p1 : f x1 ≡ y) where
As an intermediate step in proving that , we must show that - without this, we can’t even state that and are identified, since they live in different types! To this end, we will build , parts of which will be required to assemble the overall proof that .
We’ll detail the construction of
;
for
,
the same method is used. We want to construct a line, which we
can do by exhibiting that line as the missing face in a square.
We have equations
(refl
),
(the action of g
on p0
), and
by the assumption that
is a right inverse to
.
Diagrammatically, these fit together into a square:
The missing line in this square is
.
Since the inside (the filler
) will be useful to us later, we also
give it a name:
.
: g y ≡ x0
π₀ = hcomp (∂ i) λ where
π₀ i (i = i0) → g y
k (i = i1) → t x0 k
k (k = i0) → g (p0 (~ i))
k
: Square (ap g (sym p0)) refl (t x0) π₀
θ₀ = hfill (∂ i) j λ where
θ₀ i j (i = i0) → g y
k (i = i1) → t x0 k
k (k = i0) → g (p0 (~ i)) k
Since the construction of is analogous, I’ll simply present the square. We correspondingly name the missing face and the filler .
: g y ≡ x1
π₁ = hcomp (∂ i) λ where
π₁ i (i = i0) → g y
j (i = i1) → t x1 j
j (j = i0) → g (p1 (~ i))
j
: Square (ap g (sym p1)) refl (t x1) π₁
θ₁ = hfill (∂ i) j λ where
θ₁ i j (i = i0) → g y
j (i = i1) → t x1 j
j (j = i0) → g (p1 (~ i)) j
Joining these paths by their common face, we obtain . This square also has a filler, connecting and over the line .
: x0 ≡ x1
π = hcomp (∂ i) λ where
π i (j = i0) → g y
j (i = i0) → π₀ j
j (i = i1) → π₁ j j
This concludes the construction of , and thus, the 2D part of the proof. Now, we want to show that over a path induced by . This is a square with a specific boundary, which can be built by constructing an appropriate open cube, where the missing face is that square. As an intermediate step, we define to be the filler for the square above.
: Square refl π₀ π₁ π
θ = hfill (∂ i) j λ where
θ i j (i = i1) → π₁ k
k (i = i0) → π₀ k
k (k = i0) → g y k
Observe that we can coherently alter to get below, which expresses that and are identified.
: Square (ap (g ∘ f) π) (ap g p0) (ap g p1) refl
ι = hcomp (∂ i ∨ ∂ j) λ where
ι i j (k = i0) → θ i (~ j)
k (i = i0) → θ₀ (~ j) (~ k)
k (i = i1) → θ₁ (~ j) (~ k)
k (j = i0) → t (π i) (~ k)
k (j = i1) → g y k
This composition can be visualised as the red (front) face
in the diagram below. The back face is
,
i.e. (θ i (~ j))
in the code. Similarly, the
(bottom) face is g y
, the
(top) face is t (π i) (~ k)
, and similarly for
(left) and
(right).
The fact that only appears as can be understood as the diagram above being upside-down. Indeed, and in the boundary of (the inner, blue face) are inverted when their types are considered. We’re in the home stretch: Using our assumption , we can cancel all of the s in the diagram above to get what we wanted: .
: Square (ap f π) p0 p1 refl
sq1 = hcomp (∂ i ∨ ∂ j) λ where
sq1 i j (i = i0) → s (p0 j) k
k (i = i1) → s (p1 j) k
k (j = i0) → s (f (π i)) k
k (j = i1) → s y k
k (k = i0) → f (ι i j) k
The composition above can be visualised as the front (red) face in the cubical diagram below. Once more, left is , right is , up is , and down is .
Putting all of this together, we get that . Since there were no assumptions on any of the variables under consideration, this indeed says that the fibre over is a proposition for any choice of .
: (x0 , p0) ≡ (x1 , p1)
is-iso→fibre-is-prop .fst = π i
is-iso→fibre-is-prop i .snd = sq1 i is-iso→fibre-is-prop i
Since the fibre over is inhabited by , we get that any isomorphism has contractible fibres:
: is-equiv f
is-iso→is-equiv .is-eqv y .centre .fst = g y
is-iso→is-equiv .is-eqv y .centre .snd = s y
is-iso→is-equiv .is-eqv y .paths z =
is-iso→is-equiv (g y) (fst z) (s y) (snd z) is-iso→fibre-is-prop y
Applying this to the Iso
and
_≃_
pairs, we can turn any isomorphism into a coherent equivalence.
: ∀ {ℓ₁ ℓ₂} {A : Type ℓ₁} {B : Type ℓ₂}
Iso→Equiv → Iso A B
→ A ≃ B
(f , is-iso) = f , is-iso→is-equiv is-iso Iso→Equiv
A helpful lemma: Any function between contractible types is an equivalence:
: ∀ {ℓ₁ ℓ₂} {A : Type ℓ₁} {B : Type ℓ₂}
is-contr→is-equiv → is-contr A → is-contr B → {f : A → B}
→ is-equiv f
= is-iso→is-equiv f-is-iso where
is-contr→is-equiv cA cB : is-iso _
f-is-iso .inv f-is-iso _ = cA .centre
is-iso.rinv f-is-iso _ = is-contr→is-prop cB _ _
is-iso.linv f-is-iso _ = is-contr→is-prop cA _ _
is-iso
: ∀ {ℓ₁ ℓ₂} {A : Type ℓ₁} {B : Type ℓ₂}
is-contr→≃ → is-contr A → is-contr B → A ≃ B
= (λ _ → cB .centre) , is-iso→is-equiv f-is-iso where
is-contr→≃ cA cB : is-iso _
f-is-iso .inv f-is-iso _ = cA .centre
is-iso.rinv f-is-iso _ = is-contr→is-prop cB _ _
is-iso.linv f-is-iso _ = is-contr→is-prop cA _ _
is-iso
: ∀ {ℓ} {A : Type ℓ} → is-contr A → A ≃ ⊤
is-contr→≃⊤ = is-contr→≃ c ⊤-is-contr is-contr→≃⊤ c
…and so is any function into an empty type:
: ∀ {ℓ} {A : Type ℓ} (f : A → ⊥) → is-equiv f
¬-is-equiv .is-eqv ()
¬-is-equiv f
: ∀ {ℓ} {A : Type ℓ} → ¬ A → A ≃ ⊥
is-empty→≃⊥ = _ , ¬-is-equiv ¬a is-empty→≃⊥ ¬a
Any involution is an equivalence:
: ∀ {ℓ} {A : Type ℓ} {f : A → A} → (∀ a → f (f a) ≡ a) → is-equiv f
is-involutive→is-equiv = is-iso→is-equiv (iso _ inv inv) is-involutive→is-equiv inv
Closure properties🔗
We can prove a 2-out-of-3 property for equivalences: given two functions , , if any two of , and is an equivalence, then so is the third.
module _
{ℓ ℓ₁ ℓ₂} {A : Type ℓ} {B : Type ℓ₁} {C : Type ℓ₂}
{f : A → B} {g : B → C}
where
∙-is-equiv: is-equiv f → is-equiv g
→ is-equiv (λ x → g (f x))
equiv-cancell: is-equiv g → is-equiv (λ x → g (f x))
→ is-equiv f
equiv-cancelr: is-equiv f → is-equiv (λ x → g (f x))
→ is-equiv g
In particular, any left or right inverse of an equivalence is an equivalence:
left-inverse→equiv: ∀ {ℓ ℓ'} {A : Type ℓ} {B : Type ℓ'} {f : A → B} {g : B → A}
→ is-left-inverse g f
→ is-equiv f → is-equiv g
= equiv-cancelr ef
left-inverse→equiv linv ef (subst is-equiv (sym (funext linv)) id-equiv)
right-inverse→equiv: ∀ {ℓ ℓ'} {A : Type ℓ} {B : Type ℓ'} {f : A → B} {g : B → A}
→ is-right-inverse g f
→ is-equiv f → is-equiv g
= equiv-cancell ef
right-inverse→equiv rinv ef (subst is-equiv (sym (funext rinv)) id-equiv)
Equivalence reasoning🔗
To make composing equivalences more intuitive, we implement operators to do equivalence reasoning in the same style as equational reasoning.
: ∀ {ℓ} {A : Type ℓ} → A ≃ A
id≃ = id , id-equiv
id≃
_∙e_ : ∀ {ℓ ℓ₁ ℓ₂} {A : Type ℓ} {B : Type ℓ₁} {C : Type ℓ₂}
→ A ≃ B → B ≃ C → A ≃ C
_∙e_ (f , ef) (g , eg) = (λ x → g (f x)) , ∙-is-equiv ef eg
_e⁻¹ : ∀ {ℓ ℓ₁} {A : Type ℓ} {B : Type ℓ₁}
→ A ≃ B → B ≃ A
_e⁻¹ eqv = Iso→Equiv ( equiv→inverse (eqv .snd)
record { inv = eqv .fst
, ; rinv = equiv→unit (eqv .snd)
; linv = equiv→counit (eqv .snd)
})
The proofs that equivalences are closed under composition assemble nicely into transitivity operators resembling equational reasoning:
: ∀ {ℓ ℓ₁ ℓ₂} (A : Type ℓ) {B : Type ℓ₁} {C : Type ℓ₂}
≃⟨⟩-syntax → B ≃ C → A ≃ B → A ≃ C
= f ∙e g
≃⟨⟩-syntax A g f
infixr 2 ≃⟨⟩-syntax
syntax ≃⟨⟩-syntax x q p = x ≃⟨ p ⟩ q
_≃˘⟨_⟩_ : ∀ {ℓ ℓ₁ ℓ₂} (A : Type ℓ) {B : Type ℓ₁} {C : Type ℓ₂}
→ B ≃ A → B ≃ C → A ≃ C
= f e⁻¹ ∙e g
A ≃˘⟨ f ⟩ g
_≃⟨⟩_ : ∀ {ℓ ℓ₁} (A : Type ℓ) {B : Type ℓ₁} → A ≃ B → A ≃ B
= x≡y
x ≃⟨⟩ x≡y
_≃∎ : ∀ {ℓ} (A : Type ℓ) → A ≃ A
= id≃
x ≃∎
infixr 2 _≃⟨⟩_ _≃˘⟨_⟩_
infix 3 _≃∎
Propositional extensionality (redux)🔗
The following observation is not very complex, but it is incredibly useful: Equivalence of propositions is the same as biimplication.
module
_ {ℓ ℓ'} {P : Type ℓ} {Q : Type ℓ'}
(pprop : is-prop P) (qprop : is-prop Q)
where
: (f : P → Q) → (Q → P) → is-equiv f
biimp-is-equiv .is-eqv y .centre .fst = g y
biimp-is-equiv f g .is-eqv y .centre .snd = qprop (f (g y)) y
biimp-is-equiv f g .is-eqv y .paths (p' , path) = Σ-pathp
biimp-is-equiv f g (pprop (g y) p')
(is-prop→squarep (λ _ _ → qprop) _ _ _ _)
: (P → Q) → (Q → P) → P ≃ Q
prop-ext .fst = p→q
prop-ext p→q q→p .snd = biimp-is-equiv p→q q→p prop-ext p→q q→p