open import 1Lab.Path.Groupoid
open import 1Lab.Type.Sigma
open import 1Lab.HLevel
open import 1Lab.Equiv
open import 1Lab.Path
open import 1Lab.Type

module 1Lab.HLevel.Retracts where

Closure of h-levels🔗

The homotopy n-types have many closure properties. A trivial example is that they are closed under equivalences, since any property of types is preserved by equivalence (This is the univalence axiom). More interesting is that they are closed under retractions:

Retractions🔗

The first base case is to show that retracts of contractible types are contractible. We say that AA is a retract of BB if there is a map f:ABf : A \to B admitting a right-inverse. This means that ff is the retraction (the left inverse). The proof is a calculation:

retract→is-contr : (f : A  B) (g : B  A)
                  is-left-inverse f g
                  is-contr A
                  is-contr B
retract→is-contr f g h isC .centre = f (isC .centre)
retract→is-contr f g h isC .paths x =
  f (isC .centre) ≡⟨ ap f (isC .paths _) 
  f (g x)         ≡⟨ h _ 
  x               

We must also show that retracts of propositions are propositions:

retract→is-prop : (f : A  B) (g : B  A)
                 is-left-inverse f g
                 is-prop A
                 is-prop B
retract→is-prop f g h propA x y =
  x       ≡⟨ sym (h _) 
  f (g x) ≡⟨ ap f (propA _ _) 
  f (g y) ≡⟨ h _ 
  y       

Now we can extend this to all h-levels by induction:

retract→is-hlevel : (n : Nat) (f : A  B) (g : B  A)
                  is-left-inverse f g
                  is-hlevel A n
                  is-hlevel B n
retract→is-hlevel 0 = retract→is-contr
retract→is-hlevel 1 = retract→is-prop

For the base case, we already have the proofs we’re after. For the inductive case, a function g x ≡ g y → x ≡ y is constructed, which is a left inverse to the function ap g. Then, since (g x) ≡ (g y) is a homotopy (n+1)-type, we conclude that so is x ≡ y.

retract→is-hlevel (suc (suc n)) f g h hlevel x y =
  retract→is-hlevel (suc n) sect (ap g) inv (hlevel (g x) (g y))
  where
    sect : g x  g y  x  y
    sect path =
      x       ≡⟨ sym (h _) 
      f (g x) ≡⟨ ap f path 
      f (g y) ≡⟨ h _ 
      y       

The left inverse is constructed out of ap and the given homotopy.

    inv : is-left-inverse sect (ap g)
    inv path =
      sym (h x)  ap f (ap g path)  h y  refl ≡⟨ ap  e  sym (h _)  _  e) (∙-id-r (h _)) 
      sym (h x)  ap f (ap g path)  h y        ≡⟨ ap₂ _∙_ refl (sym (homotopy-natural h _)) 
      sym (h x)  h x  path                    ≡⟨ ∙-assoc _ _ _ 
      (sym (h x)  h x)  path                  ≡⟨ ap₂ _∙_ (∙-inv-l (h x)) refl 
      refl  path                               ≡⟨ ∙-id-l path 
      path                                      

The proof that this function does invert ap g on the left is boring, but it consists mostly of symbol pushing. The only non-trivial step, and the key to the proof, is the theorem that homotopies are natural transformations: We can flip ap f (ap g path) and h y to get a pair of paths that annihilates on the left, and path on the right.

Equivalences🔗

It follows, without a use of univalence, that h-levels are closed under isomorphisms and equivalences:

iso→is-hlevel : (n : Nat) (f : A  B)  is-iso f  is-hlevel A n  is-hlevel B n
iso→is-hlevel n f is-iso =
  retract→is-hlevel n f (is-iso .is-iso.inv)
                       (is-iso .is-iso.rinv)

equiv→is-hlevel : (n : Nat) (f : A  B)  is-equiv f  is-hlevel A n  is-hlevel B n
equiv→is-hlevel n f eqv = iso→is-hlevel n f (is-equiv→is-iso eqv)

is-hlevel≃ : (n : Nat)  (A  B)  is-hlevel A n  is-hlevel B n
is-hlevel≃ n (f , eqv) = iso→is-hlevel n f (is-equiv→is-iso eqv)

Functions into n-types🔗

Since h-levels are closed under retracts, The type of functions into a homotopy n-type is itself a homotopy n-type.

Π-is-hlevel :  {a b} {A : Type a} {B : A  Type b}
             (n : Nat) (Bhl : (x : A)  is-hlevel (B x) n)
             is-hlevel ((x : A)  B x) n
Π-is-hlevel 0 bhl = contr  x  bhl _ .centre) λ x i a  bhl _ .paths (x a) i
Π-is-hlevel 1 bhl f g i a = bhl a (f a) (g a) i
Π-is-hlevel (suc (suc n)) bhl f g =
  retract→is-hlevel (suc n) funext happly  x  refl)
    (Π-is-hlevel (suc n) λ x  bhl x (f x) (g x))

By taking B to be a type rather than a family, we get that A → B also inherits the h-level of B.

fun-is-hlevel
  :  {a b} {A : Type a} {B : Type b}
   (n : Nat)  is-hlevel B n
   is-hlevel (A  B) n
fun-is-hlevel n hl = Π-is-hlevel n  _  hl)

Sums of n-types🔗

A similar argument, using the fact that paths of pairs are pairs of paths, shows that dependent sums are also closed under h-levels.

Σ-is-hlevel : {A : Type } {B : A  Type ℓ'} (n : Nat)
             is-hlevel A n
             ((x : A)  is-hlevel (B x) n)
             is-hlevel (Σ B) n
Σ-is-hlevel 0 acontr bcontr =
  contr (acontr .centre , bcontr _ .centre)
    λ x  Σ-pathp (acontr .paths _)
                  (is-prop→pathp  _  is-contr→is-prop (bcontr _)) _ _)

Σ-is-hlevel 1 aprop bprop (a , b) (a' , b') i =
  (aprop a a' i) , (is-prop→pathp  i  bprop (aprop a a' i)) b b' i)

Σ-is-hlevel {B = B} (suc (suc n)) h1 h2 x y =
  iso→is-hlevel (suc n)
    (is-iso.inverse (Σ-path-iso .snd) .is-iso.inv)
    (Σ-path-iso .snd)
    (Σ-is-hlevel (suc n) (h1 (fst x) (fst y)) λ x  h2 _ _ _)

Similarly for dependent products and functions, there is a non-dependent version of Σ-is-hlevel that expresses closure of h-levels under _×_.

×-is-hlevel :  {a b} {A : Type a} {B : Type b}
             (n : Nat)
             is-hlevel A n  is-hlevel B n
             is-hlevel (A × B) n
×-is-hlevel n ahl bhl = Σ-is-hlevel n ahl  _  bhl)

Similarly, Lift does not induce a change of h-levels, i.e. if AA is an nn-type in a universe UU, then it’s also an nn-type in any successor universe:

Lift-is-hlevel :  {a b} {A : Type a}
                (n : Nat)
                is-hlevel A n
                is-hlevel (Lift b A) n
Lift-is-hlevel n a-hl = retract→is-hlevel n lift Lift.lower  _  refl) a-hl

Automation🔗

For the common case of proving that a composite type built out of pieces with a known h-level has that same h-level, we can apply the helpers above very uniformly. So uniformly, in fact, that Agda’s instance resolution mechanism can do it for us. However, since is-hlevel is a recursive definition which unfolds depending on the level, we must introduce a record wrapper around this type which prevents recursion. Otherwise we could not expect Agda to find instances in scope.

record H-Level {} (T : Type ) (n : Nat) : Type  where
  constructor hlevel-instance
  field
    has-hlevel : is-hlevel T n

The canonical entry point for the search is hlevel, which turns an instance argument of H-Level to an actual usable witness. Note that the parameter nn is explicit: We can not expect Agda to recover nn from the expected type of the application.

hlevel :  {} {T : Type } n  x : H-Level T n   is-hlevel T n
hlevel _  x  = H-Level.has-hlevel x

private variable
  ℓ′ : Level
  S T : Type 

module _ where
  open H-Level
  H-Level-is-prop :  {n}  is-prop (H-Level T n)
  H-Level-is-prop {n = n} x y i .has-hlevel =
    is-hlevel-is-prop n (x .has-hlevel) (y .has-hlevel) i

Because of the way we set up our search, the “leaves” in the instance search must support offsetting the index by any positive number: Rather than defining an instance saying that e.g. N\bb{N} has h-level 2, we define an instance saying it has h-level 2+k2+k, for any choice of kk. This is done using the basic-instance helper:

basic-instance :  {} {T : Type } n  is-hlevel T n   {k}  H-Level T (n + k)
basic-instance {T = T} n hl {k} =
  subst (H-Level T) (+-comm n k) (hlevel-instance (is-hlevel-+ n k hl))
  where
    +-comm :  n k  k + n  n + k
    +-comm zero k = go k where
      go :  k  k + 0  k
      go zero = refl
      go (suc x) = ap suc (go x)
    +-comm (suc n) k = go n k  ap suc (+-comm n k) where
      go :  n k  k + suc n  suc (k + n)
      go n zero = refl
      go n (suc k) = ap suc (go n k)

prop-instance :  {} {T : Type }  is-prop T   {k}  H-Level T (suc k)
prop-instance {T = T} hl = hlevel-instance (is-prop→is-hlevel-suc hl)

We then have a family of instances for solving compound types, e.g. function types, Σ\Sigma-types, path types, lifts, etc.

instance
  H-Level-pi
    :  {n} {S : T  Type }
       {x}  H-Level (S x) n 
     H-Level (∀ x  S x) n
  H-Level-pi {n = n} .H-Level.has-hlevel = Π-is-hlevel n λ _  hlevel n

  H-Level-pi′
    :  {n} {S : T  Type }
       {x}  H-Level (S x) n 
     H-Level (∀ {x}  S x) n
  H-Level-pi′ {n = n} .H-Level.has-hlevel = Π-is-hlevel′ n λ _  hlevel n

  H-Level-sigma
    :  {n} {S : T  Type }
      H-Level T n     {x}  H-Level (S x) n 
     H-Level (Σ S) n
  H-Level-sigma {n = n} .H-Level.has-hlevel =
    Σ-is-hlevel n (hlevel n) λ _  hlevel n

  H-Level-path′
    :  {n}  s : H-Level S (suc n)  {x y}  H-Level (Path S x y) n
  H-Level-path′ {n = n} .H-Level.has-hlevel = Path-is-hlevel' n (hlevel (suc n)) _ _

  H-Level-Lift
    :  {n}  s : H-Level T n   H-Level (Lift  T) n
  H-Level-Lift {n = n} .H-Level.has-hlevel = Lift-is-hlevel n (hlevel n)