packages feed

moonlight-algebra-0.1.0.0: src-finite-lattice/Moonlight/FiniteLattice/Internal/Recognize.hs

{-# LANGUAGE GHC2024 #-}
{-# LANGUAGE TupleSections #-}

module Moonlight.FiniteLattice.Internal.Recognize
  ( specializedContextPlanFromDeclaredPairs,
    specializedContextPlanFromRows,
  )
where

import Control.Applicative ((<|>))
import Control.Monad (foldM)
import Data.Bits
  ( (.&.),
    (.|.),
    bit,
    countTrailingZeros,
  )
import Data.Foldable qualified as Foldable
import Data.IntMap.Strict (IntMap)
import Data.IntMap.Strict qualified as IntMap
import Data.IntSet (IntSet)
import Data.IntSet qualified as IntSet
import Data.List (unfoldr)
import Data.Map.Strict qualified as Map
import Data.Set (Set)
import Data.Set qualified as Set
import Data.Vector.Unboxed qualified as UVector
import Data.Word (Word64)
import Moonlight.FiniteLattice.Internal.Key
  ( ContextKey (..),
    contextKeySetAll,
    contextKeySetCardinality,
    contextKeySetChunkCount,
    contextKeySetFromKeys,
    contextKeySetMember,
  )
import Moonlight.FiniteLattice.Internal.Plan
  ( ContextBooleanPlan (..),
    ContextBoundedFanPlan (..),
    ContextMaskPlan (..),
    ContextPlan (..),
    ContextTotalOrderPlan (..),
  )
import Moonlight.FiniteLattice.Internal.Relation
  ( ContextRows,
    contextKeyRelated,
    rowForRawKey,
  )
import Moonlight.FiniteLattice.Internal.Topological
  ( topologicalOrder,
  )
specializedContextPlanFromDeclaredPairs ::
  Int ->
  ContextKey ->
  ContextKey ->
  [(ContextKey, ContextKey)] ->
  Maybe ContextPlan
specializedContextPlanFromDeclaredPairs size topKey bottomKey declaredPairs =
  totalOrderPlanFromDeclaredPairs size topKey bottomKey declaredPairs
    <|> booleanPlanFromDeclaredPairs size topKey bottomKey declaredPairs
    <|> boundedFanPlanFromDeclaredPairs size topKey bottomKey declaredPairs

specializedContextPlanFromRows ::
  Int ->
  ContextKey ->
  ContextKey ->
  ContextRows ->
  ContextRows ->
  Maybe ContextPlan
specializedContextPlanFromRows size topKey bottomKey upperRows lowerRows =
  totalOrderPlanFromRows size topKey bottomKey upperRows lowerRows
    <|> booleanPlanFromRows size topKey bottomKey upperRows lowerRows
    <|> boundedFanPlanFromRows size topKey bottomKey upperRows lowerRows

totalOrderPlanFromDeclaredPairs ::
  Int ->
  ContextKey ->
  ContextKey ->
  [(ContextKey, ContextKey)] ->
  Maybe ContextPlan
totalOrderPlanFromDeclaredPairs size topKey@(ContextKey topOrdinal) bottomKey@(ContextKey bottomOrdinal) declaredPairs
  | size <= 0 = Nothing
  | not (contextKeysInBounds size topKey bottomKey) = Nothing
  | size == 1 =
      if topKey == bottomKey && Set.null strictPairs
        then
          Just
            ( TotalOrderPlan
                ContextTotalOrderPlan
                  { ctoTopKey = topKey,
                    ctoRankByKey = UVector.singleton 0,
                    ctoKeyByRank = UVector.singleton bottomOrdinal
                  }
            )
        else Nothing
  | topKey == bottomKey = Nothing
  | Set.size strictPairs /= size - 1 = Nothing
  | IntMap.size successorBySource /= size - 1 = Nothing
  | IntMap.size predecessorByTarget /= size - 1 = Nothing
  | IntMap.member bottomOrdinal predecessorByTarget = Nothing
  | IntMap.member topOrdinal successorBySource = Nothing
  | otherwise = do
      let path = successorPath size successorBySource bottomOrdinal
      guardMaybe (length path == size)
      guardMaybe (IntSet.fromList path == allContextKeyOrdinals size)
      let rankByKey = totalOrderRankByKey size path
      guardMaybe (totalOrderKeyRankValue rankByKey bottomOrdinal == Just 0)
      guardMaybe (totalOrderKeyRankValue rankByKey topOrdinal == Just (size - 1))
      pure
        ( if path == [0 .. size - 1]
            then OrdinalTotalOrderPlan size
            else
              TotalOrderPlan
              ContextTotalOrderPlan
                { ctoTopKey = topKey,
                  ctoRankByKey = rankByKey,
                    ctoKeyByRank = UVector.fromList path
                  }
        )
  where
    strictPairs = declaredStrictPairOrdinals declaredPairs
    successorBySource = IntMap.fromList (Set.toAscList strictPairs)
    predecessorByTarget =
      IntMap.fromList
        [ (targetOrdinal, sourceOrdinal)
        | (sourceOrdinal, targetOrdinal) <- Set.toAscList strictPairs
        ]

totalOrderPlanFromRows ::
  Int ->
  ContextKey ->
  ContextKey ->
  ContextRows ->
  ContextRows ->
  Maybe ContextPlan
totalOrderPlanFromRows size topKey bottomKey upperRows lowerRows = do
  guardMaybe (size > 0)
  let rankEntries =
        [ (keyOrdinal, contextKeySetCardinality (rowForRawKey lowerRows keyOrdinal) - 1)
        | keyOrdinal <- [0 .. size - 1]
        ]
      ranks = IntSet.fromList (fmap snd rankEntries)
  guardMaybe (ranks == IntSet.fromDistinctAscList [0 .. size - 1])
  guardMaybe
    ( all
        (\keyOrdinal ->
           contextKeySetCardinality (rowForRawKey upperRows keyOrdinal)
             + contextKeySetCardinality (rowForRawKey lowerRows keyOrdinal)
             == size + 1
        )
        [0 .. size - 1]
    )
  let rankByKey =
        UVector.accum
          (\_ rank -> rank)
          (UVector.replicate size (-1))
          rankEntries
      keyByRank =
        UVector.accum
          (\_ keyOrdinal -> keyOrdinal)
          (UVector.replicate size (-1))
          [ (rank, keyOrdinal)
          | (keyOrdinal, rank) <- rankEntries
          ]
  guardMaybe (UVector.all (>= 0) rankByKey)
  guardMaybe (UVector.all (>= 0) keyByRank)
  guardMaybe (totalOrderKeyRankValue rankByKey (contextKeyOrdinal bottomKey) == Just 0)
  guardMaybe (totalOrderKeyRankValue rankByKey (contextKeyOrdinal topKey) == Just (size - 1))
  pure
    ( if all (uncurry (==)) rankEntries
        then OrdinalTotalOrderPlan size
        else
          TotalOrderPlan
            ContextTotalOrderPlan
              { ctoTopKey = topKey,
                ctoRankByKey = rankByKey,
                ctoKeyByRank = keyByRank
              }
    )

boundedFanPlanFromDeclaredPairs ::
  Int ->
  ContextKey ->
  ContextKey ->
  [(ContextKey, ContextKey)] ->
  Maybe ContextPlan
boundedFanPlanFromDeclaredPairs size topKey@(ContextKey topOrdinal) bottomKey@(ContextKey bottomOrdinal) declaredPairs
  | size < 3 = Nothing
  | topKey == bottomKey = Nothing
  | not (contextKeysInBounds size topKey bottomKey) = Nothing
  | declaredStrictPairOrdinals declaredPairs /= expectedPairs = Nothing
  | otherwise =
      Just
        ( if bottomOrdinal == 0 && topOrdinal == size - 1
            then OrdinalBoundedFanPlan size
            else
              BoundedFanPlan
                ContextBoundedFanPlan
                  { cbfSize = size,
                    cbfTopKey = topKey,
                    cbfBottomKey = bottomKey,
                    cbfAtomKeys = contextKeySetFromKeys chunkCount atomOrdinals,
                    cbfAllKeys = contextKeySetAll size
                  }
        )
  where
    chunkCount = contextKeySetChunkCount size
    atomOrdinals =
      [ keyOrdinal
      | keyOrdinal <- [0 .. size - 1],
        keyOrdinal /= topOrdinal,
        keyOrdinal /= bottomOrdinal
      ]
    expectedPairs =
      Set.fromList
        ( fmap (bottomOrdinal,) atomOrdinals
            <> fmap (,topOrdinal) atomOrdinals
        )

boundedFanPlanFromRows ::
  Int ->
  ContextKey ->
  ContextKey ->
  ContextRows ->
  ContextRows ->
  Maybe ContextPlan
boundedFanPlanFromRows size topKey bottomKey upperRows lowerRows = do
  guardMaybe (size >= 3 && topKey /= bottomKey)
  guardMaybe
    ( all
        (boundedFanKeyRowsMatch size topKey bottomKey upperRows lowerRows)
        [0 .. size - 1]
    )
  pure
    ( if contextKeyOrdinal bottomKey == 0 && contextKeyOrdinal topKey == size - 1
        then OrdinalBoundedFanPlan size
        else
          BoundedFanPlan
            ContextBoundedFanPlan
              { cbfSize = size,
                cbfTopKey = topKey,
                cbfBottomKey = bottomKey,
                cbfAtomKeys =
                  contextKeySetFromKeys
                    (contextKeySetChunkCount size)
                    [ keyOrdinal
                    | keyOrdinal <- [0 .. size - 1],
                      ContextKey keyOrdinal /= topKey,
                      ContextKey keyOrdinal /= bottomKey
                    ],
                cbfAllKeys = contextKeySetAll size
              }
    )

boundedFanKeyRowsMatch ::
  Int ->
  ContextKey ->
  ContextKey ->
  ContextRows ->
  ContextRows ->
  Int ->
  Bool
boundedFanKeyRowsMatch size topKey bottomKey upperRows lowerRows keyOrdinal
  | key == bottomKey =
      upperCardinality == size
        && lowerCardinality == 1
        && contextKeySetMember keyOrdinal lowerRow
  | key == topKey =
      upperCardinality == 1
        && lowerCardinality == size
        && contextKeySetMember keyOrdinal upperRow
  | otherwise =
      upperCardinality == 2
        && lowerCardinality == 2
        && contextKeySetMember keyOrdinal upperRow
        && contextKeySetMember (contextKeyOrdinal topKey) upperRow
        && contextKeySetMember keyOrdinal lowerRow
        && contextKeySetMember (contextKeyOrdinal bottomKey) lowerRow
  where
    key = ContextKey keyOrdinal
    upperRow = rowForRawKey upperRows keyOrdinal
    lowerRow = rowForRawKey lowerRows keyOrdinal
    upperCardinality = contextKeySetCardinality upperRow
    lowerCardinality = contextKeySetCardinality lowerRow

booleanPlanFromDeclaredPairs ::
  Int ->
  ContextKey ->
  ContextKey ->
  [(ContextKey, ContextKey)] ->
  Maybe ContextPlan
booleanPlanFromDeclaredPairs size topKey bottomKey@(ContextKey bottomOrdinal) declaredPairs = do
  atomCount <- booleanAtomCountFromSize size
  guardMaybe (atomCount >= 2)
  guardMaybe (contextKeysInBounds size topKey bottomKey && topKey /= bottomKey)
  topologicalOrder' <- coverTopologicalOrder size strictPairs
  let successorSets = coverSuccessorSets strictPairs
      predecessorSets = coverPredecessorSets strictPairs
      atomOrdinals =
        IntSet.toAscList
          (IntMap.findWithDefault IntSet.empty bottomOrdinal successorSets)
  guardMaybe (length atomOrdinals == atomCount)
  let atomBitByKey =
        IntMap.fromDistinctAscList
          [ (atomOrdinal, bit atomIndex)
          | (atomOrdinal, atomIndex) <- zip atomOrdinals [0 .. atomCount - 1]
          ]
      fullMask = bit atomCount - 1
  maskByKeyMap <-
    booleanMaskMapFromCover
      size
      bottomOrdinal
      atomBitByKey
      predecessorSets
      topologicalOrder'
  let keyByMaskMap =
        Map.fromList
          [ (mask, keyOrdinal)
          | (keyOrdinal, mask) <- IntMap.toAscList maskByKeyMap
          ]
  guardMaybe (IntMap.size maskByKeyMap == size)
  guardMaybe (Map.size keyByMaskMap == size)
  guardMaybe (IntMap.lookup bottomOrdinal maskByKeyMap == Just 0)
  guardMaybe (IntMap.lookup (contextKeyOrdinal topKey) maskByKeyMap == Just fullMask)
  let maskByKey =
        UVector.generate
          size
          (\keyOrdinal -> IntMap.findWithDefault maxBound keyOrdinal maskByKeyMap)
      keyByMask =
        UVector.generate
          size
          (\maskOrdinal -> Map.findWithDefault (-1) (fromIntegral maskOrdinal) keyByMaskMap)
  guardMaybe (UVector.all (/= maxBound) maskByKey)
  guardMaybe (UVector.all (>= 0) keyByMask)
  guardMaybe
    ( strictPairs
        == booleanExpectedCoverPairs atomCount fullMask keyByMask
    )
  pure
    ( MaskPlan
        ( BooleanPlan
            ContextBooleanPlan
              { cboAtomCount = atomCount,
                cboFullMask = fullMask,
                cboMaskByKey = maskByKey,
                cboKeyByMask = keyByMask
              }
        )
    )
  where
    strictPairs = declaredStrictPairOrdinals declaredPairs

booleanPlanFromRows ::
  Int ->
  ContextKey ->
  ContextKey ->
  ContextRows ->
  ContextRows ->
  Maybe ContextPlan
booleanPlanFromRows size topKey bottomKey upperRows lowerRows = do
  atomCount <- booleanAtomCountFromSize size
  guardMaybe (atomCount >= 2)
  guardMaybe (contextKeysInBounds size topKey bottomKey)
  let atomOrdinals =
        [ keyOrdinal
        | keyOrdinal <- [0 .. size - 1],
          let lowerSet = rowForRawKey lowerRows keyOrdinal,
          ContextKey keyOrdinal /= bottomKey,
          contextKeySetCardinality lowerSet == 2,
          contextKeySetMember (contextKeyOrdinal bottomKey) lowerSet,
          contextKeySetMember keyOrdinal lowerSet
        ]
  guardMaybe (length atomOrdinals == atomCount)
  let atomBitByKey =
        IntMap.fromDistinctAscList
          [ (atomOrdinal, bit atomIndex)
          | (atomOrdinal, atomIndex) <- zip atomOrdinals [0 .. atomCount - 1]
          ]
      fullMask = bit atomCount - 1
      maskByKeyMap =
        IntMap.fromDistinctAscList
          [ (keyOrdinal, booleanMaskForRows upperRows atomBitByKey keyOrdinal)
          | keyOrdinal <- [0 .. size - 1]
          ]
      keyByMaskMap =
        Map.fromList
          [ (mask, keyOrdinal)
          | (keyOrdinal, mask) <- IntMap.toAscList maskByKeyMap
          ]
  guardMaybe (Map.size keyByMaskMap == size)
  guardMaybe (IntMap.lookup (contextKeyOrdinal bottomKey) maskByKeyMap == Just 0)
  guardMaybe (IntMap.lookup (contextKeyOrdinal topKey) maskByKeyMap == Just fullMask)
  guardMaybe (booleanOrderMatchesMasks size upperRows maskByKeyMap)
  let maskByKey =
        UVector.generate
          size
          (\keyOrdinal -> IntMap.findWithDefault maxBound keyOrdinal maskByKeyMap)
      keyByMask =
        UVector.generate
          size
          (\maskOrdinal -> Map.findWithDefault (-1) (fromIntegral maskOrdinal) keyByMaskMap)
  guardMaybe (UVector.all (/= maxBound) maskByKey)
  guardMaybe (UVector.all (>= 0) keyByMask)
  pure
    ( MaskPlan
        ( BooleanPlan
            ContextBooleanPlan
              { cboAtomCount = atomCount,
                cboFullMask = fullMask,
                cboMaskByKey = maskByKey,
                cboKeyByMask = keyByMask
              }
        )
    )

booleanMaskForRows ::
  ContextRows ->
  IntMap Word64 ->
  Int ->
  Word64
booleanMaskForRows upperRows atomBitByKey keyOrdinal =
  IntMap.foldlWithKey'
    (\mask atomOrdinal atomBit ->
       if
         contextKeyRelated
           upperRows
           (ContextKey atomOrdinal)
           (ContextKey keyOrdinal)
         then mask .|. atomBit
         else mask
    )
    0
    atomBitByKey

booleanOrderMatchesMasks ::
  Int ->
  ContextRows ->
  IntMap Word64 ->
  Bool
booleanOrderMatchesMasks size upperRows maskByKeyMap =
  all relationMatches
    [ (leftOrdinal, rightOrdinal)
    | leftOrdinal <- [0 .. size - 1],
      rightOrdinal <- [0 .. size - 1]
    ]
  where
    relationMatches (leftOrdinal, rightOrdinal) =
      case
        ( IntMap.lookup leftOrdinal maskByKeyMap,
          IntMap.lookup rightOrdinal maskByKeyMap
        )
        of
        (Just leftMask, Just rightMask) ->
          contextKeyRelated
            upperRows
            (ContextKey leftOrdinal)
            (ContextKey rightOrdinal)
            == (leftMask .&. rightMask == leftMask)
        _ -> False

booleanMaskMapFromCover ::
  Int ->
  Int ->
  IntMap Word64 ->
  IntMap IntSet ->
  [Int] ->
  Maybe (IntMap Word64)
booleanMaskMapFromCover size bottomOrdinal atomBitByKey predecessorSets =
  foldM includeKey IntMap.empty
  where
    includeKey masksByKey keyOrdinal
      | keyOrdinal < 0 || keyOrdinal >= size = Nothing
      | keyOrdinal == bottomOrdinal =
          Just (IntMap.insert keyOrdinal 0 masksByKey)
      | Just atomMask <- IntMap.lookup keyOrdinal atomBitByKey =
          Just (IntMap.insert keyOrdinal atomMask masksByKey)
      | otherwise = do
          let predecessorOrdinals =
                IntSet.toAscList
                  (IntMap.findWithDefault IntSet.empty keyOrdinal predecessorSets)
          guardMaybe (not (null predecessorOrdinals))
          predecessorMasks <-
            traverse (`IntMap.lookup` masksByKey) predecessorOrdinals
          let mask = Foldable.foldl' (.|.) 0 predecessorMasks
          guardMaybe (mask /= 0)
          Just (IntMap.insert keyOrdinal mask masksByKey)

booleanExpectedCoverPairs ::
  Int ->
  Word64 ->
  UVector.Vector Int ->
  Set (Int, Int)
booleanExpectedCoverPairs atomCount fullMask keyByMask =
  Set.fromList
    [ (lowerOrdinal, upperOrdinal)
    | lowerMask <- [0 .. fullMask],
      atomIndex <- [0 .. atomCount - 1],
      lowerMask .&. bit atomIndex == 0,
      let upperMask = lowerMask .|. bit atomIndex,
      Just lowerOrdinal <- [keyOrdinalForMask keyByMask lowerMask],
      Just upperOrdinal <- [keyOrdinalForMask keyByMask upperMask]
    ]

keyOrdinalForMask :: UVector.Vector Int -> Word64 -> Maybe Int
keyOrdinalForMask keyByMask mask
  | mask > fromIntegral (maxBound :: Int) = Nothing
  | otherwise =
      case keyByMask UVector.!? fromIntegral mask of
        Just keyOrdinal
          | keyOrdinal >= 0 -> Just keyOrdinal
        _ -> Nothing

booleanAtomCountFromSize :: Int -> Maybe Int
booleanAtomCountFromSize size
  | size <= 0 = Nothing
  | size .&. (size - 1) /= 0 = Nothing
  | otherwise = Just (countTrailingZeros size)

coverTopologicalOrder :: Int -> Set (Int, Int) -> Maybe [Int]
coverTopologicalOrder size strictPairs =
  topologicalOrder size $ \sourceOrdinal step initial ->
    IntSet.foldr
      step
      initial
      (IntMap.findWithDefault IntSet.empty sourceOrdinal successorSets)
  where
    successorSets = coverSuccessorSets strictPairs

coverSuccessorSets :: Set (Int, Int) -> IntMap IntSet
coverSuccessorSets =
  Foldable.foldl'
    (\successors (sourceOrdinal, targetOrdinal) ->
       IntMap.insertWith
         IntSet.union
         sourceOrdinal
         (IntSet.singleton targetOrdinal)
         successors
    )
    IntMap.empty
    . Set.toAscList

coverPredecessorSets :: Set (Int, Int) -> IntMap IntSet
coverPredecessorSets =
  Foldable.foldl'
    (\predecessors (sourceOrdinal, targetOrdinal) ->
       IntMap.insertWith
         IntSet.union
         targetOrdinal
         (IntSet.singleton sourceOrdinal)
         predecessors
    )
    IntMap.empty
    . Set.toAscList

declaredStrictPairOrdinals ::
  [(ContextKey, ContextKey)] ->
  Set (Int, Int)
declaredStrictPairOrdinals =
  Set.fromList
    . foldMap
      (\(ContextKey sourceOrdinal, ContextKey targetOrdinal) ->
         if sourceOrdinal == targetOrdinal
           then []
           else [(sourceOrdinal, targetOrdinal)]
      )

successorPath :: Int -> IntMap Int -> Int -> [Int]
successorPath size successorBySource bottomOrdinal =
  take size (unfoldr next (Just bottomOrdinal))
  where
    next Nothing = Nothing
    next (Just sourceOrdinal) =
      Just (sourceOrdinal, IntMap.lookup sourceOrdinal successorBySource)

totalOrderRankByKey :: Int -> [Int] -> UVector.Vector Int
totalOrderRankByKey size path =
  UVector.accum
    (\_ rank -> rank)
    (UVector.replicate size (-1))
    [ (keyOrdinal, rank)
    | (keyOrdinal, rank) <- zip path [0 ..],
      keyOrdinal >= 0,
      keyOrdinal < size
    ]

totalOrderKeyRankValue :: UVector.Vector Int -> Int -> Maybe Int
totalOrderKeyRankValue rankByKey keyOrdinal =
  case rankByKey UVector.!? keyOrdinal of
    Just rank
      | rank >= 0 -> Just rank
    _ -> Nothing

allContextKeyOrdinals :: Int -> IntSet
allContextKeyOrdinals size =
  IntSet.fromDistinctAscList [0 .. size - 1]

contextKeysInBounds :: Int -> ContextKey -> ContextKey -> Bool
contextKeysInBounds size leftKey rightKey =
  contextKeyInBounds size leftKey && contextKeyInBounds size rightKey

contextKeyInBounds :: Int -> ContextKey -> Bool
contextKeyInBounds size (ContextKey keyOrdinal) =
  keyOrdinal >= 0 && keyOrdinal < size

guardMaybe :: Bool -> Maybe ()
guardMaybe condition =
  if condition then Just () else Nothing