packages feed

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

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

-- | Compiled monotone endomaps over a 'ContextLattice' and their least and
-- greatest fixed points.
module Moonlight.FiniteLattice.Fixpoint
  ( ResidentMonotoneMap,
    ContextMonotoneMapError (..),
    compileResidentMonotoneMap,
    compileResidentMonotoneKeyMap,
    leastResidentFixpoint,
    greatestResidentFixpoint,
    leastContextFixpoint,
    greatestContextFixpoint,
  )
where

import Data.Kind (Type)
import Data.Vector.Unboxed qualified as UVector
import Moonlight.FiniteLattice.Internal.Invariant
  ( unboxedIndexInvariant,
  )
import Moonlight.FiniteLattice.Internal.Key
  ( ContextKey (..),
    contextKeySetFind,
  )
import Moonlight.FiniteLattice.Internal.Plan
  ( contextPlanLeq,
    contextPlanMonotonicityTargets,
  )
import Moonlight.FiniteLattice.Internal.Types
  ( ContextLattice (..),
    ResidentContext (..),
    ResidentContextElement,
    ResidentContextKey (..),
    contextKeyForMaybe,
    contextValueForKey,
    residentContextElementForKey,
    residentContextElementValue,
    residentKeyFromContextKey,
  )
import Moonlight.FiniteLattice.Resident
  ( withResidentContext,
  )

type ResidentMonotoneMap :: Type -> Type
newtype ResidentMonotoneMap s = ResidentMonotoneMap
  { residentMonotoneMapImage :: UVector.Vector Int
  }

type role ResidentMonotoneMap nominal

type ContextMonotoneMapError :: Type -> Type
data ContextMonotoneMapError c
  = ContextEndomapOutsideUniverse !c !c
  | ContextEndomapNotMonotone !c !c !c !c
  deriving stock (Eq, Ord, Show, Read)

type role ContextMonotoneMapError nominal

compileResidentMonotoneMap ::
  Ord c =>
  ResidentContext s c ->
  (c -> c) ->
  Either (ContextMonotoneMapError c) (ResidentMonotoneMap s)
compileResidentMonotoneMap (ResidentContext lattice) step = do
  image <-
    UVector.generateM
      (clSize lattice)
      (compileImageAt lattice step)
  validateMonotonicity lattice image
  pure (ResidentMonotoneMap image)

-- | Like 'compileResidentMonotoneMap', but closure is structural — branded keys
-- cannot leave the context — so only monotonicity is checked.
compileResidentMonotoneKeyMap ::
  ResidentContext s c ->
  (ResidentContextKey s -> ResidentContextKey s) ->
  Either (ContextMonotoneMapError c) (ResidentMonotoneMap s)
compileResidentMonotoneKeyMap (ResidentContext lattice) step =
  let image =
        UVector.generate
          (clSize lattice)
          ( \keyOrdinal ->
              residentContextKeyOrdinal
                (step (residentKeyFromContextKey (ContextKey keyOrdinal)))
          )
   in validateMonotonicity lattice image *> pure (ResidentMonotoneMap image)

compileImageAt ::
  Ord c =>
  ContextLattice c ->
  (c -> c) ->
  Int ->
  Either (ContextMonotoneMapError c) Int
compileImageAt lattice step keyOrdinal =
  let input = contextValueForKey lattice (ContextKey keyOrdinal)
      output = step input
   in case contextKeyForMaybe lattice output of
        Nothing ->
          Left (ContextEndomapOutsideUniverse input output)
        Just outputKey -> Right (contextKeyOrdinal outputKey)

validateMonotonicity ::
  ContextLattice c ->
  UVector.Vector Int ->
  Either (ContextMonotoneMapError c) ()
validateMonotonicity lattice image =
  checkLower 0
  where
    checkLower !lowerOrdinal
      | lowerOrdinal >= clSize lattice = Right ()
      | otherwise = do
          let targets =
                contextPlanMonotonicityTargets (clPlan lattice) (ContextKey lowerOrdinal)
          case contextKeySetFind (violates lowerOrdinal) targets of
            Nothing -> checkLower (lowerOrdinal + 1)
            Just upperOrdinal ->
              let lowerKey = ContextKey lowerOrdinal
                  upperKey = ContextKey upperOrdinal
                  lowerImageKey = imageKey image lowerKey
                  upperImageKey = imageKey image upperKey
               in Left
                    ( ContextEndomapNotMonotone
                        (contextValueForKey lattice lowerKey)
                        (contextValueForKey lattice upperKey)
                        (contextValueForKey lattice lowerImageKey)
                        (contextValueForKey lattice upperImageKey)
                    )

    violates lowerOrdinal upperOrdinal =
      not
        ( contextPlanLeq
            (clPlan lattice)
            (imageKey image (ContextKey lowerOrdinal))
            (imageKey image (ContextKey upperOrdinal))
        )

-- | Least fixed point of a compiled monotone endomap.
--
-- The orbit from bottom is ascending (bottom <= x1; and @xn <= x(n+1)@ gives
-- @f xn <= f x(n+1)@ by monotonicity), so finiteness forces stabilization;
-- induction gives @xn <= p@ for every fixed point @p@, so it is the least.
leastResidentFixpoint ::
  ResidentContext s c ->
  ResidentMonotoneMap s ->
  ResidentContextElement s c
leastResidentFixpoint context@(ResidentContext lattice) monotoneMap =
  residentContextElementForKey
    context
    (residentKeyFromContextKey (iterateToFixpoint monotoneMap (clBottomKey lattice)))

-- | Greatest fixed point; the order-dual proof starts from top.
greatestResidentFixpoint ::
  ResidentContext s c ->
  ResidentMonotoneMap s ->
  ResidentContextElement s c
greatestResidentFixpoint context@(ResidentContext lattice) monotoneMap =
  residentContextElementForKey
    context
    (residentKeyFromContextKey (iterateToFixpoint monotoneMap (clTopKey lattice)))

leastContextFixpoint ::
  Ord c =>
  ContextLattice c ->
  (c -> c) ->
  Either (ContextMonotoneMapError c) c
leastContextFixpoint lattice step =
  withResidentContext lattice $ \context -> do
    monotoneMap <- compileResidentMonotoneMap context step
    pure
      (residentContextElementValue (leastResidentFixpoint context monotoneMap))

greatestContextFixpoint ::
  Ord c =>
  ContextLattice c ->
  (c -> c) ->
  Either (ContextMonotoneMapError c) c
greatestContextFixpoint lattice step =
  withResidentContext lattice $ \context -> do
    monotoneMap <- compileResidentMonotoneMap context step
    pure
      (residentContextElementValue (greatestResidentFixpoint context monotoneMap))

iterateToFixpoint :: ResidentMonotoneMap s -> ContextKey -> ContextKey
iterateToFixpoint monotoneMap =
  go
  where
    go !currentKey =
      let nextKey = imageKey (residentMonotoneMapImage monotoneMap) currentKey
       in if nextKey == currentKey
            then currentKey
            else go nextKey

imageKey :: UVector.Vector Int -> ContextKey -> ContextKey
imageKey image (ContextKey keyOrdinal) =
  ContextKey (unboxedIndexInvariant image keyOrdinal)
{-# INLINE imageKey #-}