packages feed

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

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE GHC2024 #-}

module Moonlight.FiniteLattice.Internal.Validate
  ( validateDeclaredUniverse,
    validateClosedOrderRows,
    validateAntisymmetryRows,
    validateTopGreatestRows,
    validateBottomLeastRows,
    validateSuppliedOperations,
  )
where

import Control.Monad (unless, when)
import Data.Foldable qualified as Foldable
import Data.Map.Strict qualified as Map
import Data.Set (Set)
import Data.Set qualified as Set
import Moonlight.FiniteLattice.Internal.Index
  ( ContextIndex (..),
    contextIndexValueForKey,
  )
import Moonlight.FiniteLattice.Internal.Key
  ( ContextKey (..),
    contextKeySetFind,
    contextKeySetFindDifference,
  )
import Moonlight.FiniteLattice.Internal.Plan
  ( ContextPlan,
    contextPlanJoinKey,
    contextPlanMeetKey,
  )
import Moonlight.FiniteLattice.Internal.Relation
  ( ContextRows,
    contextKeyRelated,
    rowForRawKey,
  )
import Moonlight.FiniteLattice.Internal.Types
  ( ContextLatticeCompileError (..),
    ContextOrderDecl (..),
  )

validateDeclaredUniverse ::
  Ord c =>
  Set c ->
  ContextOrderDecl c ->
  Either (ContextLatticeCompileError c) ()
validateDeclaredUniverse universe declaration = do
  when (Set.null universe) (Left ContextLatticeEmptyUniverse)
  unless
    (Set.member (codTop declaration) universe)
    (Left (ContextLatticeUnknownTop (codTop declaration)))
  unless
    (Set.member (codBottom declaration) universe)
    (Left (ContextLatticeUnknownBottom (codBottom declaration)))
  case
    Foldable.find
      (\(lower, upper) ->
         not (Set.member lower universe && Set.member upper universe)
      )
      (codGeneratingPairs declaration)
    of
    Nothing -> Right ()
    Just pair -> Left (ContextLatticeUnknownRelationEndpoint pair)

validateClosedOrderRows ::
  ContextIndex c ->
  ContextRows ->
  ContextKey ->
  ContextKey ->
  Either (ContextLatticeCompileError c) ()
validateClosedOrderRows index upperRows topKey bottomKey = do
  validateReflexivityRows index upperRows
  validateAntisymmetryRows index upperRows
  validateTransitivityRows index upperRows
  validateTopGreatestRows index upperRows topKey
  validateBottomLeastRows index upperRows bottomKey

validateReflexivityRows ::
  ContextIndex c ->
  ContextRows ->
  Either (ContextLatticeCompileError c) ()
validateReflexivityRows index upperRows =
  case
    firstOrdinal
      (ciSize index)
      (\keyOrdinal ->
         not
           ( contextKeyRelated
               upperRows
               (ContextKey keyOrdinal)
               (ContextKey keyOrdinal)
           )
      )
    of
    Nothing -> Right ()
    Just keyOrdinal ->
      Left
        (ContextLatticeNotReflexive (contextIndexValueForKey index (ContextKey keyOrdinal)))

validateAntisymmetryRows ::
  ContextIndex c ->
  ContextRows ->
  Either (ContextLatticeCompileError c) ()
validateAntisymmetryRows index upperRows =
  checkLeft 0
  where
    size = ciSize index

    checkLeft !leftOrdinal
      | leftOrdinal >= size = Right ()
      | otherwise = checkRight leftOrdinal (leftOrdinal + 1)

    checkRight !leftOrdinal !rightOrdinal
      | rightOrdinal >= size = checkLeft (leftOrdinal + 1)
      | otherwise =
          let leftKey = ContextKey leftOrdinal
              rightKey = ContextKey rightOrdinal
           in if
                contextKeyRelated upperRows leftKey rightKey
                  && contextKeyRelated upperRows rightKey leftKey
                then
                  Left
                    ( ContextLatticeAntisymmetryViolation
                        (contextIndexValueForKey index leftKey)
                        (contextIndexValueForKey index rightKey)
                    )
                else checkRight leftOrdinal (rightOrdinal + 1)

validateTransitivityRows ::
  ContextIndex c ->
  ContextRows ->
  Either (ContextLatticeCompileError c) ()
validateTransitivityRows index upperRows =
  checkLeft 0
  where
    size = ciSize index

    checkLeft !leftOrdinal
      | leftOrdinal >= size = Right ()
      | otherwise =
          case
            contextKeySetFind
              (hasMissingSuccessor leftOrdinal)
              (rowForRawKey upperRows leftOrdinal)
            of
            Nothing -> checkLeft (leftOrdinal + 1)
            Just middleOrdinal ->
              case
                contextKeySetFindDifference
                  (rowForRawKey upperRows middleOrdinal)
                  (rowForRawKey upperRows leftOrdinal)
                of
                Nothing -> checkLeft (leftOrdinal + 1)
                Just rightOrdinal ->
                  Left
                    ( ContextLatticeNotTransitive
                        (contextIndexValueForKey index (ContextKey leftOrdinal))
                        (contextIndexValueForKey index (ContextKey middleOrdinal))
                        (contextIndexValueForKey index (ContextKey rightOrdinal))
                    )

    hasMissingSuccessor leftOrdinal middleOrdinal =
      case
        contextKeySetFindDifference
          (rowForRawKey upperRows middleOrdinal)
          (rowForRawKey upperRows leftOrdinal)
        of
        Nothing -> False
        Just _ -> True

validateTopGreatestRows ::
  ContextIndex c ->
  ContextRows ->
  ContextKey ->
  Either (ContextLatticeCompileError c) ()
validateTopGreatestRows index upperRows topKey =
  case
    firstOrdinal
      (ciSize index)
      (\keyOrdinal ->
         not (contextKeyRelated upperRows (ContextKey keyOrdinal) topKey)
      )
    of
    Nothing -> Right ()
    Just keyOrdinal ->
      Left
        ( ContextLatticeTopNotGreatest
            (contextIndexValueForKey index (ContextKey keyOrdinal))
        )

validateBottomLeastRows ::
  ContextIndex c ->
  ContextRows ->
  ContextKey ->
  Either (ContextLatticeCompileError c) ()
validateBottomLeastRows index upperRows bottomKey =
  case
    firstOrdinal
      (ciSize index)
      (\keyOrdinal ->
         not (contextKeyRelated upperRows bottomKey (ContextKey keyOrdinal))
      )
    of
    Nothing -> Right ()
    Just keyOrdinal ->
      Left
        ( ContextLatticeBottomNotLeast
            (contextIndexValueForKey index (ContextKey keyOrdinal))
        )

validateSuppliedOperations ::
  Ord c =>
  ContextIndex c ->
  ContextPlan ->
  (c -> c -> c) ->
  (c -> c -> c) ->
  Either (ContextLatticeCompileError c) ()
validateSuppliedOperations index plan joinFn meetFn =
  checkLeft 0
  where
    size = ciSize index

    checkLeft !leftOrdinal
      | leftOrdinal >= size = Right ()
      | otherwise = checkRight leftOrdinal 0

    checkRight !leftOrdinal !rightOrdinal
      | rightOrdinal >= size = checkLeft (leftOrdinal + 1)
      | otherwise = do
          let leftKey = ContextKey leftOrdinal
              rightKey = ContextKey rightOrdinal
              leftContext = contextIndexValueForKey index leftKey
              rightContext = contextIndexValueForKey index rightKey
              joined = joinFn leftContext rightContext
              met = meetFn leftContext rightContext
          joinedKey <-
            maybe
              (Left (ContextLatticeJoinOutsideUniverse leftContext rightContext joined))
              Right
              (Map.lookup joined (ciKeyByContext index))
          metKey <-
            maybe
              (Left (ContextLatticeMeetOutsideUniverse leftContext rightContext met))
              Right
              (Map.lookup met (ciKeyByContext index))
          let planJoinedKey = contextPlanJoinKey plan leftKey rightKey
              planMetKey = contextPlanMeetKey plan leftKey rightKey
          unless
            (joinedKey == planJoinedKey)
            (Left (ContextLatticeInvalidJoin leftContext rightContext joined))
          unless
            (metKey == planMetKey)
            (Left (ContextLatticeInvalidMeet leftContext rightContext met))
          checkRight leftOrdinal (rightOrdinal + 1)

firstOrdinal :: Int -> (Int -> Bool) -> Maybe Int
firstOrdinal size predicate =
  go 0
  where
    go !ordinal
      | ordinal >= size = Nothing
      | predicate ordinal = Just ordinal
      | otherwise = go (ordinal + 1)