module Cat.Instances.MarkedGraphs where

Marked graphsπŸ”—

Recall that every graph gives rise to a free category Though useful, this construction is rather limiting; if we want to construct (strict) categories via combinatorial methods, then we really do need a way to add non-trivial equalities. In other words, we need to be able to construct categories freely from a set of generators and relations! This leads us to the following series of definitions:

A marked graph consists of a graph along with a set of pairs of distinguished paths in which we will call marked pairs.

record Marked-graph (o β„“ : Level) : Type (lsuc o βŠ” lsuc β„“) where
  no-eta-equality
  field
    graph : Graph o β„“

  open Graph graph public

  field
    Marked : βˆ€ {x y} β†’ Path-in graph x y β†’ Path-in graph x y β†’ Ξ©

A marked graph homomorphism is a graph homomorphism that takes a marked pair in to a marked pair in

record Marked-graph-hom (G : Marked-graph o β„“) (H : Marked-graph o' β„“') : Type (o βŠ” β„“ βŠ” o' βŠ” β„“') where
  no-eta-equality
  field
    hom : Graph-hom (G .graph) (H .graph)
    pres-marked
      : βˆ€ {x y} {p q : Path-in (G .graph) x y}
      β†’ ∣ G .Marked p q ∣
      β†’ ∣ H .Marked (path-map hom p) (path-map hom q) ∣
  open Graph-hom hom public
open Marked-graph-hom

unquoteDecl H-Level-Marked-graph-hom = declare-record-hlevel 2 H-Level-Marked-graph-hom (quote Marked-graph-hom)

instance
  Funlike-Marked-graph-hom : Funlike (Marked-graph-hom G H) ⌞ G ⌟ Ξ» _ β†’ ⌞ H ⌟
  Funlike-Marked-graph-hom .Funlike._#_ = vertex

Marked-graph-hom-pathp
  : {G : I β†’ Marked-graph o β„“} {H : I β†’ Marked-graph o' β„“'}
  β†’ {f : Marked-graph-hom (G i0) (H i0)} {g : Marked-graph-hom (G i1) (H i1)}
  β†’ (p0 : βˆ€ (x : βˆ€ i β†’ G i .Vertex)
          β†’ PathP (Ξ» i β†’ H i .Vertex)
              (f .vertex (x i0)) (g .vertex (x i1)))
  β†’ (p1 : βˆ€ {x y : βˆ€ i β†’ G i .Vertex}
          β†’ (e : βˆ€ i β†’ G i .Edge (x i) (y i))
          β†’ PathP (Ξ» i β†’ H i .Edge (p0 x i) (p0 y i))
              (f .edge (e i0)) (g .edge (e i1)))
  β†’ PathP (Ξ» i β†’ Marked-graph-hom (G i) (H i)) f g
Marked-graph-hom-pathp {G = G} {H = H} {f = f} {g = g} p0 p1 = comm-path where
  hom-path : PathP (Ξ» i β†’ Graph-hom (G i .graph) (H i .graph)) (f .hom) (g .hom)
  hom-path =
    Graph-hom-pathp {G = Ξ» i β†’ G i .graph} {H = Ξ» i β†’ H i .graph}
      {f = f .hom} {g .hom}
      p0 p1

  comm-path : PathP (Ξ» i β†’ Marked-graph-hom (G i) (H i)) f g
  comm-path i .hom = hom-path i
  comm-path i .pres-marked {x} {y} {p} {q} =
    is-prop→pathp (λ i →
        Ξ -is-hlevel {A = G i .Vertex} 1 Ξ» x β†’
        Ξ -is-hlevel {A = G i .Vertex} 1 Ξ» y β†’
        Ξ -is-hlevel {A = Path-in (G i .graph) x y} 1 Ξ» p β†’
        Ξ -is-hlevel {A = Path-in (G i .graph) x y} 1 Ξ» q β†’
        Ξ -is-hlevel {A = ∣ G i .Marked p q ∣} 1 Ξ» _ β†’
        H i .Marked (path-map (hom-path i) p) (path-map (hom-path i) q) .is-tr)
      (Ξ» _ _ _ _ β†’ f .pres-marked)
      (Ξ» _ _ _ _ β†’ g .pres-marked)
      i x y p q

Marked-graph-hom-path
  : {f g : Marked-graph-hom G H}
  β†’ (p0 : βˆ€ x β†’ f .vertex x ≑ g .vertex x)
  β†’ (p1 : βˆ€ {x y} β†’ (e : G .Edge x y) β†’ PathP (Ξ» i β†’ H .Edge (p0 x i) (p0 y i)) (f .edge e) (g .edge e))
  β†’ f ≑ g
Marked-graph-hom-path {G = G} {H = H} p0 p1 =
  Marked-graph-hom-pathp {G = Ξ» _ β†’ G} {H = Ξ» _ β†’ H}
    (Ξ» x i β†’ p0 (x i) i)
    (Ξ» e i β†’ p1 (e i) i)

We can organize marked graphs and their homomorphisms into a category

Marked-graphs : βˆ€ o β„“ β†’ Precategory (lsuc o βŠ” lsuc β„“) (o βŠ” β„“)
Marked-graphs o β„“ .Precategory.Ob = Marked-graph o β„“
Marked-graphs o β„“ .Precategory.Hom = Marked-graph-hom
Composition, identities, and their corresponding equations are a bit tedious, so we omit the details.
Marked-graphs o β„“ .Precategory.Hom-set _ _ = hlevel 2
Marked-graphs o β„“ .Precategory.id {G} .hom = Graphs.id
Marked-graphs o β„“ .Precategory.id {G} .pres-marked p~q =
  substβ‚‚ (Ξ» p q β†’ ∣ G .Marked p q ∣)
    (sym (path-map-id _))
    (sym (path-map-id _))
    p~q
Marked-graphs o β„“ .Precategory._∘_ f g .hom = (f .hom) Graphs.∘ (g .hom)
Marked-graphs o β„“ .Precategory._∘_ {G} {H} {K} f g .pres-marked p~q =
  substβ‚‚ (Ξ» p q β†’ ∣ K .Marked p q ∣)
    (sym (path-map-∘ _))
    (sym (path-map-∘ _))
    (f .pres-marked (g .pres-marked p~q))
Marked-graphs o β„“ .Precategory.idr _ =
  Marked-graph-hom-path (Ξ» _ β†’ refl) (Ξ» _ β†’ refl)
Marked-graphs o β„“ .Precategory.idl _ =
  Marked-graph-hom-path (Ξ» _ β†’ refl) (Ξ» _ β†’ refl)
Marked-graphs o β„“ .Precategory.assoc _ _ _ =
  Marked-graph-hom-path (Ξ» _ β†’ refl) (Ξ» _ β†’ refl)

Moreover, there is a faithful functor from the category of strict categories to the category of marked graphs.

Strict-catsβ†ͺMarked-graphs : Functor (Strict-cats o β„“) (Marked-graphs o β„“)

The action on objects takes a strict category to its underlying graph, and adds a marked pair between whenever and are equal as morphisms in

Strict-catsβ†ͺMarked-graphs .Fβ‚€ C .graph =
  Strict-catsβ†ͺGraphs .Fβ‚€ C
Strict-catsβ†ͺMarked-graphs .Fβ‚€ C .Marked p q =
  elΞ© (path-reduce C p ≑ path-reduce C q)

Functoriality follows from the fact that functors take equal paths in to equal paths in

Strict-catsβ†ͺMarked-graphs .F₁ F .hom =
  Strict-catsβ†ͺGraphs .F₁ F
Strict-catsβ†ͺMarked-graphs .F₁ F .pres-marked {p = p} {q = q} =
  β–‘-map Ξ» p~q β†’ path-reduce-natural F p βˆ™ ap (F .F₁) p~q βˆ™ sym (path-reduce-natural F q)
Strict-catsβ†ͺMarked-graphs .F-id =
  Marked-graph-hom-path (Ξ» _ β†’ refl) (Ξ» _ β†’ refl)
Strict-catsβ†ͺMarked-graphs .F-∘ F G =
  Marked-graph-hom-path (Ξ» _ β†’ refl) (Ξ» _ β†’ refl)

This functor is clearly faithful; the proof is essentially just relabeling data.

Strict-catsβ†ͺMarked-graphs-faithful
  : is-faithful (Strict-catsβ†ͺMarked-graphs {o} {β„“})
Strict-catsβ†ͺMarked-graphs-faithful p =
  Functor-path (Ξ» x i β†’ p i .vertex x) (Ξ» f i β†’ p i .edge f)

More surprisingly this functor is also full!

Strict-catsβ†ͺMarked-graphs-full
  : is-full (Strict-catsβ†ͺMarked-graphs {o} {β„“})

To see why, suppose that is a marked graph homomorphism between strict categories. By definition, already contains the data of a functor; the tricky part is showing that this data is functorial.

Strict-catsβ†ͺMarked-graphs-full {x = C} {y = D} f =
  pure (functor , Marked-graph-hom-path (Ξ» _ β†’ refl) (Ξ» _ β†’ refl))
  where
    module C = Precategory (C .fst)
    module D = Precategory (D .fst)

    functor : Functor (C .fst) (D .fst)
    functor .Fβ‚€ = f .vertex
    functor .F₁ = f .edge

The trick is that marked graph homomorphisms between categories cannot observe the intensional structure of paths, as they must preserve all marked pairs. In particular, will preserve the makred pair consisting of and the empty path; so

    functor .F-id =
      f .edge C.id          β‰‘Λ˜βŸ¨ D.idl _ βŸ©β‰‘Λ˜
      D.id D.∘ f .edge C.id β‰‘βŸ¨ β–‘-out! (f .pres-marked {p = cons C.id nil} {q = nil} (inc (C.idl _))) βŸ©β‰‘
      D.id                  ∎

Additionally, will preserve the marked pair consisting of the singleton path and the path so

    functor .F-∘ g h =
      f .edge (g C.∘ h)                  β‰‘Λ˜βŸ¨ D.idl _ βŸ©β‰‘Λ˜
      D.id D.∘ f .edge (g C.∘ h)         β‰‘Λ˜βŸ¨ β–‘-out! (f .pres-marked {p = cons h (cons g nil)} {q = cons (g C.∘ h) nil} (pure (sym (C.assoc _ _ _)))) βŸ©β‰‘Λ˜
      (D.id D.∘ f .edge g) D.∘ f .edge h β‰‘βŸ¨ apβ‚‚ D._∘_ (D.idl _) refl βŸ©β‰‘
      f .edge g D.∘ f .edge h ∎

Categories from generators and relationsπŸ”—

In this section, we will detail how to freely construct a category from a marked graph Intuitvely, this works by freely generating a category from and then identifying all marked pairs. However, there is a slight subtlety here: the marked pairs of may not respect path concatenation, and may not even form an equivalence relation!

Luckily, there is an easy (though tedious) solution to this problem: we can instead quotient by the reflexive-transitive-congruent closure instead, which we encode in Agda via the following higher-inductive type.

  data Marking
    : βˆ€ {x y} β†’ Path-in G.graph x y β†’ Path-in G.graph x y
    β†’ Type (o βŠ” β„“)
    where
    marked
      : βˆ€ {x y} {p q : Path-in G.graph x y}
      β†’ ∣ G.Marked p q ∣
      β†’ Marking p q
    reflexive
      : βˆ€ {x y} {p : Path-in G.graph x y}
      β†’ Marking p p
    symmetric
      : βˆ€ {x y} {p q : Path-in G.graph x y}
      β†’ Marking q p
      β†’ Marking p q
    transitive
      : βˆ€ {x y} {p q r : Path-in G.graph x y}
      β†’ Marking p q β†’ Marking q r
      β†’ Marking p r
    congruent
      : βˆ€ {x y z} {p q : Path-in G.graph x y} {r s : Path-in G.graph y z}
      β†’ Marking p q β†’ Marking r s
      β†’ Marking (p ++ r) (q ++ s)
    trunc : βˆ€ {x y} {p q : Path-in G.graph x y} β†’ is-prop (Marking p q)

The resulting category is not too difficult to construct: the objects are the vertices of and the morphisms are paths in quotiented by the aforementioned closure.

  Marked-path-category : Precategory o (o βŠ” β„“)
  Marked-path-category .Precategory.Ob = G.Vertex
  Marked-path-category .Precategory.Hom x y =
    Path-in G.graph x y / Marking
  Marked-path-category .Precategory.Hom-set _ _ = hlevel 2
  Marked-path-category .Precategory.id = inc nil
  Marked-path-category .Precategory._∘_ =
    Quot-opβ‚‚
      (Ξ» _ β†’ reflexive) (Ξ» _ β†’ reflexive)
      (flip _++_)
      (Ξ» p q r s p~q r~s β†’ congruent r~s p~q)
  Marked-path-category .Precategory.idr =
    elim! Ξ» p β†’ refl
  Marked-path-category .Precategory.idl =
    elim! Ξ» p β†’ ap inc (++-idr p)
  Marked-path-category .Precategory.assoc =
    elim! Ξ» p q r β†’ ap inc (++-assoc r q p)

Proving that this construction is free involves a bit more work.

We start with a useful lemma. Let be an arbitrary strict category, a marked graph, and a marked graph homomorphism. If are related by the marked closure of then folding over and results in the same morphism.

module _ (C : Ξ£ (Precategory o β„“) is-strict) where
  private
    module C = Cat.Reasoning (C .fst)
    ∣C∣ : Marked-graph o β„“
    ∣C∣ = Strict-catsβ†ͺMarked-graphs .Fβ‚€ C

  path-fold-marking
    : (f : Marked-graph-hom G ∣C∣)
    β†’ βˆ€ {x y} (p q : Path-in (G .graph) x y)
    β†’ Marking G p q
    β†’ path-fold C (f .hom) p ≑ path-fold C (f .hom) q

The proof is an easy but tedious application of the elimination principle of Marking.

  path-fold-marking {G = G} f p q =
    Marking-elim-prop G
      {P = Ξ» {x} {y} {p} {q} _ β†’
        path-fold C (f .hom) p ≑ path-fold C (f .hom) q}
      (Ξ» _ β†’ hlevel 1)
      (Ξ» {_} {_} {p} {q} p~q β†’
        sym (path-reduce-map p)
        βˆ™ β–‘-out! (f .pres-marked p~q)
        βˆ™ path-reduce-map q)
      refl (Ξ» _ β†’ sym) (Ξ» _ p=q _ q=r β†’ p=q βˆ™ q=r)
      (Ξ» {_} {_} {_} {p} {q} {r} {s} _ p=q _ r=s β†’
        path-fold-++ C p r
        βˆ™ apβ‚‚ C._∘_ r=s p=q
        βˆ™ sym (path-fold-++ C q s))

This lemma lets us extend folds over paths to folds over quotiented paths.

  comm-path-fold
    : (f : Marked-graph-hom G ∣C∣)
    β†’ βˆ€ {x y} β†’ Path-in (G .graph) x y / Marking G
    β†’ C.Hom (f .vertex x) (f .vertex y)
  comm-path-fold f =
    Quot-elim (Ξ» _ β†’ hlevel 2)
      (path-fold C (f .hom))
      (path-fold-marking f)

Moreover, this extends to a functor from the free category over to

  MarkedF
    : Marked-graph-hom G ∣C∣
    β†’ Functor (Marked-path-category G) (C .fst)
  MarkedF f .Fβ‚€ = f .vertex
  MarkedF f .F₁ = comm-path-fold f
  MarkedF f .F-id = refl
  MarkedF f .F-∘ = elim! Ξ» p q β†’ path-fold-++ C q p

Like path categories, functors out of marked path categories are purely characterized by their behaviour on edges.

  Marked-path-category-functor-path
    : {F F' : Functor (Marked-path-category G) C}
    β†’ (p0 : βˆ€ x β†’ F .Fβ‚€ x ≑ F' .Fβ‚€ x)
    β†’ (p1 : βˆ€ {x y} (e : G .Edge x y)
            β†’ PathP (Ξ» i β†’ C.Hom (p0 x i) (p0 y i))
                (F .F₁ (inc (cons e nil)))
                (F' .F₁ (inc (cons e nil))))
    β†’ F ≑ F'
Like the analogous result for path categories, this proof involves some cubical yoga which we will hide from the innocent reader.
  Marked-path-category-functor-path {G = G} {F = F} {F' = F'} p0 p1 =
    Functor-path p0 (elim! p1-paths)
    where
      p1-paths
        : βˆ€ {x y}
        β†’ (p : Path-in (G .graph) x y)
        β†’ PathP (Ξ» i β†’ C.Hom (p0 x i) (p0 y i)) (F .F₁ (inc p)) (F' .F₁ (inc p))
      p1-paths {x = x} nil i =
        hcomp (βˆ‚ i) Ξ» where
          j (i = i0) β†’ F .F-id (~ j)
          j (i = i1) β†’ F' .F-id (~ j)
          j (j = i0) β†’ C.id
      p1-paths (cons e p) i =
        hcomp (βˆ‚ i) Ξ» where
          j (i = i0) β†’ F .F-∘ (inc p) (inc (cons e nil)) (~ j)
          j (i = i1) β†’ F' .F-∘ (inc p) (inc (cons e nil)) (~ j)
          j (j = i0) β†’ p1-paths p i C.∘ p1 e i

With these results in hand, we can return to our original goal of showing that the marked path category on is a free object relative to the underlying marked graph functor.

Marked-free-category
  : βˆ€ (G : Marked-graph β„“ β„“)
  β†’ Free-object Strict-catsβ†ͺMarked-graphs G
Marked-free-category G .Free-object.free = Marked-path-category G , hlevel 2

The unit of the free object takes vertices to themselves, and edges to singleton paths. We need to do a bit of work to show that this construction preserves markings but it’s not too difficult.

Marked-free-category G .Free-object.unit = unit-comm where
  G* : Ξ£ (Precategory _ _) is-strict
  G* = Marked-path-category G , hlevel 2

  ∣G*∣ : Graph _ _
  ∣G*∣ = Strict-catsβ†ͺGraphs .Fβ‚€ G*

  unit : Graph-hom (G .graph) ∣G*∣
  unit .Graph-hom.vertex x = x
  unit .Graph-hom.edge e = inc (cons e nil)

  unit-comm : Marked-graph-hom G (Strict-catsβ†ͺMarked-graphs .Fβ‚€ G*)
  unit-comm .hom = unit
  unit-comm .pres-marked {p = p} {q = q} p~q =
    pure $
      path-reduce G* (path-map unit p) β‰‘βŸ¨ path-reduce-map p βŸ©β‰‘
      path-fold G* unit p              β‰‘Λ˜βŸ¨ path-fold-unique G* inc refl (Ξ» _ _ β†’ refl) p βŸ©β‰‘Λ˜
      inc p                            β‰‘βŸ¨ quot (marked p~q) βŸ©β‰‘
      inc q                            β‰‘βŸ¨ path-fold-unique G* inc refl (Ξ» _ _ β†’ refl) q βŸ©β‰‘
      path-fold G* unit q              β‰‘Λ˜βŸ¨ path-reduce-map q βŸ©β‰‘Λ˜
      path-reduce G* (path-map unit q) ∎

We’ve already put in the work for the universal property: all that we need to do is assemble previous results!

Marked-free-category G .Free-object.fold {Y = C} f = MarkedF C f
Marked-free-category G .Free-object.commute {Y = C} =
  Marked-graph-hom-path (Ξ» _ β†’ refl) (Ξ» _ β†’ idl _)
  where open Precategory (C .fst)
Marked-free-category G .Free-object.unique {Y = C} F p =
  Marked-path-category-functor-path
    (Ξ» x i β†’ p i .vertex x)
    (Ξ» e β†’ to-pathp (from-pathp (Ξ» i β†’ p i .edge e) βˆ™ sym (idl _)))
  where open Precategory (C .fst)

This means that the category of strict categories is a reflective subcategory of the category of marked graphs. In more intuitive terms, that we can think about strict categories as algebraic structure placed atop a marked graph, as inclusions from reflective subcategories are monadic!

Marked-free-categories⊣Underlying-marked-graph-reflective
  : is-reflective (Marked-free-categories⊣Underlying-marked-graph {β„“})
Marked-free-categories⊣Underlying-marked-graph-reflective =
  full+faithfulβ†’ff Strict-catsβ†ͺMarked-graphs
    Strict-catsβ†ͺMarked-graphs-full
    Strict-catsβ†ͺMarked-graphs-faithful