packages feed

moonlight-category-0.1.0.0: src-simplicial/Moonlight/Category/Pure/Simplicial/Set.hs

-- | Finite truncated simplicial sets generated from indexed simplices, with
-- typed well-formedness obstructions.
module Moonlight.Category.Pure.Simplicial.Set
  ( GeneratedSSet,
    generationBound,
    GeneratedSSetObstruction (..),
    GeneratedSSetCheck,
    IndexedSimplex,
    unindexSimplex,
    SomeIndexedSimplex (..),
    indexSimplexIn,
    mkGeneratedSSet,
    validateGeneratedSSet,
    generatedSimplicesAt,
    generatedSimplicesAtDimension,
    applyGeneratedFaceAtDimension,
    applyGeneratedDegeneracyAtDimension,
    TruncatedNormalizedSSet,
    truncationBound,
    TruncatedSSetObstruction (..),
    TruncatedSSetCheck,
    normalizeGeneratedSSet,
    mkTruncatedSSet,
    validateTruncatedSSet,
    simplicesAt,
    simplicesAtDimension,
    applyFaceAtDimension,
    applyDegeneracyAtDimension,
    faceIndexed,
    degeneracyIndexed,
  )
where

import Data.Function ((&))
import Data.Foldable (toList)
import Data.Kind (Type)
import Data.List.NonEmpty (NonEmpty)
import Data.Maybe (isNothing)
import qualified Data.Map.Strict as Map
import GHC.TypeNats (KnownNat, Nat, type (+))
import Numeric.Natural (Natural)
import Moonlight.Category.Pure.Simplicial.Validation.Internal
  ( SimplicialLawCarrier (..),
    SimplicialLawIndices (..),
    SimplicialLawObstruction (..),
    checkObstructions,
    checkSimplicialLawsBy,
    dimensionsStrictlyBelow,
    simplicialLawEq,
  )
import Moonlight.Category.Pure.Simplicial.Set.Internal
  ( GeneratedSSet (..),
    TruncatedNormalizedSSet (..),
    trustedGeneratedSSet,
    trustedTruncatedNormalizedSSet,
  )
import Moonlight.Category.Pure.Simplicial.TypeLevel (Dimension (..), Fin, dimensionValue, withReifiedFinOffset)

type GeneratedSSetObstruction :: Type -> Type
data GeneratedSSetObstruction simplex
  = GeneratedFaceUndefined Natural Natural simplex
  | GeneratedFaceOutsideCarrier Natural Natural simplex simplex
  | GeneratedDegeneracyUndefined Natural Natural simplex
  | GeneratedDegeneracyOutsideCarrier Natural Natural simplex simplex
  | GeneratedFaceFaceMismatch Natural simplex Natural Natural (Maybe simplex) (Maybe simplex)
  | GeneratedDegeneracyDegeneracyMismatch Natural simplex Natural Natural (Maybe simplex) (Maybe simplex)
  | GeneratedFaceDegeneracyMismatch Natural simplex Natural Natural (Maybe simplex) (Maybe simplex)
  deriving stock (Eq, Show)

type GeneratedSSetCheck :: Type -> Type
type GeneratedSSetCheck simplex = Either (NonEmpty (GeneratedSSetObstruction simplex)) ()

type TruncatedSSetObstruction :: Type -> Type
data TruncatedSSetObstruction simplex
  = TruncatedRowOutsideBound Natural
  | TruncatedFaceUndefined Natural Natural simplex
  | TruncatedDegeneracyUndefined Natural Natural simplex
  | TruncatedLawViolation (SimplicialLawObstruction simplex)
  deriving stock (Eq, Show)

type TruncatedSSetCheck :: Type -> Type
type TruncatedSSetCheck simplex = Either (NonEmpty (TruncatedSSetObstruction simplex)) ()

type IndexedSimplex :: Nat -> Type -> Type
newtype IndexedSimplex (n :: Nat) simplex = IndexedSimplex
  { unindexSimplex :: simplex
  }
  deriving stock (Eq, Ord, Show)

type SomeIndexedSimplex :: Type -> Type
data SomeIndexedSimplex simplex where
  SomeIndexedSimplex :: KnownNat n => Dimension n -> IndexedSimplex n simplex -> SomeIndexedSimplex simplex

indexSimplexIn :: (Eq simplex, KnownNat n) => TruncatedNormalizedSSet simplex -> Dimension n -> simplex -> Maybe (IndexedSimplex n simplex)
indexSimplexIn simplicialSet dimensionWitness simplexValue =
  if simplexValue `elem` simplicesAt simplicialSet dimensionWitness
    then Just (IndexedSimplex simplexValue)
    else Nothing

mkGeneratedSSet ::
  Eq simplex =>
  Natural ->
  (Natural -> [simplex]) ->
  (forall n. KnownNat n => Dimension (n + 1) -> Fin (n + 2) -> simplex -> Maybe simplex) ->
  (forall n. KnownNat n => Dimension n -> Fin (n + 1) -> simplex -> Maybe simplex) ->
  Either (NonEmpty (GeneratedSSetObstruction simplex)) (GeneratedSSet simplex)
mkGeneratedSSet upperBound simplicesFunction faceFunction degeneracyFunction =
  let generatedSet = trustedGeneratedSSet upperBound simplicesFunction faceFunction degeneracyFunction
   in generatedSet <$ validateGeneratedSSet generatedSet

validateGeneratedSSet :: Eq simplex => GeneratedSSet simplex -> GeneratedSSetCheck simplex
validateGeneratedSSet generatedSet =
  checkObstructions
    ( faceClosureObstructions generatedSet
        <> degeneracyClosureObstructions generatedSet
        <> generatedLawObstructions generatedSet
    )

generatedSimplicesAt :: forall n simplex. KnownNat n => GeneratedSSet simplex -> Dimension n -> [simplex]
generatedSimplicesAt generatedSet dimensionWitness =
  generatedSimplicesAtDimension generatedSet (dimensionValue dimensionWitness)

generatedSimplicesAtDimension :: GeneratedSSet simplex -> Natural -> [simplex]
generatedSimplicesAtDimension generatedSet dimensionValue' =
  if dimensionValue' <= generationBound generatedSet
    then generatedSimplicesByDimension generatedSet dimensionValue'
    else []

applyGeneratedFaceAtDimension :: GeneratedSSet simplex -> Natural -> Natural -> simplex -> Maybe simplex
applyGeneratedFaceAtDimension generatedSet simplexDimension faceIndex simplexValue
  | simplexDimension == 0 = Nothing
  | simplexDimension > generationBound generatedSet = Nothing
  | faceIndex > simplexDimension = Nothing
  | otherwise = withReifiedFinOffset @2 (simplexDimension - 1) faceIndex $ \(_ :: Dimension n) finiteIndex ->
      generatedFaceMap generatedSet (Dimension @(n + 1)) finiteIndex simplexValue

applyGeneratedDegeneracyAtDimension :: GeneratedSSet simplex -> Natural -> Natural -> simplex -> Maybe simplex
applyGeneratedDegeneracyAtDimension generatedSet simplexDimension degeneracyIndex simplexValue
  | simplexDimension >= generationBound generatedSet = Nothing
  | degeneracyIndex > simplexDimension = Nothing
  | otherwise = withReifiedFinOffset @1 simplexDimension degeneracyIndex $ \dimensionWitness finiteIndex ->
      generatedDegeneracyMap generatedSet dimensionWitness finiteIndex simplexValue

generatedCarrierContains :: Eq simplex => GeneratedSSet simplex -> Natural -> simplex -> Bool
generatedCarrierContains generatedSet dimensionValue' simplexValue =
  simplexValue `elem` generatedSimplicesAtDimension generatedSet dimensionValue'

faceClosureObstructions :: Eq simplex => GeneratedSSet simplex -> [GeneratedSSetObstruction simplex]
faceClosureObstructions generatedSet =
  [ obstruction
    | simplexDimension <- [1 .. generationBound generatedSet],
      simplexValue <- generatedSimplicesAtDimension generatedSet simplexDimension,
      faceIndex <- [0 .. simplexDimension],
      obstruction <-
        case applyGeneratedFaceAtDimension generatedSet simplexDimension faceIndex simplexValue of
          Nothing -> [GeneratedFaceUndefined simplexDimension faceIndex simplexValue]
          Just faceValue ->
            if generatedCarrierContains generatedSet (simplexDimension - 1) faceValue
              then []
              else [GeneratedFaceOutsideCarrier simplexDimension faceIndex simplexValue faceValue]
  ]

degeneracyClosureObstructions :: Eq simplex => GeneratedSSet simplex -> [GeneratedSSetObstruction simplex]
degeneracyClosureObstructions generatedSet =
  [ obstruction
    | simplexDimension <- dimensionsStrictlyBelow (generationBound generatedSet),
      simplexValue <- generatedSimplicesAtDimension generatedSet simplexDimension,
      degeneracyIndex <- [0 .. simplexDimension],
      obstruction <-
        case applyGeneratedDegeneracyAtDimension generatedSet simplexDimension degeneracyIndex simplexValue of
          Nothing -> [GeneratedDegeneracyUndefined simplexDimension degeneracyIndex simplexValue]
          Just degeneracyValue ->
            if generatedCarrierContains generatedSet (simplexDimension + 1) degeneracyValue
              then []
              else [GeneratedDegeneracyOutsideCarrier simplexDimension degeneracyIndex simplexValue degeneracyValue]
  ]

generatedLawCarrier :: GeneratedSSet simplex -> SimplicialLawCarrier simplex
generatedLawCarrier generatedSet =
  SimplicialLawCarrier
    { lawCarrierUpperBound = generationBound generatedSet,
      lawCarrierSimplicesAtDimension = generatedSimplicesAtDimension generatedSet,
      lawCarrierFaceAtDimension = applyGeneratedFaceAtDimension generatedSet,
      lawCarrierDegeneracyAtDimension = applyGeneratedDegeneracyAtDimension generatedSet
    }

generatedLawObstruction :: SimplicialLawObstruction simplex -> GeneratedSSetObstruction simplex
generatedLawObstruction obstruction =
  case lawObstructionIndices obstruction of
    FaceFaceIndices leftFaceIndex rightFaceIndex ->
      GeneratedFaceFaceMismatch
        (lawObstructionDimension obstruction)
        (lawObstructionSimplex obstruction)
        leftFaceIndex
        rightFaceIndex
        (lawObstructionLeftResult obstruction)
        (lawObstructionRightResult obstruction)
    DegeneracyDegeneracyIndices leftDegeneracyIndex rightDegeneracyIndex ->
      GeneratedDegeneracyDegeneracyMismatch
        (lawObstructionDimension obstruction)
        (lawObstructionSimplex obstruction)
        leftDegeneracyIndex
        rightDegeneracyIndex
        (lawObstructionLeftResult obstruction)
        (lawObstructionRightResult obstruction)
    FaceDegeneracyIndices faceIndex degeneracyIndex ->
      GeneratedFaceDegeneracyMismatch
        (lawObstructionDimension obstruction)
        (lawObstructionSimplex obstruction)
        faceIndex
        degeneracyIndex
        (lawObstructionLeftResult obstruction)
        (lawObstructionRightResult obstruction)

generatedLawObstructions :: Eq simplex => GeneratedSSet simplex -> [GeneratedSSetObstruction simplex]
generatedLawObstructions generatedSet =
  case checkSimplicialLawsBy simplicialLawEq (generatedLawCarrier generatedSet) of
    Right () -> []
    Left obstructions -> generatedLawObstruction <$> toList obstructions

canonicalizeDimension :: (Natural -> simplex -> Bool) -> Natural -> [simplex] -> [simplex]
canonicalizeDimension isDegenerate dimensionValue' simplices =
  simplices
    & filter (not . isDegenerate dimensionValue')

normalizeGeneratedSSet :: GeneratedSSet simplex -> TruncatedNormalizedSSet simplex
normalizeGeneratedSSet generatedSet =
  let upperBound = generationBound generatedSet
      canonicalRows =
        [0 .. upperBound]
          & foldr
            ( \dimensionValue' ->
                let canonicalRow =
                      canonicalizeDimension
                        (generatedDegenerateWitness generatedSet)
                        dimensionValue'
                        (generatedSimplicesByDimension generatedSet dimensionValue')
                 in if null canonicalRow
                      then id
                      else Map.insert dimensionValue' canonicalRow
            )
            Map.empty
   in trustedTruncatedNormalizedSSet
        upperBound
        canonicalRows
        (generatedFaceMap generatedSet)
        (generatedDegeneracyMap generatedSet)

mkTruncatedSSet ::
  Eq simplex =>
  Natural ->
  [(Natural, [simplex])] ->
  (forall n. KnownNat n => Dimension (n + 1) -> Fin (n + 2) -> simplex -> Maybe simplex) ->
  (forall n. KnownNat n => Dimension n -> Fin (n + 1) -> simplex -> Maybe simplex) ->
  (Natural -> simplex -> Bool) ->
  Either (NonEmpty (TruncatedSSetObstruction simplex)) (TruncatedNormalizedSSet simplex)
mkTruncatedSSet upperBound levelRows faceFunction degeneracyFunction degeneracyWitness =
  let checkedRows =
        levelRows
          & foldr
            ( \(dimensionValue', simplices) (outsideBounds, accumulatedLevelMap) ->
                if dimensionValue' <= upperBound
                  then (outsideBounds, Map.insertWith (<>) dimensionValue' simplices accumulatedLevelMap)
                  else (TruncatedRowOutsideBound dimensionValue' : outsideBounds, accumulatedLevelMap)
            )
            ([], Map.empty)
      (rowObstructions, levelMap) = checkedRows
      canonicalRows =
        Map.mapWithKey
          (\dimensionValue' -> filter (not . degeneracyWitness dimensionValue'))
          levelMap
          & Map.filter (not . null)
      simplicialSet = trustedTruncatedNormalizedSSet upperBound canonicalRows faceFunction degeneracyFunction
   in simplicialSet <$ checkObstructions (rowObstructions <> truncatedValidationObstructions simplicialSet)

validateTruncatedSSet :: Eq simplex => TruncatedNormalizedSSet simplex -> TruncatedSSetCheck simplex
validateTruncatedSSet =
  checkObstructions . truncatedValidationObstructions

truncatedLawCarrier :: TruncatedNormalizedSSet simplex -> SimplicialLawCarrier simplex
truncatedLawCarrier simplicialSet =
  SimplicialLawCarrier
    { lawCarrierUpperBound = truncationBound simplicialSet,
      lawCarrierSimplicesAtDimension = simplicesAtDimension simplicialSet,
      lawCarrierFaceAtDimension = applyFaceAtDimension simplicialSet,
      lawCarrierDegeneracyAtDimension = applyDegeneracyAtDimension simplicialSet
    }

truncatedLawObstructions :: Eq simplex => TruncatedNormalizedSSet simplex -> [TruncatedSSetObstruction simplex]
truncatedLawObstructions simplicialSet =
  case checkSimplicialLawsBy simplicialLawEq (truncatedLawCarrier simplicialSet) of
    Right () -> []
    Left obstructions -> TruncatedLawViolation <$> toList obstructions

truncatedFaceTotalityObstructions :: TruncatedNormalizedSSet simplex -> [TruncatedSSetObstruction simplex]
truncatedFaceTotalityObstructions simplicialSet =
  [ TruncatedFaceUndefined simplexDimension faceIndex simplexValue
  | simplexDimension <- [1 .. truncationBound simplicialSet],
    simplexValue <- simplicesAtDimension simplicialSet simplexDimension,
    faceIndex <- [0 .. simplexDimension],
    isNothing (applyFaceAtDimension simplicialSet simplexDimension faceIndex simplexValue)
  ]

truncatedDegeneracyTotalityObstructions :: TruncatedNormalizedSSet simplex -> [TruncatedSSetObstruction simplex]
truncatedDegeneracyTotalityObstructions simplicialSet =
  [ TruncatedDegeneracyUndefined simplexDimension degeneracyIndex simplexValue
  | simplexDimension <- dimensionsStrictlyBelow (truncationBound simplicialSet),
    simplexValue <- simplicesAtDimension simplicialSet simplexDimension,
    degeneracyIndex <- [0 .. simplexDimension],
    isNothing (applyDegeneracyAtDimension simplicialSet simplexDimension degeneracyIndex simplexValue)
  ]

truncatedValidationObstructions :: Eq simplex => TruncatedNormalizedSSet simplex -> [TruncatedSSetObstruction simplex]
truncatedValidationObstructions simplicialSet =
  truncatedFaceTotalityObstructions simplicialSet
    <> truncatedDegeneracyTotalityObstructions simplicialSet
    <> truncatedLawObstructions simplicialSet

simplicesAt :: forall n simplex. KnownNat n => TruncatedNormalizedSSet simplex -> Dimension n -> [simplex]
simplicesAt simplicialSet dimensionWitness =
  Map.findWithDefault [] (dimensionValue dimensionWitness) (nondegenerateSimplicesByDimension simplicialSet)

simplicesAtDimension :: TruncatedNormalizedSSet simplex -> Natural -> [simplex]
simplicesAtDimension simplicialSet dimensionValue' =
  Map.findWithDefault [] dimensionValue' (nondegenerateSimplicesByDimension simplicialSet)

applyFaceAtDimension :: TruncatedNormalizedSSet simplex -> Natural -> Natural -> simplex -> Maybe simplex
applyFaceAtDimension simplicialSet simplexDimension faceIndex simplexValue
  | simplexDimension == 0 = Nothing
  | simplexDimension > truncationBound simplicialSet = Nothing
  | faceIndex > simplexDimension = Nothing
  | otherwise = withReifiedFinOffset @2 (simplexDimension - 1) faceIndex $ \(_ :: Dimension n) finiteIndex ->
      faceMap simplicialSet (Dimension @(n + 1)) finiteIndex simplexValue

applyDegeneracyAtDimension :: TruncatedNormalizedSSet simplex -> Natural -> Natural -> simplex -> Maybe simplex
applyDegeneracyAtDimension simplicialSet simplexDimension degeneracyIndex simplexValue
  | simplexDimension >= truncationBound simplicialSet = Nothing
  | degeneracyIndex > simplexDimension = Nothing
  | otherwise = withReifiedFinOffset @1 simplexDimension degeneracyIndex $ \dimensionWitness finiteIndex ->
      degeneracyMap simplicialSet dimensionWitness finiteIndex simplexValue

faceIndexed ::
  forall n simplex.
  KnownNat n =>
  TruncatedNormalizedSSet simplex ->
  Fin (n + 2) ->
  IndexedSimplex (n + 1) simplex ->
  Maybe (IndexedSimplex n simplex)
faceIndexed simplicialSet faceIndex (IndexedSimplex simplexValue) =
  IndexedSimplex <$> faceMap simplicialSet (Dimension @(n + 1)) faceIndex simplexValue

degeneracyIndexed ::
  forall n simplex.
  KnownNat n =>
  TruncatedNormalizedSSet simplex ->
  Fin (n + 1) ->
  IndexedSimplex n simplex ->
  Maybe (IndexedSimplex (n + 1) simplex)
degeneracyIndexed simplicialSet degeneracyIndex (IndexedSimplex simplexValue) =
  IndexedSimplex <$> degeneracyMap simplicialSet (Dimension @n) degeneracyIndex simplexValue