packages feed

moonlight-core-0.1.0.0: src-solver/Moonlight/Core/UnionFind/Transaction/Internal/Policy.hs

-- | Pure dense-prefix sizing policy: how many dense slots to allocate for a
-- given key distribution, growth targets, and the in-bounds predicate. No 'ST'.
module Moonlight.Core.UnionFind.Transaction.Internal.Policy
  ( chooseDenseLength,
    selectDenseLength,
    countKeysBelow,
    densePrefixMap,
    denseTargetLength,
    denseKeyInBounds,
  )
where

import Data.IntMap.Strict (IntMap)
import Data.IntMap.Strict qualified as IntMap
import Data.Vector.Unboxed.Mutable qualified as Mutable
import Moonlight.Core.Identifier.EGraph (ClassId)
import Moonlight.Core.UnionFind.Transaction.Internal.Types
  ( DenseStore (..),
    maximumDenseSlots,
    minimumDenseSlots,
  )
import Prelude

chooseDenseLength :: IntMap ClassId -> Int
chooseDenseLength parents
  | IntMap.null parents =
      minimumDenseSlots
  | otherwise =
      selectDenseLength
        (IntMap.keys (densePrefixMap maximumDenseSlots parents))
        denseCandidates
        0
        0
  where
    denseCandidates =
      takeWhile
        (<= maximumDenseSlots)
        (iterate (* 2) minimumDenseSlots)

selectDenseLength ::
  [Int] ->
  [Int] ->
  Int ->
  Int ->
  Int
selectDenseLength remainingKeys remainingLimits observedCount bestLength =
  case remainingLimits of
    [] ->
      bestLength
    limit : trailingLimits ->
      let (countAtLimit, keysAtOrAboveLimit) =
            countKeysBelow limit observedCount remainingKeys
          accepted =
            countAtLimit > 0
              && ( limit == minimumDenseSlots
                     || countAtLimit * 4 >= limit * 3
                 )
          nextBest =
            if accepted
              then limit
              else bestLength
       in selectDenseLength keysAtOrAboveLimit trailingLimits countAtLimit nextBest

countKeysBelow :: Int -> Int -> [Int] -> (Int, [Int])
countKeysBelow limit =
  go
  where
    go count keys =
      case keys of
        key : trailingKeys
          | key < limit ->
              go (count + 1) trailingKeys
        _ ->
          (count, keys)

densePrefixMap :: Int -> IntMap value -> IntMap value
densePrefixMap limit entries
  | limit <= 0 =
      IntMap.empty
  | otherwise =
      let (_, nonNegativeEntries) = IntMap.split (-1) entries
          (prefixEntries, _) = IntMap.split limit nonNegativeEntries
       in prefixEntries

denseTargetLength :: Int -> Int -> Maybe Int
denseTargetLength currentLength key
  | key < 0 =
      Nothing
  | key >= maximumDenseSlots =
      Nothing
  | key < currentLength =
      Just currentLength
  | currentLength == 0 =
      if key < minimumDenseSlots
        then Just minimumDenseSlots
        else Nothing
  | key < min maximumDenseSlots (currentLength * 2) =
      Just (min maximumDenseSlots (currentLength * 2))
  | otherwise =
      Nothing

denseKeyInBounds :: DenseStore state -> Int -> Bool
denseKeyInBounds store key =
  key >= 0
    && key < Mutable.length (parent store)
{-# INLINE denseKeyInBounds #-}