packages feed

moonlight-category-1.1.0.0: src-abstract/Moonlight/Category/Pure/FiniteComposable.hs

{-# LANGUAGE ConstrainedClassMethods #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE TypeFamilies #-}

-- | Composable chains of morphisms (and the 'FiniteComposableCategory' class) for
-- enumerating a finite category by chain dimension.
module Moonlight.Category.Pure.FiniteComposable
  ( ComposableChain,
    chainStartObject,
    chainVertices,
    chainMorphisms,
    ComposableChainError (..),
    SizedComposableChain,
    sizedChainDimension,
    sizedChainValue,
    chainDimension,
    chainTerminalObject,
    singletonComposableChain,
    mkComposableChain,
    sizedComposableChain,
    appendComposableMorphism,
    chainsOfDimension,
    FiniteComposableCategory (..),
  )
where

import Data.Bifunctor (first)
import Control.Monad (foldM)
import Data.Function ((&))
import Data.Kind (Constraint, Type)
import Data.List (genericTake)
import Data.List.NonEmpty (NonEmpty (..))
import Data.Maybe (fromMaybe, mapMaybe)
import Numeric.Natural (Natural)
import Moonlight.Category.Pure.Category (Category (..))

type ComposableChain :: Type -> Type
-- | A path whose adjacent morphism endpoints have been validated.  Every stored
-- step carries the target established while appending it, so vertex projection
-- is total and never has to query the category again.  The terminal object is
-- an O(1) view of the newest step; the strict finite step count is an opaque
-- cache with the same representable domain as the previous sequence-length
-- view.
data ComposableChain c = ComposableChain
  { chainStartObjectInternal :: Ob c,
    chainStepCountInternal :: !Int,
    chainValidatedStepsNewestFirst :: [(Mor c, Ob c)]
  }

type SizedComposableChain :: Type -> Type
-- | A validated path paired with its morphism count.
data SizedComposableChain c = SizedComposableChain
  { -- | The number of morphisms in the path.
    sizedChainDimension :: Natural,
    -- | The underlying validated path.
    sizedChainValue :: ComposableChain c
  }

type ComposableChainError :: Type -> Type
-- | A category failure or an endpoint incompatibility encountered while
-- extending a path.
data ComposableChainError c
  = ComposableChainCategoryError (CategoryError c)
  | ComposableChainEndpointMismatch (Ob c) (Ob c)

-- | Count the morphisms in a validated path.
chainDimension :: ComposableChain c -> Natural
chainDimension = fromIntegral . chainStepCountInternal
{-# INLINE chainDimension #-}

-- | The first vertex of a validated path.
chainStartObject :: ComposableChain c -> Ob c
chainStartObject = chainStartObjectInternal
{-# INLINE chainStartObject #-}

-- | The terminal vertex of a validated path.
chainTerminalObject :: ComposableChain c -> Ob c
chainTerminalObject chainValue =
  case chainValidatedStepsNewestFirst chainValue of
    [] -> chainStartObjectInternal chainValue
    (_, terminalObject) : _ -> terminalObject
{-# INLINE chainTerminalObject #-}

-- | The nonempty vertex sequence established by chain construction, in path
-- order.  Its final vertex is 'chainTerminalObject'.
chainVertices :: ComposableChain c -> NonEmpty (Ob c)
chainVertices chainValue =
  chainStartObjectInternal chainValue
    :| foldl'
      (\targets (_, targetObject) -> targetObject : targets)
      []
      (chainValidatedStepsNewestFirst chainValue)

-- | Project the morphisms in composition order.
chainMorphisms :: ComposableChain c -> [Mor c]
chainMorphisms chainValue =
  foldl'
    (\morphisms (morphism, _) -> morphism : morphisms)
    []
    (chainValidatedStepsNewestFirst chainValue)

-- | The dimension-zero path at an object.
singletonComposableChain :: Ob c -> ComposableChain c
singletonComposableChain objectValue =
  ComposableChain objectValue 0 []

-- | Validate a morphism sequence from a declared start object.
mkComposableChain :: (Category c, Eq (Ob c)) => c -> Ob c -> [Mor c] -> Either (ComposableChainError c) (ComposableChain c)
mkComposableChain categoryValue startObject =
  foldM (appendComposableMorphism categoryValue) (singletonComposableChain startObject)

-- | Attach the derived dimension to a validated path.
sizedComposableChain :: ComposableChain c -> SizedComposableChain c
sizedComposableChain chainValue =
  SizedComposableChain
    { sizedChainDimension = chainDimension chainValue,
      sizedChainValue = chainValue
    }

-- | Extend a path when the new morphism starts at its terminal object.
appendComposableMorphism :: (Category c, Eq (Ob c)) => c -> ComposableChain c -> Mor c -> Either (ComposableChainError c) (ComposableChain c)
appendComposableMorphism categoryValue chainValue morphism = do
  morphismSource <- first ComposableChainCategoryError (source categoryValue morphism)
  morphismTarget <- first ComposableChainCategoryError (target categoryValue morphism)
  let terminalObject = chainTerminalObject chainValue
  if morphismSource == terminalObject
    then
      Right
        ComposableChain
          { chainStartObjectInternal = chainStartObjectInternal chainValue,
            chainStepCountInternal = chainStepCountInternal chainValue + 1,
            chainValidatedStepsNewestFirst = (morphism, morphismTarget) : chainValidatedStepsNewestFirst chainValue
          }
    else Left (ComposableChainEndpointMismatch terminalObject morphismSource)

-- | Enumerate all composable paths at one exact dimension.
chainsOfDimension :: FiniteComposableCategory c => c -> Natural -> [ComposableChain c]
chainsOfDimension categoryValue dimensionBound =
  fromMaybe [] (indexByNatural dimensionBound (chainsByDimension categoryValue))

indexByNatural :: Natural -> [a] -> Maybe a
indexByNatural indexValue values =
  case (indexValue, values) of
    (_, []) -> Nothing
    (0, value : _) -> Just value
    (_, _ : rest) -> indexByNatural (indexValue - 1) rest

chainsByDimension :: FiniteComposableCategory c => c -> [[ComposableChain c]]
chainsByDimension categoryValue =
  iterate (extendChains categoryValue) (map singletonComposableChain (enumerateObjects categoryValue))

extendChains :: FiniteComposableCategory c => c -> [ComposableChain c] -> [ComposableChain c]
extendChains categoryValue chains =
  chains
    >>= ( \chainValue ->
            mapMaybe
              (either (const Nothing) Just . appendComposableMorphism categoryValue chainValue)
              (enumerateMorphismsFrom categoryValue (chainTerminalObject chainValue))
        )

extendGrowingChainsNonIdentity ::
  (FiniteComposableCategory c, Eq (Mor c)) =>
  c ->
  [ComposableChain c] ->
  [ComposableChain c]
extendGrowingChainsNonIdentity categoryValue chains =
  chains
    >>= ( \chainValue ->
            let terminalObject = chainTerminalObject chainValue
                terminalIdentity =
                  either (const Nothing) Just (identity categoryValue terminalObject)
             in enumerateMorphismsFrom categoryValue terminalObject
                  & mapMaybe
                    ( \morphism -> do
                        if Just morphism == terminalIdentity
                          then Nothing
                          else either (const Nothing) Just (appendComposableMorphism categoryValue chainValue morphism)
                    )
        )

type FiniteComposableCategory :: Type -> Constraint
-- | A finite category whose objects, morphisms, and composable paths can be
-- enumerated.
class (Category c, Eq (Ob c)) => FiniteComposableCategory c where
  enumerateObjects :: c -> [Ob c]
  enumerateMorphisms :: c -> [Mor c]
  enumerateMorphismsFrom :: c -> Ob c -> [Mor c]
  enumerateMorphismsFrom categoryValue sourceObject =
    enumerateMorphisms categoryValue
      & filter
        ( \morphism ->
            case source categoryValue morphism of
              Right morphismSource -> morphismSource == sourceObject
              Left _ -> False
        )

  enumerateComposableChains :: c -> Natural -> [SizedComposableChain c]
  default enumerateComposableChains :: c -> Natural -> [SizedComposableChain c]
  enumerateComposableChains categoryValue dimensionBound =
    genericTake (dimensionBound + 1) (chainsByDimension categoryValue)
      & foldMap (fmap sizedComposableChain)

  enumerateNonDegenerateChainsByDimension :: Eq (Mor c) => c -> Natural -> [[ComposableChain c]]
  enumerateNonDegenerateChainsByDimension categoryValue dimensionBound =
    genericTake
      (dimensionBound + 1)
      ( iterate
          (extendGrowingChainsNonIdentity categoryValue)
          (fmap singletonComposableChain (enumerateObjects categoryValue))
      )