packages feed

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

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE RoleAnnotations #-}

-- | Compilation of a thin site presentation to a runtime-validated finite category,
-- with kernel-relative object and morphism lookup.
module Moonlight.Category.Pure.Site.Compile
  ( ThinSiteValidation (..),
    ThinSiteKernel,
    thinSiteKernelManifest,
    thinSiteKernelCodomain,
    ThinSiteLookupError (..),
    ThinSiteObjectValueError (..),
    ThinSitePresentation (..),
    thinSitePresentation,
    thinPresentationToFinCat,
    thinSiteImportKernel,
    thinSiteKernel,
    thinSiteFinObject,
    thinSiteObjectValue,
    thinSiteFinMorphism,
    thinSiteFinMorphismByEndpoints,
  )
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,
    FinCatHandle,
    FinCatError,
    FinCatValidationError,
    FinMor,
    FinMorphismId (..),
    FinObjectId (..),
    FinObj,
    mkFinCat,
    mkFinMorphism,
    mkFinObject,
    denseThinEndpointMorphismsFromCategory,
    finCatExplicitCompositionMapView,
    finCatHandle,
    finCatMorphismIdByEndpoints,
    finObjCategoryHandle,
    finObjId,
    trustedDenseThinFinCatFromReachabilityRows,
  )
import Moonlight.Category.Pure.Site.Core (SiteFinCatError (..), SiteManifest, SiteViolation)
import Moonlight.Category.Pure.Site.Manifest
  ( ValidatedSiteManifest,
    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
  }

-- | The validation obligation discharged before compiling a 'ThinSiteKernel'.
--
-- An import kernel validates only the import graph. A site kernel additionally
-- proves the cover axioms required by path and quotient construction.
type ThinSiteValidation :: Type
data ThinSiteValidation
  = ImportsValidated
  | SiteValidated
  deriving stock (Eq, Show)

-- | A finite import category together with the exact manifest-local
-- correspondence between semantic objects and its opaque finite objects.
--
-- 'FinObjectId' values produced here are representation tokens relative to this
-- kernel, not semantic object names or a stable ordering contract. Use
-- 'thinSiteFinObject' and 'thinSiteObjectValue' rather than reconstructing the
-- ascending-set enumeration.
--
-- The validation index is nominal: an import-only kernel cannot be coerced into
-- the full-site evidence required by path and quotient construction.
type ThinSiteKernel :: ThinSiteValidation -> Type -> Type
type role ThinSiteKernel nominal nominal
data ThinSiteKernel validation obj = ThinSiteKernel
  { thinSiteKernelManifest :: SiteManifest obj,
    thinSiteKernelCodomain :: FinCat,
    thinSiteKernelObjectIds :: Map obj FinObjectId,
    thinSiteKernelObjectValues :: Vector.Vector obj
  }
  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)

-- | Obstructions specific to inverting a finite object through a site kernel.
--
-- These are intentionally distinct from 'ThinSiteLookupError': callers that
-- only construct finite objects or morphisms do not acquire impossible inverse
-- lookup cases in their error algebra.
type ThinSiteObjectValueError :: Type
data ThinSiteObjectValueError
  = ThinSiteForeignCodomainObject FinCatHandle FinCatHandle FinObjectId
  | ThinSiteUnmappedCodomainObject FinObjectId
  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 a 'ThinSiteKernel', which
-- stays on the dense handle and answers composition in
-- @O(1)@ without materializing.
thinSitePresentation :: ThinSiteKernel validation 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)

-- | Compile the import category while deliberately leaving cover validation
-- outside the obligation. The resulting kernel cannot construct a
-- 'SitePathCategory' or a 'SitePathQuotient'.
thinSiteImportKernel :: Ord obj => SiteManifest obj -> Either (SiteFinCatError obj) (ThinSiteKernel 'ImportsValidated obj)
thinSiteImportKernel =
  compileThinSiteKernel validateSiteImportManifest

-- | Compile a full site kernel after validating both imports and cover axioms.
thinSiteKernel :: Ord obj => SiteManifest obj -> Either (SiteFinCatError obj) (ThinSiteKernel 'SiteValidated obj)
thinSiteKernel =
  compileThinSiteKernel validateSiteManifestDetailed

compileThinSiteKernel ::
  Ord obj =>
  (SiteManifest obj -> Either (NonEmpty (SiteViolation obj)) (ValidatedSiteManifest obj)) ->
  SiteManifest obj ->
  Either (SiteFinCatError obj) (ThinSiteKernel validation obj)
compileThinSiteKernel validateManifest manifest =
  case validateManifest manifest of
    Left errors -> Left (SiteManifestInvalid errors)
    Right validatedManifest ->
      Right (thinSiteKernelFromValidatedManifest manifest validatedManifest)

thinSiteKernelFromValidatedManifest ::
  Ord obj =>
  SiteManifest obj ->
  ValidatedSiteManifest obj ->
  ThinSiteKernel validation obj
thinSiteKernelFromValidatedManifest manifest validatedManifest =
  let objectValues = validatedSiteObjectVector validatedManifest
      objectIds = thinSiteObjectIds (Vector.toList objectValues)
      codomain =
        trustedDenseThinFinCatFromReachabilityRows
          (thinSiteFinObjectSet objectIds)
          (validatedSiteReachabilityRows validatedManifest)
   in ThinSiteKernel
        { thinSiteKernelManifest = manifest,
          thinSiteKernelCodomain = codomain,
          thinSiteKernelObjectIds = objectIds,
          thinSiteKernelObjectValues = objectValues
        }

thinSiteFinObject :: Ord obj => ThinSiteKernel validation 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

-- | Recover the semantic manifest object for an object from this kernel's
-- codomain. The category-handle check rejects objects from a different finite
-- category; a correctly handled object without an entry is reported as an
-- explicit unmapped-codomain obstruction rather than silently reusing its
-- numeric identifier.
thinSiteObjectValue :: ThinSiteKernel validation obj -> FinObj -> Either ThinSiteObjectValueError obj
thinSiteObjectValue kernel finObject
  | finObjCategoryHandle finObject /= expectedHandle =
      Left
        ( ThinSiteForeignCodomainObject
            expectedHandle
            (finObjCategoryHandle finObject)
            (finObjId finObject)
        )
  | otherwise =
      case finObjId finObject of
        objectId@(FinObjectId objectIndex) ->
          case thinSiteKernelObjectValues kernel Vector.!? objectIndex of
            Nothing -> Left (ThinSiteUnmappedCodomainObject objectId)
            Just objectValue -> Right objectValue
  where
    expectedHandle = finCatHandle (thinSiteKernelCodomain kernel)

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

thinSiteFinMorphismByEndpoints ::
  Ord obj =>
  ThinSiteKernel validation 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 validation 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