index

⚠️ This module is not part of this project and is only included for reference.
It is either part of the 1lab, the cubical library, or a built-in Agda module.

---
description: |
  We establish that h-levels are closed under retractions, and use this
  to establish many closure properties of h-levels. Then we table
  these closure properties using Agda's instance resolution mechanism,
  automating "boring" h-level obligations.
---
<!--
```agda
{-# OPTIONS --no-projection-like #-}
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
```
-->

```agda
module 1Lab.HLevel.Retracts where
```

# Closure of h-levels

<!--
```
private variable
   ℓ' : Level
  A B C : Type 
  F G : A  Type 
```
-->

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 $A$ is a retract of $B$ if there is a map
$f : A \to B$ admitting a `right-inverse`{.Agda}. This means that $f$ is
the retraction (the left inverse). The proof is a calculation:

```agda
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:

```agda
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:

```agda
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`.

```agda
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`{.Agda} and the given
homotopy.

```agda
    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) (∙-idr (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₂ _∙_ (∙-invl (h x)) refl 
      refl  path                               ≡⟨ ∙-idl 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 behave like
natural transformations`{.Agda ident=homotopy-natural}: 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:

```agda
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)  (B  A)  is-hlevel A n  is-hlevel B n
is-hlevel≃ n f = iso→is-hlevel n (equiv→inverse (f .snd)) (iso (f .fst) (equiv→unit (f .snd)) (equiv→counit (f .snd)))

Iso→is-hlevel : (n : Nat)  Iso B A  is-hlevel A n  is-hlevel B n
Iso→is-hlevel n (f , isic) = iso→is-hlevel n (isic .is-iso.inv) $
  iso f (isic .is-iso.linv) (isic .is-iso.rinv)
```

## 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.

```agda
Π-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))
```

<!--
```agda
Π-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' n bhl = retract→is-hlevel n
   f {x}  f x)  f x  f)  _  refl)
  (Π-is-hlevel n bhl)

Π-is-hlevel²
  :  {a b c} {A : Type a} {B : A  Type b} {C :  a  B a  Type c}
   (n : Nat) (Bhl : (x : A) (y : B x)  is-hlevel (C x y) n)
   is-hlevel (∀ x y  C x y) n
Π-is-hlevel² n w = Π-is-hlevel n λ _  Π-is-hlevel n (w _)

Π-is-hlevel²'
  :  {a b c} {A : Type a} {B : A  Type b} {C :  a  B a  Type c}
   (n : Nat) (Bhl : (x : A) (y : B x)  is-hlevel (C x y) n)
   is-hlevel (∀ {x y}  C x y) n
Π-is-hlevel²' n w = Π-is-hlevel' n λ _  Π-is-hlevel' n (w _)

Π-is-hlevel³
  :  {a b c d} {A : Type a} {B : A  Type b} {C :  a  B a  Type c}
      {D :  a b  C a b  Type d}
   (n : Nat) (Bhl : (x : A) (y : B x) (z : C x y)  is-hlevel (D x y z) n)
   is-hlevel (∀ x y z  D x y z) n
Π-is-hlevel³ n w = Π-is-hlevel n λ _  Π-is-hlevel² n (w _)

Π-is-hlevel³'
  :  {a b c d} {A : Type a} {B : A  Type b} {C :  a  B a  Type c}
      {D :  a b  C a b  Type d}
   (n : Nat) (Bhl : (x : A) (y : B x) (z : C x y)  is-hlevel (D x y z) n)
   is-hlevel (∀ {x y z}  D x y z) n
Π-is-hlevel³' n w = Π-is-hlevel' n λ _  Π-is-hlevel²' n (w _)
```
-->

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

```agda
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 between pairs are pairs
of paths`{.Agda ident=Σ-path-iso}, shows that dependent sums are also
closed under h-levels.

```agda
Σ-is-hlevel : {A : Type } {B : A  Type ℓ'} (n : Nat)
             is-hlevel A n
             ((x : A)  is-hlevel (B x) n)
             is-hlevel (Σ A 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`{.Agda} that expresses closure of h-levels under
`_×_`{.Agda}.

```agda
×-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`{.Agda} does not induce a change of h-levels, i.e. if
$A$ is an $n$-type in a universe $U$, then it's also an $n$-type in any
successor universe:

```agda
opaque
  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
```

Likewise, if the `Lift`{.Agda} of $A$ is an $n$-type, then $A$ must also
be an n-type.

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

The `fibre`{.Agda}s of a function between $n$-types are $n$-types.

```agda
fibre-is-hlevel
  :  { ℓ'} {A : Type } {B : Type ℓ'}
   (n : Nat)
   is-hlevel A n  is-hlevel B n
   (f : A  B)
    b  is-hlevel (fibre f b) n
fibre-is-hlevel n Ah Bh f b = Σ-is-hlevel n Ah λ _  Path-is-hlevel n Bh
```

# 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`{.Agda}
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.

```agda
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`{.Agda}, which turns
an instance argument of `H-Level`{.Agda} to an actual usable witness.
Note that the parameter $n$ is explicit: We can not expect Agda to
recover $n$ from the expected type of the application.

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

private variable
  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. $\bb{N}$ has h-level
2, we define an instance saying it has h-level $2+k$, for any choice of
$k$. This is done using the `basic-instance`{.Agda} helper:

```agda
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.

```agda
instance opaque
  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-⊤ :  {n}  H-Level  n
  H-Level-⊤ {n = zero} = hlevel-instance (contr tt  x i  tt))
  H-Level-⊤ {n = suc n} = prop-instance λ x y i  tt

  H-Level-⊥ :  {n}  H-Level  (suc n)
  H-Level-⊥ {n = n} = prop-instance λ x y  absurd x

  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 (Σ T S) n
  H-Level-sigma {n = n} .H-Level.has-hlevel =
    Σ-is-hlevel n (hlevel n) λ _  hlevel n

  H-Level-PathP
    :  {n} {S : I  Type }  s : H-Level (S i1) (suc n)  {x y}
     H-Level (PathP S x y) n
  H-Level-PathP {n = n} .H-Level.has-hlevel = PathP-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)

  H-Level-is-contr :  {n} {} {T : Type }  H-Level (is-contr T) (suc n)
  H-Level-is-contr = prop-instance is-contr-is-prop
```

<!--
```agda
positive-hlevel :  {} (T : Type )  Nat  Type _
positive-hlevel T n =  {k}  H-Level T (n + k)
{-# INLINE positive-hlevel #-}
```
-->