packages feed

moonlight-category-0.1.0.0: src-site/Moonlight/Category/Pure/Site/Compile.hs

-- | Compilation of a thin site presentation to a runtime-validated finite category,
-- with object and morphism lookup.
module Moonlight.Category.Pure.Site.Compile
  ( ThinSitePresentation (..),
    ThinSiteKernel,
    thinSiteKernelManifest,
    thinSiteKernelCodomain,
    thinSiteKernelObjectIds,
    ThinSiteLookupError (..),
    thinSitePresentation,
    thinPresentationToFinCat,
    thinSiteKernel,
    thinSiteFinObject,
    thinSiteFinMorphism,
    thinSiteFinMorphismByEndpoints,
    siteImportsAsFinCat,
  )
where

import Data.Kind (Type)
import Data.Bifunctor (first)
import Data.Function ((&))
import Data.List.NonEmpty (NonEmpty)
import qualified Data.List.NonEmpty as NonEmpty
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Data.Set (Set)
import qualified Data.Set as Set
import qualified Data.Vector as Vector
import Moonlight.Category.Pure.Category (Category (identity))
import Moonlight.Category.Pure.FinCat
  ( FinCat,
    FinCatError,
    FinCatValidationError,
    FinMor,
    FinMorphismId (..),
    FinObjectId (..),
    FinObj,
    mkFinCat,
    mkFinMorphism,
    mkFinObject,
    denseThinEndpointMorphismsFromCategory,
    finCatExplicitCompositionMapView,
    finCatMorphismIdByEndpoints,
    trustedDenseThinFinCatFromReachabilityRows,
  )
import Moonlight.Category.Pure.Site.Core (SiteFinCatError (..), SiteManifest)
import Moonlight.Category.Pure.Site.Manifest
  ( validateSiteImportManifest,
    validateSiteManifestDetailed,
    validatedSiteObjectVector,
    validatedSiteReachabilityRows,
  )

type ThinSitePresentation :: Type -> Type
data ThinSitePresentation obj = ThinSitePresentation
  { thinPresentationObjectIds :: Map obj FinObjectId,
    thinPresentationPairIds :: Map (obj, obj) FinMorphismId,
    thinPresentationObjects :: Set FinObjectId,
    thinPresentationMorphisms :: Map (FinObjectId, FinObjectId) [FinMorphismId],
    thinPresentationComposition :: Map (FinMorphismId, FinMorphismId) FinMorphismId
  }

type ThinSiteKernel :: Type -> Type
data ThinSiteKernel obj = ThinSiteKernel
  { thinSiteKernelManifest :: SiteManifest obj,
    thinSiteKernelCodomain :: FinCat,
    thinSiteKernelObjectIds :: Map obj FinObjectId
  }
  deriving stock (Eq, Show)

type ThinSiteLookupError :: Type -> Type
data ThinSiteLookupError obj
  = ThinSiteUnknownObject obj
  | ThinSiteCodomainObjectMissing FinObjectId
  | ThinSiteUnknownMorphismPair obj obj
  | ThinSiteCodomainMorphismMissing FinMorphismId
  | ThinSiteCodomainMorphismInvalid FinCatError
  deriving stock (Eq, Show)

-- | Builds the explicit presentation, including the materialized composition
-- table via 'finCatExplicitCompositionMapView' — an output-bound @Θ(n³)@ witness
-- for a linear site on @n@ objects, dominating the @Θ(n²/w)@ dense validation
-- that precedes it. The record fields are lazy, so the cubic table is only paid
-- when 'thinPresentationComposition' is forced. Callers that need composition
-- queries rather than the explicit witness should use 'thinSiteKernel', which
-- stays on the dense handle and answers composition in
-- @O(1)@ without materializing.
thinSitePresentation :: ThinSiteKernel obj -> ThinSitePresentation obj
thinSitePresentation kernel =
  let objectIds = thinSiteKernelObjectIds kernel
      codomain = thinSiteKernelCodomain kernel
      objectSet = thinSiteFinObjectSet objectIds
      endpointPairIds = denseThinEndpointMorphismsFromCategory codomain
   in ThinSitePresentation
        { thinPresentationObjectIds = objectIds,
          thinPresentationPairIds = thinSitePairIdsFromEndpoints objectIds endpointPairIds,
          thinPresentationObjects = objectSet,
          thinPresentationMorphisms = thinSiteMorphismMap endpointPairIds,
          thinPresentationComposition = finCatExplicitCompositionMapView codomain
        }

thinSitePairIdsFromEndpoints :: Map obj FinObjectId -> Map (FinObjectId, FinObjectId) FinMorphismId -> Map (obj, obj) FinMorphismId
thinSitePairIdsFromEndpoints objectIds endpointPairIds =
  objectIds
    & Map.toAscList
    >>= ( \(sourceObject, sourceId) ->
            objectIds
              & Map.toAscList
              >>= ( \(targetObject, targetId) ->
                      case Map.lookup (sourceId, targetId) endpointPairIds of
                        Nothing -> []
                        Just morphismId -> [((sourceObject, targetObject), morphismId)]
                  )
        )
    & Map.fromDistinctAscList

thinSiteObjectIds :: Ord obj => [obj] -> Map obj FinObjectId
thinSiteObjectIds objects =
  objects
    & zip [0 ..]
    & fmap (\(idx, obj) -> (obj, FinObjectId idx))
    & Map.fromList

thinSiteFinObjectSet :: Map obj FinObjectId -> Set FinObjectId
thinSiteFinObjectSet objectIds =
  objectIds
    & Map.elems
    & Set.fromList

thinSiteMorphismMap :: Map (FinObjectId, FinObjectId) FinMorphismId -> Map (FinObjectId, FinObjectId) [FinMorphismId]
thinSiteMorphismMap =
  fmap (: [])

thinPresentationToFinCat ::
  ThinSitePresentation obj ->
  Either (NonEmpty FinCatValidationError) FinCat
thinPresentationToFinCat presentation =
  mkFinCat
    (thinPresentationObjects presentation)
    (thinPresentationMorphisms presentation)
    (thinPresentationComposition presentation)

thinSiteKernel :: Ord obj => SiteManifest obj -> Either (SiteFinCatError obj) (ThinSiteKernel obj)
thinSiteKernel manifest =
  case validateSiteManifestDetailed manifest of
    Right validatedManifest ->
      let objectIds = thinSiteObjectIds (Vector.toList (validatedSiteObjectVector validatedManifest))
          codomain =
            trustedDenseThinFinCatFromReachabilityRows
              (thinSiteFinObjectSet objectIds)
              (validatedSiteReachabilityRows validatedManifest)
       in Right
            ThinSiteKernel
              { thinSiteKernelManifest = manifest,
                thinSiteKernelCodomain = codomain,
                thinSiteKernelObjectIds = objectIds
              }
    Left errors ->
      Left (SiteManifestInvalid errors)

thinSiteFinObject :: Ord obj => ThinSiteKernel obj -> obj -> Either (ThinSiteLookupError obj) FinObj
thinSiteFinObject kernel objectValue =
  case Map.lookup objectValue (thinSiteKernelObjectIds kernel) of
    Nothing ->
      Left (ThinSiteUnknownObject objectValue)
    Just objectId ->
      case mkFinObject (thinSiteKernelCodomain kernel) objectId of
        Left _ ->
          Left (ThinSiteCodomainObjectMissing objectId)
        Right finObject ->
          Right finObject

thinSiteFinMorphism :: Ord obj => ThinSiteKernel obj -> NonEmpty obj -> Either (ThinSiteLookupError obj) FinMor
thinSiteFinMorphism kernel nodes =
  thinSiteFinMorphismByEndpoints
    kernel
    (NonEmpty.head nodes)
    (NonEmpty.last nodes)

thinSiteFinMorphismByEndpoints ::
  Ord obj =>
  ThinSiteKernel obj ->
  obj ->
  obj ->
  Either (ThinSiteLookupError obj) FinMor
thinSiteFinMorphismByEndpoints kernel sourceValue targetValue =
  if sourceValue == targetValue
    then do
      sourceObject <- thinSiteFinObject kernel sourceValue
      first ThinSiteCodomainMorphismInvalid (identity (thinSiteKernelCodomain kernel) sourceObject)
    else
      case thinSiteMorphismIdByEndpoints kernel sourceValue targetValue of
        Nothing ->
          Left (ThinSiteUnknownMorphismPair sourceValue targetValue)
        Just morId ->
          case mkFinMorphism (thinSiteKernelCodomain kernel) morId of
            Left _ ->
              Left (ThinSiteCodomainMorphismMissing morId)
            Right finMorphism ->
              Right finMorphism

thinSiteMorphismIdByEndpoints :: Ord obj => ThinSiteKernel obj -> obj -> obj -> Maybe FinMorphismId
thinSiteMorphismIdByEndpoints kernel sourceValue targetValue = do
  sourceId <- Map.lookup sourceValue (thinSiteKernelObjectIds kernel)
  targetId <- Map.lookup targetValue (thinSiteKernelObjectIds kernel)
  finCatMorphismIdByEndpoints (thinSiteKernelCodomain kernel) sourceId targetId

-- | Compile only the import category. Cover axioms are deliberately outside this
-- boundary; use 'thinSiteKernel' when a validated site is required.
siteImportsAsFinCat :: Ord obj => SiteManifest obj -> Either (SiteFinCatError obj) FinCat
siteImportsAsFinCat manifest =
  case validateSiteImportManifest manifest of
    Right validatedManifest ->
      let objectIds = thinSiteObjectIds (Vector.toList (validatedSiteObjectVector validatedManifest))
       in Right
            ( trustedDenseThinFinCatFromReachabilityRows
                (thinSiteFinObjectSet objectIds)
                (validatedSiteReachabilityRows validatedManifest)
            )
    Left errors ->
      Left (SiteManifestInvalid errors)