packages feed

moonlight-category-1.1.0.0: src-simplicial/Moonlight/Category/Pure/Simplicial/Nerve.hs

{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneKindSignatures #-}
{-# LANGUAGE UndecidableInstances #-}

-- | The nerve of a finite composable category: simplices are composable chains,
-- with face and degeneracy structure.  'normalizedNerve' is the ordinary
-- nonidentity-chain construction.  'unnormalizedNerve' materializes every
-- composable chain, including identity insertions, and therefore has a
-- substantially larger cost surface.
module Moonlight.Category.Pure.Simplicial.Nerve
  ( NerveSimplex,
    nerveSimplexDimension,
    nerveSimplexChain,
    nerveSimplexFromChain,
    mkNerveSimplex,
    Nerve,
    nerveCategory,
    unNerve,
    unnormalizedNerve,
    isNerveSimplexDegenerate,
    nerveSimplexFace,
    nerveSimplexDegeneracy,
    normalizedNerve,
    nerveInnerKan,
    fillNerveInnerHorn,
    fillNerveInnerHornIndexed,
  )
where

import Control.Monad (guard)
import Data.Containers.ListUtils (nubOrdOn)
import Data.Function ((&))
import Data.Kind (Type)
import Data.List (genericLength, genericSplitAt, unsnoc)
import Data.List.NonEmpty qualified as NonEmpty
import Data.Maybe (mapMaybe)
import Data.Map.Strict qualified as Map
import GHC.TypeNats (KnownNat, type (+))
import Moonlight.Category.Pure.Category (Category (..))
import Moonlight.Category.Pure.FiniteComposable
  ( ComposableChain,
    FiniteComposableCategory (..),
    SizedComposableChain,
    chainDimension,
    chainMorphisms,
    chainStartObject,
    chainVertices,
    mkComposableChain,
    sizedChainDimension,
    sizedChainValue,
  )
import Moonlight.Core (safeIndexNatural)
import Moonlight.Category.Pure.Simplicial.Kan
  ( IndexedHorn,
    InnerHorn,
    InnerHornError,
    InnerKan (..),
    hornDimension,
    hornFaces,
    indexedHornToHorn,
    innerHorn,
    mkInnerHorn,
  )
import Moonlight.Category.Pure.Simplicial.Set
  ( GeneratedSSet,
    TruncatedNormalizedSSet,
    normalizeGeneratedSSet,
  )
import Moonlight.Category.Pure.Simplicial.Set.Internal (trustedGeneratedSSetWithWitness)
import Moonlight.Category.Pure.Simplicial.TypeLevel (Dimension (..), Fin, finValue)
import Numeric.Natural (Natural)

type NerveSimplex :: Type -> Type
data NerveSimplex c = NerveSimplex
  { nerveSimplexDimension :: Natural,
    nerveSimplexChain :: ComposableChain c
  }

instance (Eq (Ob c), Eq (Mor c)) => Eq (NerveSimplex c) where
  (==) = sameSimplex

nerveSimplexFromChain :: ComposableChain c -> NerveSimplex c
nerveSimplexFromChain chainValue =
  NerveSimplex (chainDimension chainValue) chainValue

mkNerveSimplex :: Natural -> ComposableChain c -> Maybe (NerveSimplex c)
mkNerveSimplex dimensionValue chainValue =
  if dimensionValue == chainDimension chainValue
    then Just (NerveSimplex dimensionValue chainValue)
    else Nothing

type Nerve :: Type -> Type
data Nerve c = Nerve
  { nerveCategory :: c,
    unNerve :: TruncatedNormalizedSSet (NerveSimplex c)
  }

morphismIsIdentity :: (Category c, Eq (Mor c)) => c -> Mor c -> Bool
morphismIsIdentity categoryValue morphism =
  case source categoryValue morphism >>= identity categoryValue of
    Right identityMorphism -> morphism == identityMorphism
    Left _ -> False

isNerveSimplexDegenerate :: (Category c, Eq (Mor c)) => c -> Natural -> NerveSimplex c -> Bool
isNerveSimplexDegenerate categoryValue _ simplexValue =
  chainMorphisms (nerveSimplexChain simplexValue)
    & any (morphismIsIdentity categoryValue)

-- | Materialize the unnormalized nerve through the supplied dimension.  This
-- includes every identity insertion, so it is intended only for small
-- diagnostic or law workloads.  The dimension bound is /not/ a resource bound:
-- the number of composable chains can grow combinatorially before that bound.
unnormalizedNerve ::
  (FiniteComposableCategory c, Ord (Ob c), Ord (Mor c)) =>
  c ->
  Natural ->
  GeneratedSSet (NerveSimplex c)
unnormalizedNerve categoryValue upperBound =
  let levelMap = Map.fromAscListWith (<>) (nerveLevels categoryValue upperBound)
   in trustedGeneratedSSetWithWitness
        upperBound
        (\dimensionValue' -> Map.findWithDefault [] dimensionValue' levelMap)
        (nerveFace categoryValue)
        (nerveDegeneracy categoryValue)
        (isNerveSimplexDegenerate categoryValue)

-- | Construct the normalized nerve through the supplied dimension by
-- enumerating nonidentity chains and closing them under faces.  This is the
-- default construction for topological and combinatorial queries.  Its
-- dimension bound selects simplices; it does not promise a fixed time or memory
-- budget for an arbitrary finite category.
normalizedNerve ::
  (FiniteComposableCategory c, Ord (Ob c), Ord (Mor c)) =>
  c ->
  Natural ->
  TruncatedNormalizedSSet (NerveSimplex c)
normalizedNerve categoryValue upperBound =
  normalizeGeneratedSSet
    ( trustedGeneratedSSetWithWitness
        upperBound
        (\dimensionValue' -> Map.findWithDefault [] dimensionValue' closedLevelMap)
        (nerveFace categoryValue)
        (nerveDegeneracy categoryValue)
        (isNerveSimplexDegenerate categoryValue)
    )
  where
    closedLevelMap =
      closeUnderFaces categoryValue upperBound nonDegenerateLevelMap

    nonDegenerateLevelMap =
      Map.fromDistinctAscList
        ( zipWith
            (\dimensionValue' chains -> (dimensionValue', fmap (NerveSimplex dimensionValue') chains))
            [0 ..]
            (enumerateNonDegenerateChainsByDimension categoryValue upperBound)
        )

nerveInnerKan ::
  (FiniteComposableCategory c, Ord (Ob c), Ord (Mor c)) =>
  c ->
  Natural ->
  Nerve c
nerveInnerKan categoryValue upperBound = Nerve categoryValue (normalizedNerve categoryValue upperBound)

nerveLevels :: (FiniteComposableCategory c, Ord (Ob c), Ord (Mor c)) => c -> Natural -> [(Natural, [NerveSimplex c])]
nerveLevels categoryValue upperBound =
  Map.toAscList
    ( closeUnderFaces
        categoryValue
        upperBound
        (seedNerveLevelMap categoryValue upperBound (\_ _ -> True))
    )

seedNerveLevelMap ::
  forall c.
  (FiniteComposableCategory c, Ord (Ob c), Ord (Mor c)) =>
  c ->
  Natural ->
  (Natural -> NerveSimplex c -> Bool) ->
  Map.Map Natural [NerveSimplex c]
seedNerveLevelMap categoryValue upperBound keepSimplex =
  enumerateComposableChains categoryValue upperBound
    & mapMaybe sizedChainEntry
    & foldl' insertChainEntry Map.empty
    & Map.map (dedupeSimplices . reverse)
  where
    insertChainEntry ::
      Map.Map Natural [NerveSimplex c] ->
      (Natural, NerveSimplex c) ->
      Map.Map Natural [NerveSimplex c]
    insertChainEntry levelMap (dimensionValue, simplexValue) =
      Map.insertWith (<>) dimensionValue [simplexValue] levelMap

    sizedChainEntry ::
      SizedComposableChain c ->
      Maybe (Natural, NerveSimplex c)
    sizedChainEntry sizedChain =
      let dimensionValue = sizedChainDimension sizedChain
          simplexValue = NerveSimplex dimensionValue (sizedChainValue sizedChain)
       in if keepSimplex dimensionValue simplexValue
            then Just (dimensionValue, simplexValue)
            else Nothing

closeUnderFaces ::
  forall c.
  (Category c, Ord (Ob c), Ord (Mor c)) =>
  c ->
  Natural ->
  Map.Map Natural [NerveSimplex c] ->
  Map.Map Natural [NerveSimplex c]
closeUnderFaces categoryValue upperBound simplicesByDimension =
  foldr closeDimension
    (Map.map dedupeSimplices simplicesByDimension)
    [1 .. upperBound]
  where
    closeDimension ::
      Natural ->
      Map.Map Natural [NerveSimplex c] ->
      Map.Map Natural [NerveSimplex c]
    closeDimension dimensionValue simplicesAtDimension =
      let simplexFaceRows =
            Map.findWithDefault [] dimensionValue simplicesAtDimension
              & mapMaybe simplexFacesIfClosed
          validSimplices = fmap fst simplexFaceRows
          faceSimplices = foldMap snd simplexFaceRows
          withValidDimension = Map.insert dimensionValue validSimplices simplicesAtDimension
       in Map.insertWith mergeSimplices (dimensionValue - 1) faceSimplices withValidDimension

    simplexFacesIfClosed :: NerveSimplex c -> Maybe (NerveSimplex c, [NerveSimplex c])
    simplexFacesIfClosed simplexValue =
      let dimensionValue = nerveSimplexDimension simplexValue
       in fmap
            (\faceSimplices -> (simplexValue, faceSimplices))
            (traverse (\faceIndex -> faceSimplex dimensionValue faceIndex simplexValue) [0 .. dimensionValue])

    faceSimplex ::
      Natural ->
      Natural ->
      NerveSimplex c ->
      Maybe (NerveSimplex c)
    faceSimplex dimensionValue faceIndex simplexValue =
      NerveSimplex (dimensionValue - 1)
        <$> faceChain categoryValue faceIndex (nerveSimplexChain simplexValue)

mergeSimplices ::
  (Ord (Ob c), Ord (Mor c)) =>
  [NerveSimplex c] ->
  [NerveSimplex c] ->
  [NerveSimplex c]
mergeSimplices newSimplices existingSimplices =
  dedupeSimplices (existingSimplices <> newSimplices)

type NerveSimplexKey :: Type -> Type
type NerveSimplexKey c = (Natural, Ob c, [Mor c])

nerveSimplexKey :: NerveSimplex c -> NerveSimplexKey c
nerveSimplexKey simplexValue =
  ( nerveSimplexDimension simplexValue,
    chainStartObject (nerveSimplexChain simplexValue),
    chainMorphisms (nerveSimplexChain simplexValue)
  )

dedupeSimplices ::
  forall c.
  (Ord (Ob c), Ord (Mor c)) =>
  [NerveSimplex c] ->
  [NerveSimplex c]
dedupeSimplices =
  nubOrdOn nerveSimplexKey

nerveFace ::
  forall c n.
  (Category c, Eq (Ob c)) =>
  c ->
  Dimension (n + 1) ->
  Fin (n + 2) ->
  NerveSimplex c ->
  Maybe (NerveSimplex c)
nerveFace categoryValue _ faceIndex =
  nerveSimplexFace categoryValue (finValue faceIndex)

nerveDegeneracy ::
  forall c n.
  (Category c, Eq (Ob c)) =>
  c ->
  Dimension n ->
  Fin (n + 1) ->
  NerveSimplex c ->
  Maybe (NerveSimplex c)
nerveDegeneracy categoryValue _ degeneracyIndex =
  nerveSimplexDegeneracy categoryValue (finValue degeneracyIndex)

nerveSimplexFace ::
  (Category c, Eq (Ob c)) =>
  c ->
  Natural ->
  NerveSimplex c ->
  Maybe (NerveSimplex c)
nerveSimplexFace categoryValue faceIndex simplexValue = do
  let currentDimension = nerveSimplexDimension simplexValue
  guard (currentDimension > 0)
  faceChainValue <- faceChain categoryValue faceIndex (nerveSimplexChain simplexValue)
  pure (NerveSimplex (currentDimension - 1) faceChainValue)

nerveSimplexDegeneracy ::
  (Category c, Eq (Ob c)) =>
  c ->
  Natural ->
  NerveSimplex c ->
  Maybe (NerveSimplex c)
nerveSimplexDegeneracy categoryValue degeneracyIndex simplexValue = do
  let currentDimension = nerveSimplexDimension simplexValue
  degeneracyChainValue <- degeneracyChain categoryValue degeneracyIndex (nerveSimplexChain simplexValue)
  pure (NerveSimplex (currentDimension + 1) degeneracyChainValue)

splitAtNatural :: Natural -> [a] -> Maybe ([a], [a])
splitAtNatural splitIndex values =
  let (prefix, suffix) = genericSplitAt splitIndex values
   in if genericLength prefix == splitIndex
        then Just (prefix, suffix)
        else Nothing

insertAt :: Natural -> a -> [a] -> Maybe [a]
insertAt indexValue inserted values = do
  (prefix, suffix) <- splitAtNatural indexValue values
  pure (prefix <> (inserted : suffix))

faceChain :: (Category c, Eq (Ob c)) => c -> Natural -> ComposableChain c -> Maybe (ComposableChain c)
faceChain categoryValue faceIndex chainValue
  | faceIndex > dimensionValue' = Nothing
  | otherwise = case morphisms of
      [] -> Nothing
      _ : restMorphisms
        | faceIndex == 0 -> do
            startObject <- safeIndexNatural 1 (NonEmpty.toList (chainVertices chainValue))
            either (const Nothing) Just (mkComposableChain categoryValue startObject restMorphisms)
        | faceIndex == dimensionValue' -> do
            (prefixMorphisms, _) <- unsnoc morphisms
            either (const Nothing) Just (mkComposableChain categoryValue (chainStartObject chainValue) prefixMorphisms)
        | otherwise -> do
            (leftMorphisms, rightMorphisms) <- splitAtNatural faceIndex morphisms
            (leftPrefix, leftMorphism) <- unsnoc leftMorphisms
            case rightMorphisms of
              [] -> Nothing
              rightMorphism : rightSuffix -> do
                (composedMorphism, _) <- either (const Nothing) Just (compose categoryValue rightMorphism leftMorphism)
                either
                  (const Nothing)
                  Just
                  ( mkComposableChain
                      categoryValue
                      (chainStartObject chainValue)
                      (leftPrefix <> (composedMorphism : rightSuffix))
                  )
  where
    morphisms = chainMorphisms chainValue
    dimensionValue' = chainDimension chainValue

degeneracyChain :: (Category c, Eq (Ob c)) => c -> Natural -> ComposableChain c -> Maybe (ComposableChain c)
degeneracyChain categoryValue degeneracyIndex chainValue =
  let dimensionValue' = chainDimension chainValue
   in if degeneracyIndex > dimensionValue'
        then Nothing
        else do
          duplicatedObject <- safeIndexNatural degeneracyIndex (NonEmpty.toList (chainVertices chainValue))
          identityMorphism <- either (const Nothing) Just (identity categoryValue duplicatedObject)
          insertedMorphisms <- insertAt degeneracyIndex identityMorphism (chainMorphisms chainValue)
          either (const Nothing) Just (mkComposableChain categoryValue (chainStartObject chainValue) insertedMorphisms)

sameSimplex :: (Eq (Ob c), Eq (Mor c)) => NerveSimplex c -> NerveSimplex c -> Bool
sameSimplex leftSimplex rightSimplex =
  nerveSimplexDimension leftSimplex == nerveSimplexDimension rightSimplex
    && chainStartObject (nerveSimplexChain leftSimplex) == chainStartObject (nerveSimplexChain rightSimplex)
    && chainMorphisms (nerveSimplexChain leftSimplex) == chainMorphisms (nerveSimplexChain rightSimplex)

fillNerveInnerHorn :: (Category c, Eq (Ob c), Eq (Mor c)) => c -> InnerHorn (NerveSimplex c) -> Maybe (NerveSimplex c)
fillNerveInnerHorn categoryValue innerHornValue = do
      let hornValue = innerHorn innerHornValue
          dimensionValue' = hornDimension hornValue
      leftOuterFace <- Map.lookup 0 (hornFaces hornValue)
      rightOuterFace <- Map.lookup dimensionValue' (hornFaces hornValue)
      guard
        ( nerveSimplexDimension leftOuterFace == dimensionValue' - 1
            && nerveSimplexDimension rightOuterFace == dimensionValue' - 1
        )
      let leftMorphisms = chainMorphisms (nerveSimplexChain leftOuterFace)
          rightMorphisms = chainMorphisms (nerveSimplexChain rightOuterFace)
      firstMorphism <- safeIndexNatural 0 rightMorphisms
      filledChain <-
        either
          (const Nothing)
          Just
          (mkComposableChain categoryValue (chainStartObject (nerveSimplexChain rightOuterFace)) (firstMorphism : leftMorphisms))
      let candidateSimplex = NerveSimplex dimensionValue' filledChain
      checks <-
        traverse
          (\(fi, ef) -> sameSimplex ef . NerveSimplex (dimensionValue' - 1) <$> faceChain categoryValue fi filledChain)
          (Map.toAscList (hornFaces hornValue))
      guard (and checks)
      pure candidateSimplex

fillNerveInnerHornIndexed ::
  (Category c, Eq (Ob c), Eq (Mor c), KnownNat n) =>
  c ->
  IndexedHorn n (NerveSimplex c) ->
  Either InnerHornError (Maybe (NerveSimplex c))
fillNerveInnerHornIndexed categoryValue indexedHornValue =
  fillNerveInnerHorn categoryValue <$> mkInnerHorn (indexedHornToHorn indexedHornValue)

instance (Category c, Eq (Ob c), Eq (Mor c)) => InnerKan (Nerve c) where
  type InnerSimplex (Nerve c) = NerveSimplex c
  fillInnerHorn nerveValue = fillNerveInnerHorn (nerveCategory nerveValue)