moonlight-category-0.1.0.0: src-finite/Moonlight/Category/Pure/FinCat.hs
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE TypeFamilies #-}
-- | Runtime-validated finite categories ('FinCat'): object and morphism handles and
-- ids, validation errors, morphism enumeration and folds, and bit-packed thin
-- constructions.
module Moonlight.Category.Pure.FinCat
( FinCat,
FinCatHandle,
FinObjectId (..),
FinGeneratorId (..),
FinMorphismId (..),
FinObj,
FinMor,
FinCompositor (..),
FinTwoMor,
FinCatValidationError (..),
FinCatError (..),
finCatIsThin,
finCatObjects,
objectCount,
finCatMorphismCount,
finCatNonIdentityMorphismCount,
finCatMorphismCountFrom,
finCatMorphismCountTo,
finCatExplicitMorphismMapView,
finCatExplicitCompositionMapView,
finCatHandle,
finObjCategoryHandle,
finObjId,
finMorCategoryHandle,
finMorId,
finMorSourceId,
finMorTargetId,
finTwoSource,
finTwoTarget,
mkFinCat,
trustedFinCatWithGeneratorBasis,
finCatMorphismIdByEndpoints,
foldMapFinMorphisms,
foldMapFinMorphismsFrom,
foldMapFinMorphismsTo,
trustedThinFinCatFromTransitiveEndpoints,
trustedDenseThinFinCatFromReachabilityRows,
denseThinEndpointMorphismsFromCategory,
mkFinObject,
mkFinMorphism,
mkFinTwoMor,
finMorDomObject,
finMorCodObject,
finObjectIdentityMor,
finCatHomMorphism,
allObjects,
allMorphisms,
allMorphismsFrom,
)
where
import Data.Bits (bit, clearBit, popCount, shiftR, testBit, (.&.), (.|.))
import Data.Kind (Type)
import Data.List (find, genericTake, sort)
import Data.List.NonEmpty (NonEmpty (..))
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Data.Maybe (fromMaybe, mapMaybe)
import Data.Set (Set)
import qualified Data.Set as Set
import Data.Vector (Vector)
import qualified Data.Vector as Vector
import qualified Data.Vector.Unboxed as UVector
import Data.Word (Word64)
import Data.Foldable (fold)
import Data.Function ((&))
import Moonlight.Category.Pure.Category (Category (..), composeMor)
import Moonlight.Category.Pure.FiniteComposable
( ComposableChain,
FiniteComposableCategory (..),
SizedComposableChain,
appendComposableMorphism,
chainTerminalObject,
sizedComposableChain,
singletonComposableChain,
)
import Moonlight.Category.Pure.Higher (Bicategory (..), HigherCategory (..), TwoCategory (..))
import Moonlight.Core
( ExactEncoding,
ExactEncodingAtom (..),
ExactToken,
Validation (..),
exactAtomEncoding,
exactSequenceEncoding,
exactSequenceMapEncoding,
exactTokenFromEncoding,
validationToEither,
)
import Moonlight.Category.Pure.Finite.DenseReachability (bitsToAscList, transposeBitRows)
import Numeric.Natural (Natural)
newtype FinObjectId = FinObjectId {unFinObjectId :: Int}
deriving stock (Eq, Ord, Show)
newtype FinGeneratorId = FinGeneratorId {unFinGeneratorId :: Int}
deriving stock (Eq, Ord, Show)
type FinMorphismId :: Type
data FinMorphismId
= FinIdentityId FinObjectId
| FinGeneratorMorphismId FinGeneratorId
deriving stock (Eq, Ord, Show)
type FinCatHandle :: Type
newtype FinCatHandle = FinCatHandle ExactToken
deriving stock (Eq, Ord, Show)
type FinCat :: Type
data FinCat
= ExplicitFinCat ExplicitFinCatData
| ThinFinCat ThinFinCatData
| DenseThinFinCat DenseThinFinCatData
finCatIsThin :: FinCat -> Bool
finCatIsThin categoryValue =
case categoryValue of
ThinFinCat _ -> True
DenseThinFinCat _ -> True
ExplicitFinCat explicitData ->
all ((<= 1) . length) (Map.elems (explicitFinCatMorphismMap explicitData))
&& all
(\((sourceObject, targetObject), morphisms) -> sourceObject /= targetObject || null morphisms)
(Map.toAscList (explicitFinCatMorphismMap explicitData))
type ExplicitFinCatData :: Type
data ExplicitFinCatData = ExplicitFinCatData
{ explicitFinCatHandle :: FinCatHandle,
explicitFinCatObjects :: Set FinObjectId,
explicitFinCatMorphismMap :: Map (FinObjectId, FinObjectId) [FinMorphismId],
explicitFinCatCompositionMap :: Map (FinMorphismId, FinMorphismId) FinMorphismId,
explicitFinCatMorphismIndex :: Map FinMorphismId (FinObjectId, FinObjectId),
explicitFinCatMorphismsBySource :: Map FinObjectId [(FinObjectId, [FinMorphismId])],
explicitFinCatMorphismsByTarget :: Map FinObjectId [(FinObjectId, [FinMorphismId])]
}
type ThinFinCatData :: Type
data ThinFinCatData = ThinFinCatData
{ thinFinCatHandle :: FinCatHandle,
thinFinCatObjects :: Set FinObjectId,
thinFinCatEndpointMorphisms :: Map (FinObjectId, FinObjectId) FinMorphismId,
thinFinCatMorphismIndex :: Map FinMorphismId (FinObjectId, FinObjectId),
thinFinCatMorphismsBySource :: Map FinObjectId [(FinObjectId, FinMorphismId)],
thinFinCatMorphismsByTarget :: Map FinObjectId [(FinObjectId, FinMorphismId)]
}
type DenseThinFinCatData :: Type
data DenseThinFinCatData = DenseThinFinCatData
{ denseThinFinCatHandle :: FinCatHandle,
denseThinFinCatObjects :: Set FinObjectId,
denseThinFinCatObjectCount :: !Int,
denseThinFinCatNonIdentityMorphismCount :: !Int,
denseThinFinCatReachabilityRows :: Vector Integer,
denseThinFinCatPredecessorRows :: Vector Integer,
denseThinFinCatSourceCounts :: UVector.Vector Int,
denseThinFinCatTargetCounts :: UVector.Vector Int,
denseThinFinCatPrefixCounts :: UVector.Vector Int,
denseThinFinCatEndpointIndex :: UVector.Vector Int,
denseThinFinCatSourceColumn :: UVector.Vector Int,
denseThinFinCatTargetColumn :: UVector.Vector Int
}
finCatHandle :: FinCat -> FinCatHandle
finCatHandle category =
case category of
ExplicitFinCat explicitData -> explicitFinCatHandle explicitData
ThinFinCat thinData -> thinFinCatHandle thinData
DenseThinFinCat denseData -> denseThinFinCatHandle denseData
finCatObjects :: FinCat -> Set FinObjectId
finCatObjects category =
case category of
ExplicitFinCat explicitData -> explicitFinCatObjects explicitData
ThinFinCat thinData -> thinFinCatObjects thinData
DenseThinFinCat denseData -> denseThinFinCatObjects denseData
objectCount :: FinCat -> Int
objectCount category =
case category of
ExplicitFinCat explicitData -> Set.size (explicitFinCatObjects explicitData)
ThinFinCat thinData -> Set.size (thinFinCatObjects thinData)
DenseThinFinCat denseData -> denseThinFinCatObjectCount denseData
finCatNonIdentityMorphismCount :: FinCat -> Int
finCatNonIdentityMorphismCount category =
case category of
ExplicitFinCat explicitData ->
explicitFinCatMorphismMap explicitData
& Map.elems
& fmap length
& sum
ThinFinCat thinData ->
Map.size (thinFinCatEndpointMorphisms thinData)
DenseThinFinCat denseData ->
denseThinFinCatNonIdentityMorphismCount denseData
finCatMorphismCount :: FinCat -> Int
finCatMorphismCount category =
objectCount category + finCatNonIdentityMorphismCount category
finCatMorphismCountFrom :: FinCat -> FinObjectId -> Int
finCatMorphismCountFrom category sourceId =
case category of
DenseThinFinCat denseData ->
denseMorphismCountFrom denseData sourceId
_
| not (finCatHasObject category sourceId) -> 0
| otherwise -> 1 + nonIdentityCount
where
nonIdentityCount =
case category of
ExplicitFinCat explicitData ->
Map.findWithDefault [] sourceId (explicitFinCatMorphismsBySource explicitData)
& fmap (length . snd)
& sum
ThinFinCat thinData ->
Map.findWithDefault [] sourceId (thinFinCatMorphismsBySource thinData)
& length
DenseThinFinCat _ -> 0
finCatMorphismCountTo :: FinCat -> FinObjectId -> Int
finCatMorphismCountTo category targetId =
case category of
DenseThinFinCat denseData ->
denseMorphismCountTo denseData targetId
_
| not (finCatHasObject category targetId) -> 0
| otherwise -> 1 + nonIdentityCount
where
nonIdentityCount =
case category of
ExplicitFinCat explicitData ->
Map.findWithDefault [] targetId (explicitFinCatMorphismsByTarget explicitData)
& fmap (length . snd)
& sum
ThinFinCat thinData ->
Map.findWithDefault [] targetId (thinFinCatMorphismsByTarget thinData)
& length
DenseThinFinCat _ -> 0
finCatExplicitMorphismMapView :: FinCat -> Map (FinObjectId, FinObjectId) [FinMorphismId]
finCatExplicitMorphismMapView category =
case category of
ExplicitFinCat explicitData -> explicitFinCatMorphismMap explicitData
ThinFinCat thinData -> thinMorphismMap (thinFinCatEndpointMorphisms thinData)
DenseThinFinCat denseData -> thinMorphismMap (denseThinEndpointMorphisms denseData)
-- | Materializes the full composition table, so the cost is bound below by the
-- output size: one entry per composable generator pair, which is @Θ(n³)@ for a
-- linear site on @n@ objects. No implementation of this contract can be
-- asymptotically faster. For a thin category the table is redundant — composition
-- is a function of endpoints already answered in @O(1)@ from @Θ(n²/w)@ storage by
-- the dense handle — so consumers that need composition at scale should query the
-- 'FinCat' directly and reserve this view for explicit witnesses (law tests,
-- 'Moonlight.Category.Pure.FinCat.mkFinCat' round-trips).
finCatExplicitCompositionMapView :: FinCat -> Map (FinMorphismId, FinMorphismId) FinMorphismId
finCatExplicitCompositionMapView category =
case category of
ExplicitFinCat explicitData -> explicitFinCatCompositionMap explicitData
ThinFinCat thinData -> thinCompositionMap (thinCompositionEntries (thinFinCatEndpointMorphisms thinData))
DenseThinFinCat denseData -> denseThinCompositionMap denseData
finCatHasObject :: FinCat -> FinObjectId -> Bool
finCatHasObject category =
(`Set.member` finCatObjects category)
finCatMorphismIndex :: FinCat -> Map FinMorphismId (FinObjectId, FinObjectId)
finCatMorphismIndex category =
case category of
ExplicitFinCat explicitData -> explicitFinCatMorphismIndex explicitData
ThinFinCat thinData -> thinFinCatMorphismIndex thinData
DenseThinFinCat denseData -> denseThinMorphismIndex denseData
type FinObj :: Type
data FinObj = FinObj
{ finObjCategoryHandle :: FinCatHandle,
finObjId :: FinObjectId
}
deriving stock (Show)
type FinMor :: Type
data FinMor = FinMor
{ finMorCategoryHandle :: FinCatHandle,
finMorId :: FinMorphismId,
finMorSourceId :: FinObjectId,
finMorTargetId :: FinObjectId
}
deriving stock (Show)
type FinCompositor :: Type
data FinCompositor
= FinStrictCompositor
| FinAssociator FinMor FinMor FinMor
| FinLeftUnitor FinMor
| FinRightUnitor FinMor
deriving stock (Eq, Ord, Show)
type FinTwoMor :: Type
data FinTwoMor = FinTwoMor
{ finTwoSource :: FinMor,
finTwoTarget :: FinMor
}
deriving stock (Eq, Ord, Show)
type FinCatValidationError :: Type
data FinCatValidationError
= MorphismEndpointOutsideObjects FinObjectId FinObjectId
| ReservedIdentityMorphismId FinMorphismId
| DuplicateMorphismId FinMorphismId
| CompositionReferencesUnknownMorphism FinMorphismId
| CompositionPairNotComposable FinMorphismId FinMorphismId
| CompositionResultUnknownMorphism FinMorphismId
| CompositionResultEndpointMismatch FinMorphismId FinMorphismId FinMorphismId
| CompositionTableUsesIdentityKey FinMorphismId FinMorphismId
| MissingCompositionForPair FinMorphismId FinMorphismId
| AssociativityViolation FinMorphismId FinMorphismId FinMorphismId (Maybe FinMorphismId) (Maybe FinMorphismId)
deriving stock (Eq, Show)
type FinCatError :: Type
data FinCatError
= FinCatObjectNotDeclared FinCatHandle FinObjectId
| FinCatMorphismNotDeclared FinCatHandle FinMorphismId
| FinCatObjectWrongCategory FinCatHandle FinCatHandle FinObjectId
| FinCatMorphismWrongCategory FinCatHandle FinCatHandle FinMorphismId
| FinCatMorphismNotComposable FinMorphismId FinMorphismId FinObjectId FinObjectId
| FinCatTwoMorphismBoundaryNotParallel FinMor FinMor
| FinCatTwoMorphismNotVerticallyComposable FinTwoMor FinTwoMor
| FinCatCompositionMissing FinMorphismId FinMorphismId
| FinCatCompositionResultInvalid FinMorphismId FinObjectId FinObjectId
deriving stock (Eq, Show)
type AssociativityMiddleCover :: Type
data AssociativityMiddleCover
= GeneratorRestrictedMiddleCover (Set FinMorphismId)
| ExhaustiveMiddleCover
instance Eq FinCat where
left == right = finCatHandle left == finCatHandle right
instance Ord FinCat where
compare left right = compare (finCatHandle left) (finCatHandle right)
instance Show FinCat where
show category =
case category of
ExplicitFinCat explicitData ->
"FinCat "
<> show (explicitFinCatObjects explicitData)
<> " "
<> show (explicitFinCatMorphismMap explicitData)
<> " "
<> show (explicitFinCatCompositionMap explicitData)
ThinFinCat thinData ->
"ThinFinCat "
<> show (thinFinCatObjects thinData)
<> " "
<> show (thinMorphismMap (thinFinCatEndpointMorphisms thinData))
DenseThinFinCat denseData ->
"DenseThinFinCat "
<> show (denseThinFinCatObjects denseData)
<> " morphisms="
<> show (denseThinFinCatNonIdentityMorphismCount denseData)
instance Eq FinObj where
left == right =
finObjCategoryHandle left == finObjCategoryHandle right
&& finObjId left == finObjId right
instance Ord FinObj where
compare left right =
compare (finObjCategoryHandle left) (finObjCategoryHandle right)
<> compare (finObjId left) (finObjId right)
instance Eq FinMor where
left == right =
finMorCategoryHandle left == finMorCategoryHandle right
&& finMorId left == finMorId right
&& finMorSourceId left == finMorSourceId right
&& finMorTargetId left == finMorTargetId right
instance Ord FinMor where
compare left right =
compare (finMorCategoryHandle left) (finMorCategoryHandle right)
<> compare (finMorId left) (finMorId right)
<> compare (finMorSourceId left) (finMorSourceId right)
<> compare (finMorTargetId left) (finMorTargetId right)
encodingAtom :: ExactEncodingAtom -> ExactEncoding
encodingAtom =
exactAtomEncoding
encodingInt :: Int -> ExactEncoding
encodingInt =
encodingAtom . ExactInt
finCatCategoryEncoding :: Set FinObjectId -> Map (FinObjectId, FinObjectId) [FinMorphismId] -> Map (FinMorphismId, FinMorphismId) FinMorphismId -> ExactEncoding
finCatCategoryEncoding objects morphismMap compositionMap =
exactSequenceEncoding
[ encodingAtom (ExactWord8 0),
exactSequenceMapEncoding finCatObjectIdEncoding objects,
exactSequenceMapEncoding finCatMorphismBucketEncoding (Map.toAscList morphismMap),
exactSequenceMapEncoding finCatCompositionEntryEncoding (Map.toAscList compositionMap)
]
finCatDenseThinCategoryEncoding :: Set FinObjectId -> Vector Integer -> ExactEncoding
finCatDenseThinCategoryEncoding objects reachabilityRows =
exactSequenceEncoding
[ encodingAtom (ExactWord8 5),
exactSequenceMapEncoding finCatObjectIdEncoding objects,
exactSequenceMapEncoding (finCatReachabilityRowEncoding (Set.size objects)) reachabilityRows
]
finCatReachabilityRowEncoding :: Int -> Integer -> ExactEncoding
finCatReachabilityRowEncoding objectTotal bits =
exactSequenceEncoding
[ encodingAtom (ExactWord8 7),
exactSequenceMapEncoding encodingInt (denseRowWordChunks objectTotal bits)
]
denseRowWordChunks :: Int -> Integer -> [Int]
denseRowWordChunks objectTotal bits =
[0 .. ((objectTotal + 63) `div` 64) - 1]
& fmap (\chunkIndex -> fromIntegral (fromIntegral (bits `shiftR` (64 * chunkIndex)) :: Word64))
finCatObjectIdEncoding :: FinObjectId -> ExactEncoding
finCatObjectIdEncoding (FinObjectId objectId) =
exactSequenceEncoding [encodingAtom (ExactWord8 1), encodingInt objectId]
finCatGeneratorIdEncoding :: FinGeneratorId -> ExactEncoding
finCatGeneratorIdEncoding (FinGeneratorId generatorId) =
exactSequenceEncoding [encodingAtom (ExactWord8 2), encodingInt generatorId]
finCatMorphismIdEncoding :: FinMorphismId -> ExactEncoding
finCatMorphismIdEncoding morphismId =
case morphismId of
FinIdentityId objectId ->
exactSequenceEncoding [encodingAtom (ExactWord8 3), finCatObjectIdEncoding objectId]
FinGeneratorMorphismId generatorId ->
exactSequenceEncoding [encodingAtom (ExactWord8 4), finCatGeneratorIdEncoding generatorId]
finCatMorphismBucketEncoding :: ((FinObjectId, FinObjectId), [FinMorphismId]) -> ExactEncoding
finCatMorphismBucketEncoding ((sourceId, targetId), morphismIds) =
exactSequenceEncoding
[ finCatObjectIdEncoding sourceId,
finCatObjectIdEncoding targetId,
exactSequenceMapEncoding finCatMorphismIdEncoding morphismIds
]
finCatCompositionEntryEncoding :: ((FinMorphismId, FinMorphismId), FinMorphismId) -> ExactEncoding
finCatCompositionEntryEncoding ((left, right), result) =
exactSequenceEncoding
[ finCatMorphismIdEncoding left,
finCatMorphismIdEncoding right,
finCatMorphismIdEncoding result
]
mkFinCatHandle :: Set FinObjectId -> Map (FinObjectId, FinObjectId) [FinMorphismId] -> Map (FinMorphismId, FinMorphismId) FinMorphismId -> FinCatHandle
mkFinCatHandle objects morphismMap compositionMap =
FinCatHandle
(exactTokenFromEncoding (finCatCategoryEncoding objects morphismMap compositionMap))
normalizeMorphismMap :: Map (FinObjectId, FinObjectId) [FinMorphismId] -> Map (FinObjectId, FinObjectId) [FinMorphismId]
normalizeMorphismMap =
Map.filter (not . null) . fmap sort
morphismBucketsBySource :: Map (FinObjectId, FinObjectId) [FinMorphismId] -> Map FinObjectId [(FinObjectId, [FinMorphismId])]
morphismBucketsBySource morphismMap =
morphismMap
& Map.toAscList
& fmap (\((sourceId, targetId), morphismIds) -> (sourceId, [(targetId, morphismIds)]))
& Map.fromListWith (flip (<>))
morphismBucketsByTarget :: Map (FinObjectId, FinObjectId) [FinMorphismId] -> Map FinObjectId [(FinObjectId, [FinMorphismId])]
morphismBucketsByTarget morphismMap =
morphismMap
& Map.toAscList
& fmap (\((sourceId, targetId), morphismIds) -> (targetId, [(sourceId, morphismIds)]))
& Map.fromListWith (flip (<>))
thinEndpointMorphismsBySource :: Map (FinObjectId, FinObjectId) FinMorphismId -> Map FinObjectId [(FinObjectId, FinMorphismId)]
thinEndpointMorphismsBySource endpointMorphisms =
endpointMorphisms
& Map.toAscList
& fmap (\((sourceId, targetId), morphismId) -> (sourceId, [(targetId, morphismId)]))
& Map.fromListWith (flip (<>))
thinEndpointMorphismsByTargetSimple :: Map (FinObjectId, FinObjectId) FinMorphismId -> Map FinObjectId [(FinObjectId, FinMorphismId)]
thinEndpointMorphismsByTargetSimple endpointMorphisms =
endpointMorphisms
& Map.toAscList
& fmap (\((sourceId, targetId), morphismId) -> (targetId, [(sourceId, morphismId)]))
& Map.fromListWith (flip (<>))
buildFinCat :: Set FinObjectId -> Map (FinObjectId, FinObjectId) [FinMorphismId] -> Map (FinMorphismId, FinMorphismId) FinMorphismId -> FinCat
buildFinCat objects morphismMap compositionMap =
let normalizedMorphismMap = normalizeMorphismMap morphismMap
index = declaredMorphismIndex objects normalizedMorphismMap
in ExplicitFinCat
ExplicitFinCatData
{ explicitFinCatHandle = mkFinCatHandle objects normalizedMorphismMap compositionMap,
explicitFinCatObjects = objects,
explicitFinCatMorphismMap = normalizedMorphismMap,
explicitFinCatCompositionMap = compositionMap,
explicitFinCatMorphismIndex = index,
explicitFinCatMorphismsBySource = morphismBucketsBySource normalizedMorphismMap,
explicitFinCatMorphismsByTarget = morphismBucketsByTarget normalizedMorphismMap
}
buildThinFinCat :: Set FinObjectId -> Map (FinObjectId, FinObjectId) FinMorphismId -> FinCat
buildThinFinCat objects endpointMorphisms =
let morphismMap = thinMorphismMap endpointMorphisms
index = declaredMorphismIndex objects morphismMap
in ThinFinCat
ThinFinCatData
{ thinFinCatHandle = mkFinCatHandle objects morphismMap Map.empty,
thinFinCatObjects = objects,
thinFinCatEndpointMorphisms = endpointMorphisms,
thinFinCatMorphismIndex = index,
thinFinCatMorphismsBySource = thinEndpointMorphismsBySource endpointMorphisms,
thinFinCatMorphismsByTarget = thinEndpointMorphismsByTargetSimple endpointMorphisms
}
buildDenseThinFinCat :: Set FinObjectId -> Vector Integer -> FinCat
buildDenseThinFinCat objects reachabilityRows =
let objectTotal = Set.size objects
canonicalRows = denseCanonicalReachabilityRows objectTotal reachabilityRows
predecessorRows = transposeBitRows objectTotal canonicalRows
sourceCounts = denseRowCounts canonicalRows
targetCounts = denseRowCounts predecessorRows
prefixCounts = densePrefixCountsFromCounts sourceCounts
endpointIndex = denseEndpointIndex objectTotal canonicalRows prefixCounts
pairs = denseReachablePairs objectTotal canonicalRows
sourceColumn = UVector.fromList (fmap fst pairs)
targetColumn = UVector.fromList (fmap snd pairs)
in DenseThinFinCat
DenseThinFinCatData
{ denseThinFinCatHandle = mkDenseThinFinCatHandle objects canonicalRows,
denseThinFinCatObjects = objects,
denseThinFinCatObjectCount = objectTotal,
denseThinFinCatNonIdentityMorphismCount = UVector.sum sourceCounts,
denseThinFinCatReachabilityRows = canonicalRows,
denseThinFinCatPredecessorRows = predecessorRows,
denseThinFinCatSourceCounts = sourceCounts,
denseThinFinCatTargetCounts = targetCounts,
denseThinFinCatPrefixCounts = prefixCounts,
denseThinFinCatEndpointIndex = endpointIndex,
denseThinFinCatSourceColumn = sourceColumn,
denseThinFinCatTargetColumn = targetColumn
}
denseCanonicalReachabilityRows :: Int -> Vector Integer -> Vector Integer
denseCanonicalReachabilityRows objectTotal reachabilityRows =
Vector.generate
objectTotal
( \sourceIndex ->
reachabilityRows
Vector.!? sourceIndex
& maybe 0 (denseCanonicalReachabilityRow objectTotal sourceIndex)
)
denseCanonicalReachabilityRow :: Int -> Int -> Integer -> Integer
denseCanonicalReachabilityRow objectTotal sourceIndex reachableBits =
(reachableBits .&. denseBitMask objectTotal) `clearBit` sourceIndex
denseBitMask :: Int -> Integer
denseBitMask objectTotal =
if objectTotal <= 0
then 0
else bit objectTotal - 1
mkDenseThinFinCatHandle :: Set FinObjectId -> Vector Integer -> FinCatHandle
mkDenseThinFinCatHandle objects reachabilityRows =
FinCatHandle (exactTokenFromEncoding (finCatDenseThinCategoryEncoding objects reachabilityRows))
denseRowCounts :: Vector Integer -> UVector.Vector Int
denseRowCounts rows =
rows
& Vector.toList
& fmap popCount
& UVector.fromList
densePrefixCountsFromCounts :: UVector.Vector Int -> UVector.Vector Int
densePrefixCountsFromCounts =
UVector.scanl (+) 0
denseEndpointIndex :: Int -> Vector Integer -> UVector.Vector Int -> UVector.Vector Int
denseEndpointIndex objectTotal reachabilityRows prefixCounts =
UVector.concat
( [0 .. objectTotal - 1]
& fmap
( \sourceIndex ->
denseEndpointIndexRow
objectTotal
(fromMaybe 0 (reachabilityRows Vector.!? sourceIndex))
(fromMaybe 0 (prefixCounts UVector.!? sourceIndex))
)
)
denseEndpointIndexRow :: Int -> Integer -> Int -> UVector.Vector Int
denseEndpointIndexRow objectTotal reachableBits prefixCount =
UVector.fromListN
objectTotal
( zipWith
(\targetIndex rank -> if testBit reachableBits targetIndex then rank else -1)
[0 .. objectTotal - 1]
(scanl (\rank targetIndex -> if testBit reachableBits targetIndex then rank + 1 else rank) prefixCount [0 .. objectTotal - 1])
)
denseReachablePairs :: Int -> Vector Integer -> [(Int, Int)]
denseReachablePairs objectTotal reachabilityRows =
reachabilityRows
& Vector.toList
& zip [0 ..]
>>= ( \(sourceIndex, reachableBits) ->
bitsToAscList objectTotal reachableBits
& fmap (\targetIndex -> (sourceIndex, targetIndex))
)
denseThinEndpointMorphisms :: DenseThinFinCatData -> Map (FinObjectId, FinObjectId) FinMorphismId
denseThinEndpointMorphisms denseData =
zip3
[0 ..]
(UVector.toList (denseThinFinCatSourceColumn denseData))
(UVector.toList (denseThinFinCatTargetColumn denseData))
& fmap
( \(morphismIndex, sourceIndex, targetIndex) ->
((FinObjectId sourceIndex, FinObjectId targetIndex), denseMorphismId morphismIndex)
)
& Map.fromDistinctAscList
denseThinCompositionMap :: DenseThinFinCatData -> Map (FinMorphismId, FinMorphismId) FinMorphismId
denseThinCompositionMap denseData =
Map.fromDistinctAscList ([0 .. objectTotal - 1] >>= entriesForLeftSource)
where
objectTotal = denseThinFinCatObjectCount denseData
reachabilityRows = denseThinFinCatReachabilityRows denseData
predecessorRows = denseThinFinCatPredecessorRows denseData
endpointIndex = denseThinFinCatEndpointIndex denseData
morphismIds =
Vector.generate (denseThinFinCatNonIdentityMorphismCount denseData) denseMorphismId
indexAt sourceIndex targetIndex =
UVector.unsafeIndex endpointIndex (sourceIndex * objectTotal + targetIndex)
composedIdAt rightSourceIndex leftTargetIndex
| rightSourceIndex == leftTargetIndex =
Just (identityMorphismId (FinObjectId rightSourceIndex))
| otherwise =
let composedIndex = indexAt rightSourceIndex leftTargetIndex
in if composedIndex >= 0
then Just (Vector.unsafeIndex morphismIds composedIndex)
else Nothing
entriesForLeftSource leftSourceIndex =
let rightEntries =
bitsToAscList objectTotal (Vector.unsafeIndex predecessorRows leftSourceIndex)
& fmap
( \rightSourceIndex ->
(rightSourceIndex, Vector.unsafeIndex morphismIds (indexAt rightSourceIndex leftSourceIndex))
)
in bitsToAscList objectTotal (Vector.unsafeIndex reachabilityRows leftSourceIndex)
>>= entriesForLeft leftSourceIndex rightEntries
entriesForLeft leftSourceIndex rightEntries leftTargetIndex =
let leftId = Vector.unsafeIndex morphismIds (indexAt leftSourceIndex leftTargetIndex)
in rightEntries
& mapMaybe
( \(rightSourceIndex, rightId) ->
fmap
(\composedId -> ((leftId, rightId), composedId))
(composedIdAt rightSourceIndex leftTargetIndex)
)
denseThinMorphismIndex :: DenseThinFinCatData -> Map FinMorphismId (FinObjectId, FinObjectId)
denseThinMorphismIndex denseData =
let identityEntries =
Set.toAscList (denseThinFinCatObjects denseData)
& fmap (\objectId -> (identityMorphismId objectId, (objectId, objectId)))
generatorEntries =
denseThinEndpointMorphisms denseData
& Map.toAscList
& fmap (\(endpoints, morphismId) -> (morphismId, endpoints))
in Map.fromList (identityEntries <> generatorEntries)
denseMorphismId :: Int -> FinMorphismId
denseMorphismId =
FinGeneratorMorphismId . FinGeneratorId
denseMorphismIdIndex :: FinMorphismId -> Maybe Int
denseMorphismIdIndex morphismId =
case morphismId of
FinGeneratorMorphismId (FinGeneratorId indexValue)
| indexValue >= 0 -> Just indexValue
_ -> Nothing
denseMorphismEndpoints :: DenseThinFinCatData -> FinMorphismId -> Maybe (FinObjectId, FinObjectId)
denseMorphismEndpoints denseData morphismId =
case morphismId of
FinIdentityId objectId
| denseObjectIdInBounds denseData objectId -> Just (objectId, objectId)
_ -> do
morphismIndex <- denseMorphismIdIndex morphismId
sourceIndex <- denseThinFinCatSourceColumn denseData UVector.!? morphismIndex
targetIndex <- denseThinFinCatTargetColumn denseData UVector.!? morphismIndex
pure (FinObjectId sourceIndex, FinObjectId targetIndex)
denseObjectIdInBounds :: DenseThinFinCatData -> FinObjectId -> Bool
denseObjectIdInBounds denseData (FinObjectId objectIndex) =
objectIndex >= 0 && objectIndex < denseThinFinCatObjectCount denseData
denseMorphismCountFrom :: DenseThinFinCatData -> FinObjectId -> Int
denseMorphismCountFrom denseData sourceId@(FinObjectId sourceIndex) =
if denseObjectIdInBounds denseData sourceId
then 1 + UVector.unsafeIndex (denseThinFinCatSourceCounts denseData) sourceIndex
else 0
denseMorphismCountTo :: DenseThinFinCatData -> FinObjectId -> Int
denseMorphismCountTo denseData targetId@(FinObjectId targetIndex) =
if denseObjectIdInBounds denseData targetId
then 1 + UVector.unsafeIndex (denseThinFinCatTargetCounts denseData) targetIndex
else 0
denseEndpointMorphism :: DenseThinFinCatData -> FinObjectId -> FinObjectId -> Maybe FinMorphismId
denseEndpointMorphism denseData sourceId targetId =
denseMorphismId <$> denseEndpointMorphismIndex denseData sourceId targetId
denseEndpointMorphismIndex :: DenseThinFinCatData -> FinObjectId -> FinObjectId -> Maybe Int
denseEndpointMorphismIndex denseData sourceId@(FinObjectId sourceIndex) targetId@(FinObjectId targetIndex) =
if denseObjectIdInBounds denseData sourceId && denseObjectIdInBounds denseData targetId && sourceId /= targetId
then
let endpointOffset = sourceIndex * denseThinFinCatObjectCount denseData + targetIndex
morphismIndex = UVector.unsafeIndex (denseThinFinCatEndpointIndex denseData) endpointOffset
in if morphismIndex >= 0 then Just morphismIndex else Nothing
else Nothing
strictDenseThinFinCat :: Set FinObjectId -> Map (FinObjectId, FinObjectId) FinMorphismId -> Maybe FinCat
strictDenseThinFinCat objects endpointMorphisms = do
reachabilityRows <- denseRowsFromEndpointMorphisms objects endpointMorphisms
let denseCategory = buildDenseThinFinCat objects reachabilityRows
if denseThinEndpointMorphismsFromCategory denseCategory == endpointMorphisms
then Just denseCategory
else Nothing
denseRowsFromEndpointMorphisms :: Set FinObjectId -> Map (FinObjectId, FinObjectId) FinMorphismId -> Maybe (Vector Integer)
denseRowsFromEndpointMorphisms objects endpointMorphisms =
let objectIds = Set.toAscList objects
expectedObjectIds = FinObjectId <$> [0 .. Set.size objects - 1]
in if objectIds == expectedObjectIds
then
endpointMorphisms
& Map.toAscList
& traverse denseEndpointBit
& fmap
( \endpointBits ->
endpointBits
& fmap (\(sourceIndex, targetBit) -> (sourceIndex, targetBit))
& Map.fromListWith (.|.)
& (\rowMap -> Vector.generate (Set.size objects) (\sourceIndex -> Map.findWithDefault 0 sourceIndex rowMap))
)
else Nothing
denseEndpointBit :: ((FinObjectId, FinObjectId), FinMorphismId) -> Maybe (Int, Integer)
denseEndpointBit ((FinObjectId sourceIndex, FinObjectId targetIndex), _)
| sourceIndex >= 0 && targetIndex >= 0 && sourceIndex /= targetIndex = Just (sourceIndex, bit targetIndex)
| otherwise = Nothing
denseThinEndpointMorphismsFromCategory :: FinCat -> Map (FinObjectId, FinObjectId) FinMorphismId
denseThinEndpointMorphismsFromCategory category =
case category of
DenseThinFinCat denseData -> denseThinEndpointMorphisms denseData
_ -> Map.empty
strictThinEndpointMorphisms :: Map (FinObjectId, FinObjectId) [FinMorphismId] -> Maybe (Map (FinObjectId, FinObjectId) FinMorphismId)
strictThinEndpointMorphisms morphismMap =
morphismMap
& Map.toAscList
& traverse strictThinEndpointMorphism
& fmap Map.fromList
strictThinEndpointMorphism :: ((FinObjectId, FinObjectId), [FinMorphismId]) -> Maybe ((FinObjectId, FinObjectId), FinMorphismId)
strictThinEndpointMorphism ((sourceId, targetId), morphismIds) =
case morphismIds of
[morphismId]
| sourceId /= targetId && not (isIdentityMorphismId morphismId) -> Just ((sourceId, targetId), morphismId)
_ -> Nothing
identityMorphismId :: FinObjectId -> FinMorphismId
identityMorphismId = FinIdentityId
isIdentityMorphismId :: FinMorphismId -> Bool
isIdentityMorphismId morphismId =
case morphismId of
FinIdentityId _ -> True
FinGeneratorMorphismId _ -> False
morphismEntries :: Map (FinObjectId, FinObjectId) [FinMorphismId] -> [(FinMorphismId, (FinObjectId, FinObjectId))]
morphismEntries morphismMap =
Map.toList morphismMap
& concatMap
(\((sourceId, targetId), morphismIds) ->
map (\morphismId -> (morphismId, (sourceId, targetId))) morphismIds
)
duplicates :: Ord a => [a] -> [a]
duplicates values =
values
& foldr (\value -> Map.insertWith (+) value (1 :: Int)) Map.empty
& Map.toAscList
& foldMap (\(value, count) -> if count > 1 then [value] else [])
declaredMorphismIndex :: Set FinObjectId -> Map (FinObjectId, FinObjectId) [FinMorphismId] -> Map FinMorphismId (FinObjectId, FinObjectId)
declaredMorphismIndex objects morphismMap =
let identityEntries =
Set.toAscList objects
& map (\objectId -> (identityMorphismId objectId, (objectId, objectId)))
in Map.fromList (identityEntries <> morphismEntries morphismMap)
lookupMorphismEndpoints :: Map FinMorphismId (FinObjectId, FinObjectId) -> FinMorphismId -> Maybe (FinObjectId, FinObjectId)
lookupMorphismEndpoints index morphismId = Map.lookup morphismId index
composeMorphismIds :: Map FinMorphismId (FinObjectId, FinObjectId) -> Map (FinMorphismId, FinMorphismId) FinMorphismId -> FinMorphismId -> FinMorphismId -> Maybe FinMorphismId
composeMorphismIds index compositionMap left right = do
(leftSourceId, _) <- lookupMorphismEndpoints index left
(_, rightTargetId) <- lookupMorphismEndpoints index right
if rightTargetId == leftSourceId
then
if isIdentityMorphismId left
then pure right
else
if isIdentityMorphismId right
then pure left
else Map.lookup (left, right) compositionMap
else Nothing
validationFromErrors :: [FinCatValidationError] -> Validation (NonEmpty FinCatValidationError) ()
validationFromErrors errors =
case errors of
[] -> Valid ()
firstError : restErrors -> Invalid (firstError :| restErrors)
validateMorphismEndpoints :: Set FinObjectId -> Map (FinObjectId, FinObjectId) [FinMorphismId] -> Validation (NonEmpty FinCatValidationError) ()
validateMorphismEndpoints objects morphismMap =
morphismMap
& Map.keys
& foldMap
(\(sourceId, targetId) ->
if Set.member sourceId objects && Set.member targetId objects
then []
else [MorphismEndpointOutsideObjects sourceId targetId]
)
& validationFromErrors
validateReservedIds :: Map (FinObjectId, FinObjectId) [FinMorphismId] -> Validation (NonEmpty FinCatValidationError) ()
validateReservedIds morphismMap =
morphismEntries morphismMap
& foldMap ((\morphismId -> if isIdentityMorphismId morphismId then [ReservedIdentityMorphismId morphismId] else []) . fst)
& validationFromErrors
validateUniqueMorphismIds :: Map (FinObjectId, FinObjectId) [FinMorphismId] -> Validation (NonEmpty FinCatValidationError) ()
validateUniqueMorphismIds morphismMap =
morphismEntries morphismMap
& fmap fst
& duplicates
& fmap DuplicateMorphismId
& validationFromErrors
validateCompositionTable :: Map FinMorphismId (FinObjectId, FinObjectId) -> Map (FinMorphismId, FinMorphismId) FinMorphismId -> Validation (NonEmpty FinCatValidationError) ()
validateCompositionTable index compositionMap =
compositionMap
& Map.toAscList
& foldMap (compositionEntryErrors index)
& validationFromErrors
compositionEntryErrors :: Map FinMorphismId (FinObjectId, FinObjectId) -> ((FinMorphismId, FinMorphismId), FinMorphismId) -> [FinCatValidationError]
compositionEntryErrors index ((left, right), result) =
identityKeyErrors <> unknownErrors <> composabilityErrors <> endpointErrors
where
maybeLeftEndpoints = lookupMorphismEndpoints index left
maybeRightEndpoints = lookupMorphismEndpoints index right
maybeResultEndpoints = lookupMorphismEndpoints index result
identityKeyErrors =
if isIdentityMorphismId left || isIdentityMorphismId right
then [CompositionTableUsesIdentityKey left right]
else []
unknownErrors =
fold
[ maybe [CompositionReferencesUnknownMorphism left] (const []) maybeLeftEndpoints,
maybe [CompositionReferencesUnknownMorphism right] (const []) maybeRightEndpoints,
maybe [CompositionResultUnknownMorphism result] (const []) maybeResultEndpoints
]
composabilityErrors =
case (maybeLeftEndpoints, maybeRightEndpoints) of
(Just (leftSourceId, _), Just (_, rightTargetId))
| rightTargetId /= leftSourceId -> [CompositionPairNotComposable left right]
_ -> []
endpointErrors =
case (maybeLeftEndpoints, maybeRightEndpoints, maybeResultEndpoints) of
(Just (_, leftTargetId), Just (rightSourceId, _), Just (resultSourceId, resultTargetId))
| resultSourceId /= rightSourceId || resultTargetId /= leftTargetId -> [CompositionResultEndpointMismatch left right result]
_ -> []
morphismsBySourceId :: Map FinMorphismId (FinObjectId, FinObjectId) -> Map FinObjectId [FinMorphismId]
morphismsBySourceId index =
index
& Map.toAscList
& fmap (\(morphismId, (sourceId, _)) -> (sourceId, [morphismId]))
& Map.fromListWith (<>)
morphismsByTargetId :: Map FinMorphismId (FinObjectId, FinObjectId) -> Map FinObjectId [FinMorphismId]
morphismsByTargetId index =
index
& Map.toAscList
& fmap (\(morphismId, (_, targetId)) -> (targetId, [morphismId]))
& Map.fromListWith (<>)
composablePairs :: Map FinMorphismId (FinObjectId, FinObjectId) -> [(FinMorphismId, FinMorphismId)]
composablePairs index =
index
& Map.toAscList
& foldMap
( \(rightMorphism, (_, rightTargetId)) ->
morphismsFrom rightTargetId
& fmap (\leftMorphism -> (leftMorphism, rightMorphism))
)
where
bySource = morphismsBySourceId index
morphismsFrom sourceId = Map.findWithDefault [] sourceId bySource
composableTriples :: Map FinMorphismId (FinObjectId, FinObjectId) -> [(FinMorphismId, FinMorphismId, FinMorphismId)]
composableTriples index =
index
& Map.toAscList
& foldMap triplesFromRight
where
bySource = morphismsBySourceId index
morphismsFrom sourceId = Map.findWithDefault [] sourceId bySource
triplesFromRight (rightMorphism, (_, rightTargetId)) =
morphismsFrom rightTargetId
& foldMap (triplesFromMiddle rightMorphism)
triplesFromMiddle rightMorphism middleMorphism =
case Map.lookup middleMorphism index of
Nothing -> []
Just (_, middleTargetId) ->
morphismsFrom middleTargetId
& fmap (\leftMorphism -> (leftMorphism, middleMorphism, rightMorphism))
composableTriplesWithMiddleIn :: Set FinMorphismId -> Map FinMorphismId (FinObjectId, FinObjectId) -> [(FinMorphismId, FinMorphismId, FinMorphismId)]
composableTriplesWithMiddleIn middleIds index =
index
& Map.toAscList
& foldMap triplesFromMiddle
where
bySource = morphismsBySourceId index
byTarget = morphismsByTargetId index
morphismsFrom sourceId = Map.findWithDefault [] sourceId bySource
morphismsTo targetId = Map.findWithDefault [] targetId byTarget
triplesFromMiddle (middleMorphism, (middleSourceId, middleTargetId))
| Set.member middleMorphism middleIds =
morphismsFrom middleTargetId
& foldMap
( \leftMorphism ->
morphismsTo middleSourceId
& fmap
( \rightMorphism ->
(leftMorphism, middleMorphism, rightMorphism)
)
)
| otherwise = []
validateClosure :: Map FinMorphismId (FinObjectId, FinObjectId) -> Map (FinMorphismId, FinMorphismId) FinMorphismId -> Validation (NonEmpty FinCatValidationError) ()
validateClosure index compositionMap =
composablePairs index
& foldMap
(\(left, right) ->
if composeMorphismIds index compositionMap left right == Nothing
then [MissingCompositionForPair left right]
else []
)
& validationFromErrors
validateAssociativityExhaustive :: Map FinMorphismId (FinObjectId, FinObjectId) -> Map (FinMorphismId, FinMorphismId) FinMorphismId -> Validation (NonEmpty FinCatValidationError) ()
validateAssociativityExhaustive index compositionMap =
composableTriples index
& foldMap (associativityErrors index compositionMap)
& validationFromErrors
validateAssociativity :: Map FinMorphismId (FinObjectId, FinObjectId) -> Map (FinMorphismId, FinMorphismId) FinMorphismId -> Validation (NonEmpty FinCatValidationError) ()
validateAssociativity index compositionMap =
case associativityMiddleCover index compositionMap of
GeneratorRestrictedMiddleCover middleIds ->
composableTriplesWithMiddleIn middleIds index
& foldMap (associativityErrors index compositionMap)
& validationFromErrors
ExhaustiveMiddleCover ->
validateAssociativityExhaustive index compositionMap
validateAssociativityAtGenerators :: Set FinMorphismId -> Map FinMorphismId (FinObjectId, FinObjectId) -> Map (FinMorphismId, FinMorphismId) FinMorphismId -> Validation (NonEmpty FinCatValidationError) ()
validateAssociativityAtGenerators generatorIds index compositionMap =
composableTriplesWithMiddleIn (Set.filter (not . isIdentityMorphismId) generatorIds) index
& foldMap (associativityErrors index compositionMap)
& validationFromErrors
associativityMiddleCover :: Map FinMorphismId (FinObjectId, FinObjectId) -> Map (FinMorphismId, FinMorphismId) FinMorphismId -> AssociativityMiddleCover
associativityMiddleCover index compositionMap =
let nonIdentityMorphisms =
Map.keysSet index
& Set.filter (not . isIdentityMorphismId)
primaryGenerators =
explicitCompositionGenerators index compositionMap
generatedFromPrimary =
generatedMorphisms index compositionMap primaryGenerators
uncoveredMorphisms =
Set.difference nonIdentityMorphisms generatedFromPrimary
verifiedGenerators =
Set.union primaryGenerators uncoveredMorphisms
generatedFromVerified =
generatedMorphisms index compositionMap verifiedGenerators
in if Set.isSubsetOf nonIdentityMorphisms generatedFromVerified
then GeneratorRestrictedMiddleCover verifiedGenerators
else ExhaustiveMiddleCover
explicitCompositionGenerators :: Map FinMorphismId (FinObjectId, FinObjectId) -> Map (FinMorphismId, FinMorphismId) FinMorphismId -> Set FinMorphismId
explicitCompositionGenerators index compositionMap =
let compositeResults =
compositionMap
& Map.elems
& Set.fromList
& Set.filter (not . isIdentityMorphismId)
in Map.keysSet index
& Set.filter (not . isIdentityMorphismId)
& (`Set.difference` compositeResults)
generatedMorphisms :: Map FinMorphismId (FinObjectId, FinObjectId) -> Map (FinMorphismId, FinMorphismId) FinMorphismId -> Set FinMorphismId -> Set FinMorphismId
generatedMorphisms index compositionMap generators =
let initialMorphisms =
Set.union (identityMorphismSet index) generators
closeMorphisms =
closeGeneratedMorphisms index compositionMap
generatedSequence =
take (Map.size index + 1) (iterate closeMorphisms initialMorphisms)
stableMorphisms =
zip generatedSequence (drop 1 generatedSequence)
& find (uncurry (==))
& fmap snd
in case stableMorphisms of
Just morphisms -> morphisms
Nothing ->
Set.unions generatedSequence
identityMorphismSet :: Map FinMorphismId (FinObjectId, FinObjectId) -> Set FinMorphismId
identityMorphismSet index =
Map.keysSet index
& Set.filter isIdentityMorphismId
closeGeneratedMorphisms :: Map FinMorphismId (FinObjectId, FinObjectId) -> Map (FinMorphismId, FinMorphismId) FinMorphismId -> Set FinMorphismId -> Set FinMorphismId
closeGeneratedMorphisms _ compositionMap knownMorphisms =
let composedMorphisms =
compositionMap
& Map.toAscList
& foldMap
( \((leftMorphism, rightMorphism), composedMorphism) ->
if Set.member leftMorphism knownMorphisms && Set.member rightMorphism knownMorphisms
then [composedMorphism]
else []
)
& Set.fromList
in Set.union knownMorphisms composedMorphisms
associativityErrors :: Map FinMorphismId (FinObjectId, FinObjectId) -> Map (FinMorphismId, FinMorphismId) FinMorphismId -> (FinMorphismId, FinMorphismId, FinMorphismId) -> [FinCatValidationError]
associativityErrors index compositionMap (left, middle, right) =
let leftAssociated =
composeMorphismIds index compositionMap left middle
>>= (\composed -> composeMorphismIds index compositionMap composed right)
rightAssociated =
composeMorphismIds index compositionMap middle right
>>= composeMorphismIds index compositionMap left
in if leftAssociated == rightAssociated
then []
else [AssociativityViolation left middle right leftAssociated rightAssociated]
thinMorphismMap :: Map (FinObjectId, FinObjectId) FinMorphismId -> Map (FinObjectId, FinObjectId) [FinMorphismId]
thinMorphismMap =
fmap (: [])
thinEndpointMorphismsByTarget ::
Map (FinObjectId, FinObjectId) FinMorphismId ->
Map FinObjectId [((FinObjectId, FinObjectId), FinMorphismId)]
thinEndpointMorphismsByTarget endpointMorphisms =
endpointMorphisms
& Map.toAscList
& fmap (\entry@((_, targetId), _) -> (targetId, [entry]))
& Map.fromListWith (<>)
thinCompositionEntries :: Map (FinObjectId, FinObjectId) FinMorphismId -> [((FinMorphismId, FinMorphismId), FinMorphismId)]
thinCompositionEntries endpointMorphisms =
endpointMorphisms
& thinCompositionExpectations
& mapMaybe
( \(compositionKey, maybeComposedId) ->
fmap (\composedId -> (compositionKey, composedId)) maybeComposedId
)
thinCompositionExpectations :: Map (FinObjectId, FinObjectId) FinMorphismId -> [((FinMorphismId, FinMorphismId), Maybe FinMorphismId)]
thinCompositionExpectations endpointMorphisms =
endpointMorphisms
& Map.toAscList
& foldMap entriesForLeft
where
rightMorphismsByTarget = thinEndpointMorphismsByTarget endpointMorphisms
entriesForLeft ((leftSourceId, leftTargetId), leftId) =
Map.findWithDefault [] leftSourceId rightMorphismsByTarget
& foldMap (entryForRight leftTargetId leftId)
entryForRight leftTargetId leftId ((rightSourceId, _), rightId) =
[((leftId, rightId), thinCompositeMorphismId endpointMorphisms rightSourceId leftTargetId)]
thinCompositeMorphismId :: Map (FinObjectId, FinObjectId) FinMorphismId -> FinObjectId -> FinObjectId -> Maybe FinMorphismId
thinCompositeMorphismId endpointMorphisms sourceId targetId =
if sourceId == targetId
then Just (identityMorphismId sourceId)
else Map.lookup (sourceId, targetId) endpointMorphisms
validateThinCompositionClosure :: Map (FinObjectId, FinObjectId) FinMorphismId -> Map (FinMorphismId, FinMorphismId) FinMorphismId -> Validation (NonEmpty FinCatValidationError) ()
validateThinCompositionClosure endpointMorphisms compositionMap =
endpointMorphisms
& thinCompositionExpectations
& foldMap (thinCompositionExpectationErrors compositionMap)
& validationFromErrors
thinCompositionExpectationErrors :: Map (FinMorphismId, FinMorphismId) FinMorphismId -> ((FinMorphismId, FinMorphismId), Maybe FinMorphismId) -> [FinCatValidationError]
thinCompositionExpectationErrors compositionMap ((left, right), maybeExpected) =
case maybeExpected of
Nothing -> [MissingCompositionForPair left right]
Just expected ->
case Map.lookup (left, right) compositionMap of
Nothing -> [MissingCompositionForPair left right]
Just actual
| actual == expected -> []
| otherwise -> [CompositionResultEndpointMismatch left right actual]
thinCompositionMap :: [((FinMorphismId, FinMorphismId), FinMorphismId)] -> Map (FinMorphismId, FinMorphismId) FinMorphismId
thinCompositionMap =
Map.fromList
checkedThinFinCat :: Set FinObjectId -> Map (FinObjectId, FinObjectId) [FinMorphismId] -> Map (FinMorphismId, FinMorphismId) FinMorphismId -> Maybe (Either (NonEmpty FinCatValidationError) FinCat)
checkedThinFinCat objects morphismMap compositionMap =
strictThinEndpointMorphisms morphismMap
& fmap
( \endpointMorphisms ->
trustedThinFinCatFromTransitiveEndpoints objects endpointMorphisms
<$ validationToEither (validateThinCompositionClosure endpointMorphisms compositionMap)
)
mkFinCat :: Set FinObjectId -> Map (FinObjectId, FinObjectId) [FinMorphismId] -> Map (FinMorphismId, FinMorphismId) FinMorphismId -> Either (NonEmpty FinCatValidationError) FinCat
mkFinCat =
mkFinCatWithAssociativityValidation validateAssociativity
trustedFinCatWithGeneratorBasis :: Set FinMorphismId -> Set FinObjectId -> Map (FinObjectId, FinObjectId) [FinMorphismId] -> Map (FinMorphismId, FinMorphismId) FinMorphismId -> Either (NonEmpty FinCatValidationError) FinCat
trustedFinCatWithGeneratorBasis generatorIds =
mkFinCatWithAssociativityValidation (validateAssociativityAtGenerators generatorIds)
mkFinCatWithAssociativityValidation ::
( Map FinMorphismId (FinObjectId, FinObjectId) ->
Map (FinMorphismId, FinMorphismId) FinMorphismId ->
Validation (NonEmpty FinCatValidationError) ()
) ->
Set FinObjectId ->
Map (FinObjectId, FinObjectId) [FinMorphismId] ->
Map (FinMorphismId, FinMorphismId) FinMorphismId ->
Either (NonEmpty FinCatValidationError) FinCat
mkFinCatWithAssociativityValidation associativityValidation objects morphismMap compositionMap =
let normalizedMorphismMap = normalizeMorphismMap morphismMap
index = declaredMorphismIndex objects normalizedMorphismMap
basicValidation =
validateMorphismEndpoints objects morphismMap
*> validateReservedIds morphismMap
*> validateUniqueMorphismIds morphismMap
*> validateCompositionTable index compositionMap
genericValidation =
validateClosure index compositionMap
*> associativityValidation index compositionMap
in case validationToEither basicValidation of
Left errors -> Left errors
Right () ->
case checkedThinFinCat objects normalizedMorphismMap compositionMap of
Just checkedThinCategory -> checkedThinCategory
Nothing -> buildFinCat objects normalizedMorphismMap compositionMap <$ validationToEither genericValidation
trustedThinFinCatFromTransitiveEndpoints :: Set FinObjectId -> Map (FinObjectId, FinObjectId) FinMorphismId -> FinCat
trustedThinFinCatFromTransitiveEndpoints objects endpointMorphisms =
fromMaybe (buildThinFinCat objects endpointMorphisms) (strictDenseThinFinCat objects endpointMorphisms)
trustedDenseThinFinCatFromReachabilityRows :: Set FinObjectId -> Vector Integer -> FinCat
trustedDenseThinFinCatFromReachabilityRows =
buildDenseThinFinCat
finCatMorphismIdByEndpoints :: FinCat -> FinObjectId -> FinObjectId -> Maybe FinMorphismId
finCatMorphismIdByEndpoints category sourceId targetId =
case category of
DenseThinFinCat denseData
| sourceId == targetId && denseObjectIdInBounds denseData sourceId ->
Just (identityMorphismId sourceId)
| otherwise ->
denseEndpointMorphism denseData sourceId targetId
ExplicitFinCat explicitData
| sourceId == targetId && Set.member sourceId (explicitFinCatObjects explicitData) ->
Just (identityMorphismId sourceId)
| otherwise ->
case Map.lookup (sourceId, targetId) (explicitFinCatMorphismMap explicitData) of
Just [morphismId] -> Just morphismId
_ -> Nothing
ThinFinCat thinData
| sourceId == targetId && Set.member sourceId (thinFinCatObjects thinData) ->
Just (identityMorphismId sourceId)
| otherwise ->
Map.lookup (sourceId, targetId) (thinFinCatEndpointMorphisms thinData)
{-# INLINABLE finCatMorphismIdByEndpoints #-}
mkFinObject :: FinCat -> FinObjectId -> Either FinCatError FinObj
mkFinObject category objectId =
case category of
DenseThinFinCat denseData ->
if denseObjectIdInBounds denseData objectId
then Right (mkFinObj category objectId)
else Left (FinCatObjectNotDeclared (finCatHandle category) objectId)
_ ->
if Set.member objectId (finCatObjects category)
then Right (mkFinObj category objectId)
else Left (FinCatObjectNotDeclared (finCatHandle category) objectId)
mkFinObj :: FinCat -> FinObjectId -> FinObj
mkFinObj category objectId =
FinObj
{ finObjCategoryHandle = finCatHandle category,
finObjId = objectId
}
mkFinMorphism :: FinCat -> FinMorphismId -> Either FinCatError FinMor
mkFinMorphism category morphismId =
case category of
DenseThinFinCat denseData ->
case denseMorphismEndpoints denseData morphismId of
Nothing -> Left (FinCatMorphismNotDeclared (finCatHandle category) morphismId)
Just (sourceId, targetId) -> Right (mkFinMor category morphismId sourceId targetId)
_ ->
case Map.lookup morphismId (finCatMorphismIndex category) of
Nothing -> Left (FinCatMorphismNotDeclared (finCatHandle category) morphismId)
Just (sourceId, targetId) -> Right (mkFinMor category morphismId sourceId targetId)
mkFinMor :: FinCat -> FinMorphismId -> FinObjectId -> FinObjectId -> FinMor
mkFinMor category morphismId sourceId targetId =
FinMor
{ finMorCategoryHandle = finCatHandle category,
finMorId = morphismId,
finMorSourceId = sourceId,
finMorTargetId = targetId
}
-- | Trusted O(1) view of a morphism's source object. Total: every t'FinMor' is built
-- by a validated path (its constructor is unexported), so the recorded source is
-- necessarily a declared object of the morphism's category — no re-validation needed.
finMorDomObject :: FinMor -> FinObj
finMorDomObject morphism =
FinObj (finMorCategoryHandle morphism) (finMorSourceId morphism)
{-# INLINE finMorDomObject #-}
-- | Trusted O(1) view of a morphism's target object. Total, for the same reason as
-- 'finMorDomObject'.
finMorCodObject :: FinMor -> FinObj
finMorCodObject morphism =
FinObj (finMorCategoryHandle morphism) (finMorTargetId morphism)
{-# INLINE finMorCodObject #-}
-- | Total O(1) identity morphism at an already validated object handle.
finObjectIdentityMor :: FinObj -> FinMor
finObjectIdentityMor objectValue =
FinMor
(finObjCategoryHandle objectValue)
(identityMorphismId (finObjId objectValue))
(finObjId objectValue)
(finObjId objectValue)
{-# INLINE finObjectIdentityMor #-}
-- | The unique morphism between two endpoints when one exists (O(1) on the dense form),
-- including the identity when the endpoints coincide and the object is declared.
finCatHomMorphism :: FinCat -> FinObjectId -> FinObjectId -> Maybe FinMor
finCatHomMorphism category sourceId targetId =
fmap
(\morphismId -> FinMor (finCatHandle category) morphismId sourceId targetId)
(finCatMorphismIdByEndpoints category sourceId targetId)
{-# INLINE finCatHomMorphism #-}
allObjects :: FinCat -> [FinObj]
allObjects category =
fmap
(\objectId -> FinObj (finCatHandle category) objectId)
(Set.toAscList (finCatObjects category))
foldMapFinMorphisms :: Monoid monoid => (FinMor -> monoid) -> FinCat -> monoid
foldMapFinMorphisms morphismValue category =
let objectIds = Set.toAscList (finCatObjects category)
identityMorphisms =
objectIds
& foldMap (\objectId -> morphismValue (mkFinMor category (identityMorphismId objectId) objectId objectId))
nonIdentityMorphisms =
objectIds
& foldMap (foldMapNonIdentityMorphismsFrom morphismValue category)
in identityMorphisms <> nonIdentityMorphisms
foldMapFinMorphismsFrom :: Monoid monoid => (FinMor -> monoid) -> FinCat -> FinObjectId -> monoid
foldMapFinMorphismsFrom morphismValue category sourceId
| not (finCatHasObject category sourceId) = mempty
| otherwise =
morphismValue (mkFinMor category (identityMorphismId sourceId) sourceId sourceId)
<> foldMapNonIdentityMorphismsFrom morphismValue category sourceId
foldMapFinMorphismsTo :: Monoid monoid => (FinMor -> monoid) -> FinCat -> FinObjectId -> monoid
foldMapFinMorphismsTo morphismValue category targetId
| not (finCatHasObject category targetId) = mempty
| otherwise =
morphismValue (mkFinMor category (identityMorphismId targetId) targetId targetId)
<> foldMapNonIdentityMorphismsTo morphismValue category targetId
foldMapNonIdentityMorphismsFrom :: Monoid monoid => (FinMor -> monoid) -> FinCat -> FinObjectId -> monoid
foldMapNonIdentityMorphismsFrom morphismValue category sourceId =
case category of
ExplicitFinCat explicitData ->
Map.findWithDefault [] sourceId (explicitFinCatMorphismsBySource explicitData)
& foldMap
( \(targetId, morphismIds) ->
morphismIds
& foldMap (\morphismId -> morphismValue (mkFinMor category morphismId sourceId targetId))
)
ThinFinCat thinData ->
Map.findWithDefault [] sourceId (thinFinCatMorphismsBySource thinData)
& foldMap
( \(targetId, morphismId) ->
morphismValue (mkFinMor category morphismId sourceId targetId)
)
DenseThinFinCat denseData ->
denseMorphismsFrom category denseData sourceId
& foldMap morphismValue
foldMapNonIdentityMorphismsTo :: Monoid monoid => (FinMor -> monoid) -> FinCat -> FinObjectId -> monoid
foldMapNonIdentityMorphismsTo morphismValue category targetId =
case category of
ExplicitFinCat explicitData ->
Map.findWithDefault [] targetId (explicitFinCatMorphismsByTarget explicitData)
& foldMap
( \(sourceId, morphismIds) ->
morphismIds
& foldMap (\morphismId -> morphismValue (mkFinMor category morphismId sourceId targetId))
)
ThinFinCat thinData ->
Map.findWithDefault [] targetId (thinFinCatMorphismsByTarget thinData)
& foldMap
( \(sourceId, morphismId) ->
morphismValue (mkFinMor category morphismId sourceId targetId)
)
DenseThinFinCat denseData ->
denseMorphismsTo category denseData targetId
& foldMap morphismValue
allMorphisms :: FinCat -> [FinMor]
allMorphisms =
foldMapFinMorphisms (: [])
allMorphismsFrom :: FinCat -> FinObj -> [FinMor]
allMorphismsFrom category sourceObject
| finObjCategoryHandle sourceObject /= finCatHandle category = []
| otherwise = foldMapFinMorphismsFrom (: []) category (finObjId sourceObject)
denseMorphismsFrom :: FinCat -> DenseThinFinCatData -> FinObjectId -> [FinMor]
denseMorphismsFrom category denseData sourceObjectId@(FinObjectId sourceIndex) =
case (denseThinFinCatPrefixCounts denseData UVector.!? sourceIndex, denseThinFinCatReachabilityRows denseData Vector.!? sourceIndex) of
(Just startIndex, Just reachableBits) ->
bitsToAscList (denseThinFinCatObjectCount denseData) reachableBits
& zip [startIndex ..]
& fmap
( \(morphismIndex, targetIndex) ->
FinMor (finCatHandle category) (denseMorphismId morphismIndex) sourceObjectId (FinObjectId targetIndex)
)
_ -> []
denseMorphismsTo :: FinCat -> DenseThinFinCatData -> FinObjectId -> [FinMor]
denseMorphismsTo category denseData targetObjectId@(FinObjectId targetIndex) =
case denseThinFinCatPredecessorRows denseData Vector.!? targetIndex of
Nothing -> []
Just predecessorBits ->
bitsToAscList (denseThinFinCatObjectCount denseData) predecessorBits
>>= ( \sourceIndex ->
case denseEndpointMorphism denseData (FinObjectId sourceIndex) targetObjectId of
Nothing -> []
Just morphismId -> [mkFinMor category morphismId (FinObjectId sourceIndex) targetObjectId]
)
finMorphismsBySource :: FinCat -> Map FinObjectId [FinMor]
finMorphismsBySource category =
allObjects category
& fmap (\objectValue -> (finObjId objectValue, allMorphismsFrom category objectValue))
& Map.fromList
finCatComposableChains :: FinCat -> Natural -> [SizedComposableChain FinCat]
finCatComposableChains category dimensionBound =
genericTake (dimensionBound + 1) (finCatChainsByDimension category)
& foldMap (fmap sizedComposableChain)
finCatNonDegenerateChainsByDimension :: FinCat -> Natural -> [[ComposableChain FinCat]]
finCatNonDegenerateChainsByDimension category dimensionBound =
genericTake
(dimensionBound + 1)
(iterate extendNonIdentityChains seedChains)
where
nonIdentityBySource =
allObjects category
& fmap
( \objectValue ->
( finObjId objectValue,
foldMapNonIdentityMorphismsFrom (: []) category (finObjId objectValue)
)
)
& Map.fromList
seedChains =
allObjects category
& fmap singletonComposableChain
extendNonIdentityChains chains =
chains
>>= ( \chainValue ->
Map.findWithDefault [] (finObjId (chainTerminalObject chainValue)) nonIdentityBySource
& mapMaybe
(either (const Nothing) Just . appendComposableMorphism category chainValue)
)
finCatChainsByDimension :: FinCat -> [[ComposableChain FinCat]]
finCatChainsByDimension category =
iterate
(extendFinCatChains category (finMorphismsBySource category))
(fmap singletonComposableChain (allObjects category))
extendFinCatChains :: FinCat -> Map FinObjectId [FinMor] -> [ComposableChain FinCat] -> [ComposableChain FinCat]
extendFinCatChains category morphismsBySource chains =
chains
>>= ( \chainValue ->
mapMaybe
(either (const Nothing) Just . appendComposableMorphism category chainValue)
(Map.findWithDefault [] (finObjId (chainTerminalObject chainValue)) morphismsBySource)
)
morphismIsDeclared :: FinCat -> FinObjectId -> FinObjectId -> FinMorphismId -> Bool
morphismIsDeclared category sourceId targetId morphismId =
case category of
DenseThinFinCat denseData ->
denseMorphismEndpoints denseData morphismId == Just (sourceId, targetId)
_ ->
case Map.lookup morphismId (finCatMorphismIndex category) of
Just endpoints -> endpoints == (sourceId, targetId)
Nothing -> False
mkFinTwoMor :: FinMor -> FinMor -> Either FinCatError FinTwoMor
mkFinTwoMor sourceMorphism targetMorphism =
if finMorCategoryHandle sourceMorphism /= finMorCategoryHandle targetMorphism
then Left (FinCatMorphismWrongCategory (finMorCategoryHandle sourceMorphism) (finMorCategoryHandle targetMorphism) (finMorId targetMorphism))
else
if finMorSourceId sourceMorphism == finMorSourceId targetMorphism && finMorTargetId sourceMorphism == finMorTargetId targetMorphism
then Right (FinTwoMor sourceMorphism targetMorphism)
else Left (FinCatTwoMorphismBoundaryNotParallel sourceMorphism targetMorphism)
instance Category FinCat where
type Ob FinCat = FinObj
type Mor FinCat = FinMor
type TwoMor FinCat = FinTwoMor
type Compositor FinCat = FinCompositor
type CategoryError FinCat = FinCatError
identity category objectValue
| finObjCategoryHandle objectValue /= finCatHandle category =
Left (FinCatObjectWrongCategory (finCatHandle category) (finObjCategoryHandle objectValue) (finObjId objectValue))
| otherwise =
Right (FinMor (finCatHandle category) (identityMorphismId (finObjId objectValue)) (finObjId objectValue) (finObjId objectValue))
compose category left right
| finMorCategoryHandle left /= finCatHandle category =
Left (FinCatMorphismWrongCategory (finCatHandle category) (finMorCategoryHandle left) (finMorId left))
| finMorCategoryHandle right /= finCatHandle category =
Left (FinCatMorphismWrongCategory (finCatHandle category) (finMorCategoryHandle right) (finMorId right))
| finMorTargetId right /= finMorSourceId left =
Left (FinCatMorphismNotComposable (finMorId left) (finMorId right) (finMorSourceId left) (finMorTargetId right))
| isIdentityMorphismId (finMorId left) = Right (right, FinStrictCompositor)
| isIdentityMorphismId (finMorId right) = Right (left, FinStrictCompositor)
| otherwise =
composeNonIdentityMorphisms category left right
source category morphism
| finMorCategoryHandle morphism /= finCatHandle category =
Left (FinCatMorphismWrongCategory (finCatHandle category) (finMorCategoryHandle morphism) (finMorId morphism))
| otherwise = mkFinObject category (finMorSourceId morphism)
target category morphism
| finMorCategoryHandle morphism /= finCatHandle category =
Left (FinCatMorphismWrongCategory (finCatHandle category) (finMorCategoryHandle morphism) (finMorId morphism))
| otherwise = mkFinObject category (finMorTargetId morphism)
composeNonIdentityMorphisms :: FinCat -> FinMor -> FinMor -> Either FinCatError (FinMor, FinCompositor)
composeNonIdentityMorphisms category left right =
let composedSourceId = finMorSourceId right
composedTargetId = finMorTargetId left
in case category of
ExplicitFinCat explicitData ->
case Map.lookup (finMorId left, finMorId right) (explicitFinCatCompositionMap explicitData) of
Nothing -> Left (FinCatCompositionMissing (finMorId left) (finMorId right))
Just composedId ->
if morphismIsDeclared category composedSourceId composedTargetId composedId
then Right (FinMor (finCatHandle category) composedId composedSourceId composedTargetId, FinStrictCompositor)
else Left (FinCatCompositionResultInvalid composedId composedSourceId composedTargetId)
ThinFinCat thinData ->
if composedSourceId == composedTargetId
then Right (FinMor (finCatHandle category) (identityMorphismId composedSourceId) composedSourceId composedTargetId, FinStrictCompositor)
else
case Map.lookup (composedSourceId, composedTargetId) (thinFinCatEndpointMorphisms thinData) of
Nothing -> Left (FinCatCompositionMissing (finMorId left) (finMorId right))
Just composedId -> Right (FinMor (finCatHandle category) composedId composedSourceId composedTargetId, FinStrictCompositor)
DenseThinFinCat denseData ->
if composedSourceId == composedTargetId && denseObjectIdInBounds denseData composedSourceId
then Right (FinMor (finCatHandle category) (identityMorphismId composedSourceId) composedSourceId composedTargetId, FinStrictCompositor)
else
case denseEndpointMorphismIndex denseData composedSourceId composedTargetId of
Nothing -> Left (FinCatCompositionMissing (finMorId left) (finMorId right))
Just composedIndex -> Right (FinMor (finCatHandle category) (denseMorphismId composedIndex) composedSourceId composedTargetId, FinStrictCompositor)
instance HigherCategory FinCat where
source2 = finTwoSource
target2 = finTwoTarget
id2 morphism = FinTwoMor morphism morphism
hCompose category left right = do
sourceComposed <- composeMor @FinCat category (finTwoSource left) (finTwoSource right)
targetComposed <- composeMor @FinCat category (finTwoTarget left) (finTwoTarget right)
mkFinTwoMor sourceComposed targetComposed
vCompose category left right
| finMorCategoryHandle (finTwoSource left) /= finCatHandle category =
Left (FinCatMorphismWrongCategory (finCatHandle category) (finMorCategoryHandle (finTwoSource left)) (finMorId (finTwoSource left)))
| finMorCategoryHandle (finTwoTarget left) /= finCatHandle category =
Left (FinCatMorphismWrongCategory (finCatHandle category) (finMorCategoryHandle (finTwoTarget left)) (finMorId (finTwoTarget left)))
| finMorCategoryHandle (finTwoSource right) /= finCatHandle category =
Left (FinCatMorphismWrongCategory (finCatHandle category) (finMorCategoryHandle (finTwoSource right)) (finMorId (finTwoSource right)))
| finMorCategoryHandle (finTwoTarget right) /= finCatHandle category =
Left (FinCatMorphismWrongCategory (finCatHandle category) (finMorCategoryHandle (finTwoTarget right)) (finMorId (finTwoTarget right)))
| finTwoSource left /= finTwoTarget right =
Left (FinCatTwoMorphismNotVerticallyComposable left right)
| otherwise =
mkFinTwoMor (finTwoSource right) (finTwoTarget left)
compositor _ = FinAssociator
instance TwoCategory FinCat where
-- FinCat 2-cells are invertible equality witnesses, not directed rewrites.
inverse2 _ twoMorphism = Right (FinTwoMor (finTwoTarget twoMorphism) (finTwoSource twoMorphism))
instance Bicategory FinCat where
leftUnitor _ = FinLeftUnitor
rightUnitor _ = FinRightUnitor
associator _ = FinAssociator
instance FiniteComposableCategory FinCat where
enumerateObjects = allObjects
enumerateMorphisms = allMorphisms
enumerateMorphismsFrom = allMorphismsFrom
enumerateComposableChains = finCatComposableChains
enumerateNonDegenerateChainsByDimension = finCatNonDegenerateChainsByDimension