hcomp is a primitive operation in cubical type theory that completes the lid of an incomplete cube, given the bottom face and some number of side faces.

NOTE

All the code samples in this document are written in and checked for correctness by Agda, a dependently typed programming language that implements cubical type theory. Many of the names and symbols in the names are clickable links that take you to where that symbol is defined. Below are all of the imports required for this page to work.

Imports for Agda
{-# OPTIONS --cubical #-}
module 2024-09-18-hcomp.index where
open import Cubical.Foundations.Prelude hiding (isProp→isSet)
open import Cubical.Foundations.Equiv.Base
open import Cubical.Foundations.Isomorphism
open import Cubical.Core.Primitives
open import Cubical.HITs.Susp.Base
open import Cubical.HITs.S1.Base
open import Data.Bool.Base hiding (_∧_; _∨_)

Two-dimensional case

In two dimensions, hcomp\mathsf{hcomp} can be understood as the double-composition operation. Given three paths p:xyp : x \equiv y, q:yzq : y \equiv z and r:xwr : x \equiv w, it will return a path representing the composed path ((r)pq):(wz)((\sim r) \cdot p \cdot q) : (w ≡ z).

”Single” composition (between two paths rather than three) is typically implemented as a double composition with the left leg as refl\mathsf{refl}. Without the double composition, this looks like:

path-comp : {A : Type} {x y z : A}  x  y  y  z  x  z
path-comp {x = x} p q i =
  let u = λ j  λ where
    (i = i0)  x
    (i = i1)  q j
  in hcomp u (p i)

The (i = i0) case in this case is reflx(j)\mathsf{refl}_x(j) which is definitionally equal to xx.

Example: isProp(A)isSet(A)\mathsf{isProp}(A) \rightarrow \mathsf{isSet}(A)

Suppose we want to prove that all mere propositions (h-level 1) are sets (h-level 2). This result exists in the cubical library, but let’s go over it here.

isProp→isSet : {A : Type}  isProp A  isSet A
isProp→isSet {A} f = goal where
  goal : (x y : A)  (p q : x  y)  p  q
  goal x y p q j i = -- ...

We’re given a type AA, some proof that it’s a mere proposition, let’s call it f:isProp(A)f : \mathsf{isProp}(A).

Now let’s construct an hcomp. In a set, we’d want paths pp and qq between the same points xx and yy to be identified. Suppose pp and qq operate over the same dimension, ii. If we want to find a path between pp and qq, we’ll want another dimension. Let’s call this jj. So essentially, the final goal is a square with these boundaries:

Our goal is to fill this square in. Well, what is a square but a missing face in a 3-dimensional cube? Let’s draw out what this cube looks like, and then have hcomp\mathsf{hcomp} give us the top face. Before getting started, let’s familiarize ourselves with the dimensions we’re working with:

  • ii is the left-right dimension, the one that pp and qq work over
  • jj is the dimension of our final path between pqp \equiv q. Note that this is the first argument, because our top-level ask was pqp \equiv q.
  • Let’s introduce a dimension kk for doing our hcomp\mathsf{hcomp}

We can map both p(i)p(i) and q(i)q(i) down to a square that has xx on all corners and reflx\mathsf{refl}_x on all sides.

Let’s start with the left and right faces. The left is represented by the face lattice (i=i0)(i = \mathsf{i0}). This is a sub-polyhedra of the main cube since it only represents one face. The only description of this face is that it contains all the points such that the ii dimension is equal to i0\mathsf{i0}. Similarly, the right face can be represented by (i=i1)(i = \mathsf{i1}).

To fill this face, we must find some value in AA that satisfies all the boundary conditions. Fortunately, we’re given something that can produce values in AA: the proof f:isProp(A)f : \mathsf{isProp}(A) that we are given!

ff tells us that xx and yy are the same, so f(x,x):xxf(x, x) : x \equiv x and f(x,y):xyf(x, y) : x \equiv y. This means we can define the left face as f(x,x)(k)f(x, x)(k) and the right face as f(x,y)(k)f(x, y)(k). (Remember, kk is the direction going from bottom to top)

We can write this down as a mapping:

  • (i=i0)f(x,x)(k)(i = \mathsf{i0}) \mapsto f(x, x)(k)
  • (i=i1)f(x,y)(k)(i = \mathsf{i1}) \mapsto f(x, y)(k)

The same logic applies to the front face (j=i0)(j = \mathsf{i0}) and back face (j=i1)(j = \mathsf{i1}). We can use isProp\mathsf{isProp} to generate us some faces, except using xx and p(i)p(i), or xx and q(i)q(i) as the two endpoints.

Writing this down as code, we get:

  • (j=i0)f(x,p(i))(k)(j = \mathsf{i0}) \mapsto f(x, p(i))(k)
  • (j=i1)f(x,q(i))(k)(j = \mathsf{i1}) \mapsto f(x, q(i))(k)

This time, the ii dimension has the refl\mathsf{refl} edges. Since all the edges on the bottom face as refl\mathsf{refl}, we can just use the constant reflreflx\mathsf{refl}_{\mathsf{refl}_x} as the bottom face. In cubical, this is the constant expression x.

Putting this all together, we can using hcomp\mathsf{hcomp} to complete the top face (k=i1)(k = \mathsf{i1}) for us:

    let u = λ k  λ where
      (i = i0)  f x x k
      (i = i1)  f x y k
      (j = i0)  f x (p i) k
      (j = i1)  f x (q i) k
    in hcomp u x

Hooray! Agda is happy with this.

Let’s move on to a more complicated example.

Example: Σ2S1\Sigma \mathbb{2} \simeq S^1

Suspensions are an example of a higher inductive type. It can be shown that spheres can be iteratively defined in terms of suspensions.

Since the 00-sphere is just two points (solutions to x2=1\| \bm{x} \|_2 = 1 in 1 dimension), we represent this as S0:2S^0 :\equiv 2. We can show that a suspension over this, Σ2\Sigma 2, is equivalent to the classic 11-sphere, the circle S1S^1.

Let’s state the lemma:

Σ2≃S¹ : Susp Bool  

Equivalences can be generated from isomorphisms:

Σ2≃S¹ = isoToEquiv (iso f g fg gf) where

In this model, we’re going to define ff and gg by having both the north and south poles be squished into one side. The choice of side is arbitrary, so I’ll choose true\mathsf{true}. This way, true\mathsf{true} is suspended into the refl\mathsf{refl} path, and false\mathsf{false} is suspended into the loop\mathsf{loop}.

Here’s a picture of our function ff:

The left setup is presented in the traditional suspension layout, with meridians going through true\mathsf{true} and false\mathsf{false} while the right side “squishes” the north and south poles using refl\mathsf{refl}, while having the other path represent the loop\mathsf{loop}.

On the other side, gg, we want to map back from S1S^1 into Σ2\Sigma 2. We can map the loop\mathsf{loop} back into merid  false\mathsf{merid} \; \mathsf{false}, but the types mismatch, since loop:basebase\mathsf{loop} : \mathsf{base} \equiv \mathsf{base} but merid  false:northsouth\mathsf{merid} \; \mathsf{false} : \mathsf{north} \equiv \mathsf{south}. But since merid  true\mathsf{merid} \; \mathsf{true} is refl\mathsf{refl}, we can just concatenate with its inverse to get the full path:

merid  false(merid  true)1:northnorth\mathsf{merid} \; \mathsf{false} \cdot (\mathsf{merid} \; \mathsf{true})^{-1} : \mathsf{north} \equiv \mathsf{north}

In Agda, we can write it like this:

Now, the fun part is to show the extra requirements that are needed to show that these two functions indeed form an isomorphism:

  • f(g(s))sf(g(s)) \equiv s
  • g(f(b))bg(f(b)) \equiv b

Let’s show f(g(s))sf(g(s)) \equiv s by induction on ss, which is of type S1S^1. The base\mathsf{base} case is easily handled by refl\mathsf{refl}, since f(g(base))=f(north)=basef(g(\mathsf{base})) = f(\mathsf{north}) = \mathsf{base} definitionally by the reduction rules we gave above.

  fg : (s : )  f (g s)  s
  fg base = refl

The loop case is trickier. If we were using book HoTT, here’s how we would solve this (this result is given in Lemma 6.5.1 of the HoTT book):

apf(apg(loop))loopapf(merid  false(merid  true)1)loopapf(merid  false)apf(merid  true)1looploopapf(merid  true)1looplooprefl1looplooprefllooplooploop\begin{align*} \mathsf{ap}_f(\mathsf{ap}_g(\mathsf{loop})) &\equiv \mathsf{loop} \\ \mathsf{ap}_f(\mathsf{merid} \; \mathsf{false} \cdot (\mathsf{merid} \; \mathsf{true})^{-1}) &\equiv \mathsf{loop} \\ \mathsf{ap}_f(\mathsf{merid} \; \mathsf{false}) \cdot \mathsf{ap}_f (\mathsf{merid} \; \mathsf{true})^{-1} &\equiv \mathsf{loop} \\ \mathsf{loop} \cdot \mathsf{ap}_f (\mathsf{merid} \; \mathsf{true})^{-1} &\equiv \mathsf{loop} \\ \mathsf{loop} \cdot \mathsf{refl}^{-1} &\equiv \mathsf{loop} \\ \mathsf{loop} \cdot \mathsf{refl} &\equiv \mathsf{loop} \\ \mathsf{loop} &\equiv \mathsf{loop} \\ \end{align*}

Between the second and third steps, I used functoriality of the ap\mathsf{ap} operation (equation (i) of Lemma 2.2.2 in the HoTT book).

How can we construct a cube to solve this? Like in the first example, let’s start out by writing down the face we want to end up with:

We’re looking for the path in the bottom-to-top jj dimension. We would like to have hcomp\mathsf{hcomp} give us this face. This time, let’s see the whole cube first and then pick apart how it was constructed.

First of all, note that unrolling f(g(loop  i))f(g(\mathsf{loop} \; i)) would give us f((merid  false(merid  true)1)  i)f((\mathsf{merid} \; \mathsf{false} \cdot (\mathsf{merid} \; \mathsf{true})^{-1}) \; i), which is the same as f(merid  false  i)f(merid  true  (i))f(\mathsf{merid} \; \mathsf{false} \; i) \cdot f(\mathsf{merid} \; \mathsf{true} \; (\sim i)). Since this is a composition, it deserves its own face of the cube, with our goal f(g(loop  i))f(g(\mathsf{loop} \; i)) being the lid.

We can express this as an hfill\mathsf{hfill} operation, which is similar to hcomp\mathsf{hcomp} in that it takes the same face arguments, but produces the contents of the face rather than just the upper lid.

For the rest of the faces, we can just fill in opposite sides until the cube is complete. The Agda translation looks like this:

  fg (loop i) j =
    let
      u = λ k  λ where
        (i = i0)  base
        (i = i1)  f (merid true (~ k))
        (j = i0) 
          let u = λ k'  λ where
            (i = i0)  base
            (i = i1)  f (merid true (~ k'))
          in hfill u (inS (f (merid false i))) k
        (j = i1)  loop i
    in hcomp u (f (merid false i))

Nothing should be too surprising here, other than the use of a nested hfill\mathsf{hfill} which is needed to describe the face that contains the composition.

Now, to prove g(f(b))bg(f(b)) \equiv b, where b:Σ2b : \Sigma 2. We can do induction on bb. Again, the point constructor cases are relatively simple.

  gf : (b : Susp Bool)  g (f b)  b
  gf north = refl
  gf south = merid true

For the meridian case (b:2)northsouth(b : 2) \rightarrow \mathsf{north} \equiv \mathsf{south}, we can do 22-induction on bb. First, for the true\mathsf{true} case, let’s see the diagram of what we’re trying to prove.

After simplifying all the expressions, we find that g(f(merid  true  i))=g(base)=northg(f(\mathsf{merid} \; \mathsf{true} \; i)) = g(\mathsf{base}) = \mathsf{north}. So really, we’re trying to prove that northmerid  true  i\mathsf{north} \equiv \mathsf{merid} \; \mathsf{true} \; i.

But wait, that’s odd… there’s a south\mathsf{south} in the top right corner. What can we do?

Well, we could actually use the same path on the right as on the top. We won’t even need an hcomp\mathsf{hcomp} for this, just a clever interval expression:

  gf (merid true i) j = merid true (i  j)

One last cube for the g(f(merid  false  i))  jg(f(\mathsf{merid} \; \mathsf{false} \; i)) \; j case. This cube actually looks very similar to the f(g(s))f(g(s)) cube, although the points are in the suspension type rather than in S1S^1. Once again we have a composition, so we will need an extra hfill\mathsf{hfill} to get us the front face of this cube.

This cube cleanly extracts into the following Agda code.

  gf (merid false i) j =
    let
      u = λ k  λ where
        (i = i0)  north
        (i = i1)  merid true (j  ~ k)
        (j = i0) 
          let u = λ k'  λ where
            (i = i0)  north
            (i = i1)  merid true (~ k')
          in hfill u (inS (merid false i)) k
        (j = i1)  merid false i
    in hcomp u (merid false i)

These are some of my takeaways from studying cubical type theory and hcomp\mathsf{hcomp} these past few weeks. One thing I’d have to note is that drawing all these cubes really does make me wish there was some kind of 3D visualizer for these cubes…

Anyway, that’s all! See you next post :)

References